diff --git a/README.md b/README.md index 2919ec83..fc0278d0 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,143 @@ -django-htk -========== +# HTK: Django Hacktoolkit -A set of apps, utilities, middlewares, etc for Django +A comprehensive Django framework providing reusable apps, utilities, and third-party integrations for rapid development. Designed for hackathons and production applications. -Used/embedded in -License: MIT +HTK includes: + +- **[Reusable Django Apps](./apps/README.md)** - Pre-built apps for accounts, organizations, payments, messaging, and more +- **[Third-Party Integrations](./lib/README.md)** - Ready-to-use connectors for 45+ external services (Stripe, Google, AWS, Slack, etc.) +- **[Utility Modules](./utils/README.md)** - Common patterns for caching, text processing, APIs, and data handling +- **[API Helpers](./api/README.md)** - Tools for building REST APIs with DataTables support +- **[Form Utilities](./forms/README.md)** - Base form classes and validators +- **[Decorators](./decorators/README.md)** - Django and function decorators for common tasks +- **[Models & Fields](./models/README.md)** - Abstract models and custom Django fields +- **[Middleware](./middleware/README.md)** - Request/response processing utilities + +## Quick Start + +### Using HTK Apps + +HTK provides pre-built Django apps that can be installed and configured in your project: + +```python +# settings.py +INSTALLED_APPS = [ + 'htk.apps.accounts', + 'htk.apps.organizations', + 'htk.apps.stripe_lib', + # ... more apps +] +``` + +### Common Patterns + +**Caching objects:** +```python +from htk.cache.classes import CacheableObject + +class UserFollowingCache(CacheableObject): + def get_cache_key_suffix(self): + return f'user_{self.user_id}_following' +``` + +**User authentication:** +```python +from htk.apps.accounts.backends import HtkUserTokenAuthBackend +from htk.apps.accounts.utils.auth import login_authenticated_user +``` + +**API endpoints:** +```python +from htk.api.utils import json_response_form_error, get_object_or_json_error +``` + +## Key Features + +### Accounts & Authentication +- User registration and email verification +- Social authentication backends (OAuth2 support) +- User profiles and email management +- Token-based authentication + +### Payments & Billing +- Stripe integration (customers, subscriptions, charges) +- Quote/Invoice system (CPQ) +- Payment tracking and history + +### Organizations +- Multi-org support with roles and permissions +- Org invitations and member management +- Permission-based access control + +### Messaging & Notifications +- Email notifications +- Slack integration +- Conversation/threading support + +### Utilities +- Text processing (formatting, translation, sanitization) +- Caching decorators and schemes +- CSV/PDF generation +- QR codes +- Geolocation and distance calculations +- Timezone handling + +### Third-Party Services +See [lib/README.md](./lib/README.md) for details on 45+ integrations including: +- Cloud: AWS S3, Google Cloud +- Communication: Slack, Discord, Gmail, Twilio +- Data: Airtable, Stripe, Shopify, Zuora +- Analytics: Iterable, Mixpanel +- Location: Google Maps, Mapbox, Zillow +- Search: Elasticsearch integration patterns + +## Project Structure + +``` +htk/ +├── apps/ # Pre-built Django apps +├── lib/ # Third-party service integrations +├── utils/ # Common utilities and helpers +├── models/ # Abstract models and field types +├── forms/ # Base form classes +├── api/ # REST API utilities +├── decorators/ # Function and class decorators +├── middleware/ # Request/response processing +├── cache/ # Caching framework +├── constants/ # Project-wide constants +├── extensions/ # Django extensions +├── templates/ # Reusable templates +└── templatetags/ # Custom template filters and tags +``` + +## Module Documentation + +For detailed information about each module, see: + +- **[Apps](./apps/README.md)** - Reusable Django application packages +- **[Libraries](./lib/README.md)** - Third-party service integrations +- **[Utilities](./utils/README.md)** - Helper functions and utilities +- **[API](./api/README.md)** - REST API patterns and tools +- **[Cache](./cache/README.md)** - Caching framework and patterns +- **[Forms](./forms/README.md)** - Form utilities and base classes +- **[Decorators](./decorators/README.md)** - Function and class decorators +- **[Models](./models/README.md)** - Abstract models and custom fields +- **[Validators](./validators/README.md)** - Validation utilities + +## Use Cases + +**Hackathons:** Rapidly build production-quality features with pre-built apps and integrations. + +**SaaS Applications:** Multi-organization support, billing, and user management out of the box. + +**E-commerce:** Stripe payments, inventory management, order processing. + +**Content Platforms:** User accounts, organizations, messaging, notifications. + +**Marketplaces:** Payment processing, user profiles, organization support. + +## Contributing + +HTK is designed to be extended. Create custom apps that inherit from abstract base classes and add your own business logic. diff --git a/admin/README.md b/admin/README.md new file mode 100644 index 00000000..d82bc6c7 --- /dev/null +++ b/admin/README.md @@ -0,0 +1,43 @@ +# Django Admin Utilities + +Custom admin display and customization utilities. + +## Quick Start + +```python +from htk.admin.decorators import django_admin_bool_field + +@django_admin_bool_field('is_active') +def is_active_display(obj): + return obj.is_active +is_active_display.short_description = 'Active' + +class UserAdmin(admin.ModelAdmin): + list_display = ['username', is_active_display] +``` + +## Boolean Field Display + +Display boolean fields as green checkmarks or red X marks in admin list view: + +```python +from htk.admin.decorators import django_admin_bool_field + +class ProductAdmin(admin.ModelAdmin): + list_display = ['name', is_available, is_featured] + + @django_admin_bool_field('is_available') + def is_available(obj): + return obj.is_available + + @django_admin_bool_field('is_featured') + def is_featured(obj): + return obj.is_featured +``` + +## Configuration + +```python +# settings.py +ADMIN_SITE_HEADER = 'My Admin' +``` diff --git a/admintools/README.md b/admintools/README.md new file mode 100644 index 00000000..56a87fec --- /dev/null +++ b/admintools/README.md @@ -0,0 +1,83 @@ +# Admin Tools + +Advanced admin functionality including user impersonation and company user management. + +## Quick Start + +```python +from htk.admintools.decorators import company_officer_required, company_employee_required +from htk.admintools.utils import is_allowed_to_emulate_users, request_emulate_user + +# Protect views for company officers +@company_officer_required +def officer_dashboard(request): + return render(request, 'officer_dashboard.html') + +# Protect views for company employees +@company_employee_required +def employee_portal(request): + return render(request, 'employee_portal.html') +``` + +## User Impersonation + +Allow admins to emulate other users for testing: + +```python +from htk.admintools.utils import is_allowed_to_emulate_users, request_emulate_user + +# Check if user can emulate others +if is_allowed_to_emulate_users(request.user): + # Allow user emulation in admin panel + pass + +# Emulate a specific user +request_emulate_user(request, original_user, target_user) +``` + +## Company User Management + +Check company affiliation and roles: + +```python +from htk.admintools.models import company_officer_required, is_company_officer, is_company_employee + +# Check officer status +if is_company_officer(user): + # User is a company officer + pass + +# Check employee status +if is_company_employee(user): + # User is a company employee + pass + +# Check email domain +if user.has_company_email_domain(): + # User has company email + pass +``` + +## Company Data Lookups + +Get mappings of company users: + +```python +from htk.admintools.utils import get_company_officers_id_email_map, get_company_employees_id_email_map + +# Get officers mapping +officers = get_company_officers_id_email_map() +# Returns: {user_id: email, ...} + +# Get employees mapping +employees = get_company_employees_id_email_map() +# Returns: {user_id: email, ...} +``` + +## Configuration + +```python +# settings.py +# Enable company user features +COMPANY_ADMIN_ENABLED = True +``` diff --git a/admintools/constants/README.md b/admintools/constants/README.md new file mode 100644 index 00000000..5e3864be --- /dev/null +++ b/admintools/constants/README.md @@ -0,0 +1,65 @@ +# Admin Tools Constants + +## Overview + +This module provides configuration constants for the admin tools dashboard and user emulation features. + +## Configuration Settings + +```python +from htk.admintools.constants import ( + HTK_COMPANY_EMAIL_DOMAINS, + HTK_COMPANY_OFFICER_EMAILS, + HTK_COMPANY_EMPLOYEE_EMAILS, + HTK_EMULATE_USER_COOKIE_EXPIRATION_MINUTES, + HTK_ADMINTOOLS_TODOS_CONFIGS +) + +# Email domains that identify company employees +HTK_COMPANY_EMAIL_DOMAINS = () # e.g. ('company.com', 'internal.company.com') + +# Email addresses of company officers +HTK_COMPANY_OFFICER_EMAILS = () # e.g. ('ceo@company.com', 'cto@company.com') + +# Email addresses of all company employees +HTK_COMPANY_EMPLOYEE_EMAILS = () # Subset of HTK_COMPANY_OFFICER_EMAILS + +# Session timeout for user emulation (minutes) +HTK_EMULATE_USER_COOKIE_EXPIRATION_MINUTES = 15 + +# List of admin todo configurations +HTK_ADMINTOOLS_TODOS_CONFIGS = [] +``` + +## Dashboard Constants + +```python +from htk.admintools.constants import ( + PULSE_RECENTLY_EDITED_PROFILES_LIMIT, + PULSE_RECENTLY_JOINED_USERS_LIMIT, + PULSE_RECENT_LOGINS_LIMIT, + PULSE_STATS_PRECISION, + ADMINTOOLS_USER_PAGE_SIZE +) + +# Dashboard data limits +PULSE_RECENTLY_EDITED_PROFILES_LIMIT = 50 +PULSE_RECENTLY_JOINED_USERS_LIMIT = 50 +PULSE_RECENT_LOGINS_LIMIT = 50 + +# Decimal precision for statistics +PULSE_STATS_PRECISION = 4 + +# User list pagination +ADMINTOOLS_USER_PAGE_SIZE = 25 +``` + +## Customization + +Override these settings in `settings.py`: + +```python +HTK_COMPANY_EMAIL_DOMAINS = ('acme.com', 'acme.internal') +HTK_EMULATE_USER_COOKIE_EXPIRATION_MINUTES = 30 +PULSE_RECENTLY_EDITED_PROFILES_LIMIT = 100 +``` diff --git a/api/README.md b/api/README.md new file mode 100644 index 00000000..57c62f7c --- /dev/null +++ b/api/README.md @@ -0,0 +1,77 @@ +# API Utilities + +Tools for building REST APIs and handling common patterns. + +## Overview + +The `api` module provides utilities for: + +- JSON responses with form errors +- Object retrieval with error handling +- Parameter extraction from requests +- DataTables server-side processing support + +## Quick Reference + +### Form Error Responses + +Return form validation errors as JSON: + +```python +from htk.api.utils import json_response_form_error + +if not form.is_valid(): + return json_response_form_error(form) +``` + +### Safe Object Retrieval + +Get an object or return a JSON error: + +```python +from htk.api.utils import get_object_or_json_error + +user = get_object_or_json_error(User, id=user_id) +if user: + # object exists +else: + # returns JSON error response +``` + +### Extract Parameters + +Extract expected parameters from POST data: + +```python +from htk.api.utils import extract_post_params + +params = extract_post_params(request.POST, ['name', 'email', 'age']) +``` + +## DataTables Integration + +Handle server-side DataTables requests with: + +```python +from htk.api.views import model_datatables_api_get_view + +# Add to your URL patterns +path('api/users/datatable/', model_datatables_api_get_view(User)) +``` + +The view automatically handles: +- Sorting +- Filtering +- Pagination +- Column selection + +## Classes + +- **`DataTablesQueryParams`** - Parses DataTables query parameters from requests + +## Functions + +- **`json_response_form_error`** - Returns Django form errors as JSON response +- **`extract_post_params`** - Safely extracts expected parameters from POST data +- **`get_object_or_json_error`** - Retrieves object or returns JSON error +- **`model_datatables_api_get_view`** - Generic DataTables API view for any model diff --git a/api/auth/README.md b/api/auth/README.md new file mode 100644 index 00000000..c3d34a41 --- /dev/null +++ b/api/auth/README.md @@ -0,0 +1,113 @@ +# API Authentication + +HTTP Bearer token authentication for REST APIs. + +## Quick Start + +```python +from htk.api.auth import HTTPBearerAuth +from requests.auth import HTTPBearer + +# Use with requests library +import requests + +headers = {'Authorization': 'Bearer your_token_here'} +response = requests.get('https://api.example.com/data', headers=headers) + +# Or use HTTPBearerAuth class +auth = HTTPBearerAuth(token='your_token_here') +response = requests.get('https://api.example.com/data', auth=auth) +``` + +## HTTPBearerAuth + +Bearer token authentication for API calls: + +```python +from htk.api.auth import HTTPBearerAuth + +# Create bearer auth with token +auth = HTTPBearerAuth(token='abc123xyz') + +# Use with any HTTP request +import requests +response = requests.get('https://api.example.com/users', auth=auth) + +# Token automatically included in Authorization header +# Header sent: Authorization: Bearer abc123xyz +``` + +## Common Patterns + +### API Client with Bearer Token + +```python +from htk.api.auth import HTTPBearerAuth +import requests + +class APIClient: + def __init__(self, base_url, token): + self.base_url = base_url + self.auth = HTTPBearerAuth(token=token) + + def get(self, endpoint): + url = f"{self.base_url}/{endpoint}" + response = requests.get(url, auth=self.auth) + return response.json() + + def post(self, endpoint, data): + url = f"{self.base_url}/{endpoint}" + response = requests.post(url, json=data, auth=self.auth) + return response.json() + +# Usage +client = APIClient('https://api.example.com', token='your_token') +users = client.get('users') +``` + +### Django REST Framework Integration + +```python +from rest_framework.authentication import TokenAuthentication +from rest_framework.views import APIView +from rest_framework.response import Response + +class ProtectedView(APIView): + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + + def get(self, request): + # Request must include: Authorization: Bearer token_value + return Response({'user': str(request.user)}) +``` + +### Token Management + +```python +from htk.api.auth import HTTPBearerAuth +import os + +# Load token from environment +api_token = os.environ.get('API_TOKEN') +auth = HTTPBearerAuth(token=api_token) + +# Rotate token by creating new auth instance +new_token = refresh_api_token() +auth = HTTPBearerAuth(token=new_token) +``` + +## Configuration + +```python +# settings.py +API_TOKEN = os.environ.get('API_TOKEN') +API_BASE_URL = os.environ.get('API_BASE_URL', 'https://api.example.com') +``` + +## Best Practices + +1. **Store tokens securely** - Use environment variables or secrets manager +2. **Never commit tokens** - Add to .gitignore and use environment variables +3. **Rotate tokens regularly** - Update tokens periodically for security +4. **Use HTTPS** - Always use HTTPS when sending Bearer tokens +5. **Include in request headers** - Format: `Authorization: Bearer ` diff --git a/apps/README.md b/apps/README.md new file mode 100644 index 00000000..77fc52fc --- /dev/null +++ b/apps/README.md @@ -0,0 +1,335 @@ +# Django Apps + +Reusable, extensible Django applications for common functionality. + +## Overview + +HTK provides 29 pre-built apps organized by feature: + +- **User Management** - Accounts, profiles, email management +- **Social & Authentication** - OAuth, social auth backends +- **Organization & Teams** - Multi-org support, permissions, invitations +- **Payments & Commerce** - Stripe, subscriptions, quotes, invoices +- **Communication** - Messaging, conversations, notifications +- **Content & Knowledge** - Bible, forums, feedback +- **Data Management** - Files, storage, key-value storage +- **Utilities** - Async tasks, features, changelog, documentation + +## User Management + +### Accounts (`accounts`) +User registration, authentication, and profile management. + +**Key Features:** +- User registration and email verification +- Email management (multiple emails per user) +- Token-based authentication +- User profiles with timezone and locale +- Social auth integration (OAuth, OAuth2) +- Password reset and recovery +- User search and lookup + +**Usage:** +```python +from htk.apps.accounts.utils.general import create_user, get_user_by_email + +user = create_user('user@example.com', password='secure') +user = get_user_by_email('user@example.com') +``` + +**Models:** +- `BaseAbstractUserProfile` - Extend for custom user profiles +- `UserEmail` - Multiple emails per user +- Django's built-in `User` model + +### Addresses (`addresses`) +Postal address management and geocoding. + +**Key Features:** +- Store and manage postal addresses +- Google Maps integration for address geocoding +- Latitude/longitude storage + +## Social & Authentication + +### OAuth & Social Auth +HTK supports social authentication through `python-social-auth`: +- Facebook, Twitter, Google, LinkedIn +- Withings (health data) +- Custom OAuth2 backends + +## Organization & Teams + +### Organizations (`organizations`) +Multi-organization support with role-based access control. + +**Key Features:** +- Create organizations with members +- Role-based permissions (owner, member, etc.) +- Organization invitations +- Member management +- Organization-specific settings + +**Usage:** +```python +from htk.apps.organizations.models import BaseOrganization + +org = BaseOrganization.objects.create(name='Acme Corp') +org.invite_member('user@example.com', role='member') +``` + +### Invitations (`invitations`) +Handle user invitations for onboarding and access control. + +**Key Features:** +- Invitation creation and tracking +- Email-based invitations +- Invitation expiration +- Integration with user onboarding + +## Payments & Commerce + +### Stripe Integration (`stripe_lib`) +Full Stripe integration for payments and subscriptions. + +**Key Features:** +- Stripe customer creation and management +- Credit card handling +- One-time charges and payments +- Recurring subscriptions +- Invoice generation + +**Usage:** +```python +from htk.apps.stripe_lib.models import BaseStripeCustomer + +customer = BaseStripeCustomer.objects.create(user=user, stripe_id='cus_xxx') +customer.charge(amount, stripe_token) +subscription = customer.create_subscription(plan_id) +``` + +### CPQ (Configure, Price, Quote) (`cpq`) +Quoting and invoicing system. + +**Key Features:** +- Create quotes for customers +- Line items and pricing +- Group quotes +- Invoice generation +- Payment tracking + +### Customers (`customers`) +Customer management for commerce apps. + +### Store (`store`) +E-commerce and store functionality. + +## Communication & Notifications + +### Notifications (`notifications`) +Send notifications via multiple channels. + +**Key Features:** +- Email notifications +- Slack integration +- Notification history and tracking + +### Conversations (`conversations`) +User-to-user messaging and conversations. + +**Key Features:** +- Create conversations between users +- Message threads with participants +- Emoji reactions to messages +- Conversation history + +**Usage:** +```python +from htk.apps.conversations.models import BaseConversation + +convo = BaseConversation.objects.create() +convo.add_participants(user1, user2) +convo.add_message(user1, 'Hello!') +``` + +### Forums (`forums`) +Discussion forums and message threads. + +**Key Features:** +- Create forums +- Forum threads and messages +- Tags for categorization +- Thread tracking + +## Content & Knowledge + +### Bible (`bible`) +Scripture data and reference tools. + +**Key Features:** +- Bible books, chapters, verses +- Scripture passage lookup +- Bible reference formatting + +### Feedback (`feedback`) +Collect user feedback and reviews. + +## Data & File Management + +### File Storage (`file_storage`) +Store uploaded files. + +**Key Features:** +- Handle file uploads +- Store files securely +- File organization + +### Blob Storage (`blob_storage`) +Binary large object (BLOB) storage. + +### KV Storage (`kv_storage`) +Key-value storage for flexible data. + +**Usage:** +```python +from htk.apps.kv_storage.utils import kv_put, kv_get + +kv_put('user:settings', {'theme': 'dark'}) +settings = kv_get('user:settings') +``` + +## Features & Configuration + +### Features (`features`) +Feature flags for gradual rollouts. + +**Key Features:** +- Enable/disable features per user or org +- A/B testing support +- Feature flag caching + +**Usage:** +```python +from htk.apps.features.utils import get_feature_flag + +if get_feature_flag('new_dashboard', user): + # Show new dashboard +``` + +### Maintenance Mode (`maintenance_mode`) +Enable maintenance mode to prevent user access. + +**Key Features:** +- Global maintenance mode toggle +- Exception list for admin access + +### Prelaunch (`prelaunch`) +Pre-launch signup and early access management. + +## API & Documentation + +### API (`api`) +REST API utilities and tools. See [api/README.md](../api/README.md). + +### Documentation (`documentation`) +Automatic README generation for modules. + +**Key Features:** +- Analyze Python modules +- Generate documentation +- Extract classes, functions, models + +### Changelog (`changelog`) +Track and generate changelogs from Git history. + +## Localization & Internationalization + +### i18n (`i18n`) +Internationalization and localization tools. + +**Key Features:** +- Multi-language string support +- Localizable and localized strings +- Language detection + +### Sites (`sites`) +Multi-site support (Django contrib wrapper). + +## Miscellaneous + +### Mobile (`mobile`) +Mobile app integration and push notifications. + +### MP (Materialized Properties) (`mp`) +Performance optimization through materialized properties. + +### Tokens (`tokens`) +API token management and authentication. + +### URL Shortener (`url_shortener`) +Create and manage short URLs. + +**Usage:** +```python +from htk.apps.url_shortener.models import HTKShortUrl + +short = HTKShortUrl.objects.create(url='https://very-long-url.com') +short_code = short.code # e.g., 'a7f2' +``` + +## Development Patterns + +### Extending Apps + +Extend abstract models to add custom fields: + +```python +from htk.apps.accounts.models import BaseAbstractUserProfile + +class CustomUserProfile(BaseAbstractUserProfile): + organization = ForeignKey(Organization) + department = CharField(max_length=100) +``` + +### Using Mixins + +Leverage app mixins for common functionality: + +```python +from htk.apps.accounts.mixins import UserOwnedMixin + +class MyModel(UserOwnedMixin): + # Automatically tracks owner and timestamps + pass +``` + +### Signal Handlers + +Apps provide signal handlers for key events: + +```python +from htk.apps.accounts.apps import create_user_profile + +# Automatically called when User is created +``` + +## Installation + +Enable apps in `settings.py`: + +```python +INSTALLED_APPS = [ + 'htk.apps.accounts', + 'htk.apps.organizations', + 'htk.apps.stripe_lib', + 'htk.apps.conversations', + 'htk.apps.notifications', + # ... more apps +] +``` + +Each app includes migrations for easy database setup: + +```bash +python manage.py migrate +``` diff --git a/apps/accounts/README.md b/apps/accounts/README.md new file mode 100644 index 00000000..5a6d1cee --- /dev/null +++ b/apps/accounts/README.md @@ -0,0 +1,196 @@ +# Accounts App + +User authentication, registration, and profile management. + +## Overview + +The `accounts` app provides: + +- User registration and email verification +- Email management (multiple emails per user) +- Social authentication (OAuth, OAuth2) +- Token-based API authentication +- User profiles with timezone and locale +- Password reset and recovery +- User search by email, username, or name +- User following/follower system + +## Quick Start + +### Create Users + +```python +from htk.apps.accounts.utils.general import create_user, get_user_by_email + +# Create a new user +user = create_user('user@example.com', password='secure_password') + +# Get user by email +user = get_user_by_email('user@example.com') + +# Get user by username +from htk.apps.accounts.utils.general import get_user_by_username +user = get_user_by_username('john_doe') +``` + +### Email Management + +```python +from htk.apps.accounts.models import UserEmail + +# Add additional email to user +user_email = UserEmail.objects.create( + user=user, + email='alternate@example.com', + verified=False +) + +# Set primary email +user.profile.set_primary_email('alternate@example.com') + +# Get all emails for user +emails = user.profile.get_nonprimary_emails() +``` + +### Token Authentication + +```python +from htk.apps.accounts.utils.auth import get_user_token_auth_token + +# Generate token for API authentication +token = get_user_token_auth_token(user) + +# Validate token +from htk.apps.accounts.utils.auth import validate_user_token_auth_token +is_valid = validate_user_token_auth_token(user, token) +``` + +### Social Authentication + +```python +# Configure in settings.py +AUTHENTICATION_BACKENDS = [ + 'htk.apps.accounts.backends.HtkUserTokenAuthBackend', + 'social_core.backends.facebook.FacebookOAuth2', + 'social_core.backends.google.GoogleOAuth2', +] + +# Use in views via python-social-auth +# /login/facebook/ +# /login/google-oauth2/ +``` + +### User Search + +```python +from htk.apps.accounts.search import ( + search_by_username, + search_by_email, + search_by_name, + combined_user_search +) + +# Search by username +users = search_by_username('john') + +# Search by email +user = search_by_email('user@example.com').first() + +# Combined search across username and name +users = combined_user_search('john', [search_by_username, search_by_name]) +``` + +### Following System + +```python +# Add follower +user.profile.add_follower(other_user) + +# Get followers +followers = user.profile.get_followers() + +# Get following +following = user.profile.get_following() + +# Check if user is followed +is_followed = user.profile.has_follower(other_user) +``` + +## Models + +- **`BaseAbstractUserProfile`** - Extend to add custom user profile fields +- **`UserEmail`** - Stores multiple emails per user + +## Key Features + +### Password Reset +```python +from htk.apps.accounts.utils.auth import validate_reset_password_token + +# Reset password +is_valid = validate_reset_password_token(user, token) +if is_valid: + from htk.apps.accounts.utils.auth import reset_user_password + reset_user_password(user, new_password) +``` + +### User Profile + +Inherit from `BaseAbstractUserProfile` to add custom fields: + +```python +from htk.apps.accounts.models import BaseAbstractUserProfile + +class CustomUserProfile(BaseAbstractUserProfile): + department = models.CharField(max_length=100) + company = models.CharField(max_length=100) + bio = models.TextField(blank=True) +``` + +### Timezone & Locale + +```python +from htk.apps.accounts.utils.locale import get_local_time + +# Get user's local time +local_time = get_local_time(user, aware_datetime) + +# Check if user is online during specific hours +from htk.apps.accounts.filters import users_currently_at_local_time +users = users_currently_at_local_time(User.objects.all(), 9, 17) +``` + +### Caching + +The app automatically caches: +- User followers (`UserFollowersCache`) +- User following (`UserFollowingCache`) +- Account activation reminders + +## Installation + +```python +# settings.py +INSTALLED_APPS = [ + 'htk.apps.accounts', + # ... +] + +# Add custom user profile +AUTH_USER_PROFILE_MODEL = 'myapp.CustomUserProfile' +``` + +## Signals + +Automatic signal handlers: +- `create_user_profile` - Creates profile when User is created +- `process_user_email_association` - Handles email verification + +## Best Practices + +1. **Extend BaseAbstractUserProfile** for custom user data +2. **Use search functions** for user lookups +3. **Enable social auth** for multi-provider login +4. **Cache user relationships** using provided cache classes +5. **Validate tokens** before granting access +6. **Handle multiple emails** through UserEmail model diff --git a/apps/accounts/api/README.md b/apps/accounts/api/README.md new file mode 100644 index 00000000..d9d0bf67 --- /dev/null +++ b/apps/accounts/api/README.md @@ -0,0 +1,136 @@ +# API + +## Overview + +This API module provides REST API endpoints for programmatic access to the service. Endpoints support standard HTTP methods and return JSON responses. + +## Quick Start + +### Basic Request + +```python +import requests + +# Make API request +response = requests.get('https://api.example.com/endpoint/', auth=auth) +result = response.json() +``` + +### Authentication + +API endpoints require authentication. Configure credentials: + +```python +from requests.auth import HTTPBearerAuth + +auth = HTTPBearerAuth(token='your_token') +response = requests.get('https://api.example.com/endpoint/', auth=auth) +``` + +## API Endpoints + +### Available Endpoints + +Check the `views.py` file for a complete list of available endpoints and their parameters. + +### Request Format + +``` +METHOD /endpoint/ HTTP/1.1 +Host: api.example.com +Content-Type: application/json +Authorization: Bearer + +{ + "param1": "value1", + "param2": "value2" +} +``` + +### Response Format + +Successful responses return HTTP 200 with JSON data. + +Error responses include status code and error message. + +## Common Operations + +### Get Resource + +```python +response = requests.get( + 'https://api.example.com/resource/{id}/', + auth=auth +) +resource = response.json() +``` + +### Create Resource + +```python +response = requests.post( + 'https://api.example.com/resource/', + json={'field': 'value'}, + auth=auth +) +new_resource = response.json() +``` + +### Update Resource + +```python +response = requests.patch( + 'https://api.example.com/resource/{id}/', + json={'field': 'new_value'}, + auth=auth +) +updated = response.json() +``` + +### Delete Resource + +```python +response = requests.delete( + 'https://api.example.com/resource/{id}/', + auth=auth +) +# Returns 204 No Content on success +``` + +## Error Handling + +```python +import requests +from requests.exceptions import RequestException + +try: + response = requests.get(endpoint, auth=auth) + response.raise_for_status() + data = response.json() +except requests.HTTPError as e: + print(f"HTTP Error: {e.response.status_code}") +except RequestException as e: + print(f"Request Error: {e}") +``` + +## Configuration + +Configure API settings in Django settings: + +```python +# settings.py +HTK_API_ENABLED = True +HTK_API_TIMEOUT = 30 +HTK_API_MAX_RETRIES = 3 +HTK_API_RATE_LIMIT = 1000 +``` + +## Best Practices + +1. **Handle errors** - Implement proper error handling for all requests +2. **Use pagination** - For list endpoints, use limit and offset parameters +3. **Add retries** - Implement exponential backoff for transient failures +4. **Cache responses** - Cache frequently accessed data when appropriate +5. **Validate input** - Validate request parameters before sending +6. **Log requests** - Log all API calls for debugging and monitoring +7. **Set timeouts** - Always set request timeouts to prevent hanging diff --git a/apps/accounts/constants/README.md b/apps/accounts/constants/README.md new file mode 100644 index 00000000..47c65212 --- /dev/null +++ b/apps/accounts/constants/README.md @@ -0,0 +1,121 @@ +# Constants + +Configuration constants for the accounts app, including authentication settings, email templates, social auth providers, and user model references. + +## Imports + +```python +from htk.apps.accounts.constants import ( + HTK_ACCOUNTS_DEFAULT_DISPLAY_NAME, + HTK_ACCOUNTS_CONFIRM_EMAIL_URL_NAME, + HTK_DEFAULT_LOGGED_IN_ACCOUNT_HOME, + HTK_USER_PROFILE_MODEL, + HTK_SOCIAL_AUTH_PROVIDERS, + USERNAME_MAX_LENGTH, + EMAIL_ACTIVATION_KEY_EXPIRATION_HOURS, + SocialAuth, + SOCIAL_AUTHS, +) +``` + +## Configuration Constants + +### URL Settings + +- `HTK_ACCOUNTS_CONFIRM_EMAIL_URL_NAME` - URL name for email confirmation view +- `HTK_API_USERS_FOLLOW_URL_NAME` - URL name for follow user endpoint +- `HTK_API_USERS_UNFOLLOW_URL_NAME` - URL name for unfollow user endpoint +- `HTK_DEFAULT_LOGGED_IN_ACCOUNT_HOME` - Default home URL for logged-in users +- `HTK_ACCOUNTS_REGISTER_SOCIAL_LOGIN_URL_NAME` - Social login registration URL +- `HTK_ACCOUNTS_REGISTER_SOCIAL_EMAIL_URL_NAME` - Social email registration URL +- `HTK_ACCOUNTS_REGISTER_SOCIAL_ALREADY_LINKED_URL_NAME` - Already linked account URL +- `HTK_ACCOUNTS_REGISTER_SOCIAL_EMAIL_AND_TERMS_URL_NAME` - Email and terms registration URL +- `HTK_ACCOUNTS_RESET_PASSWORD_URL_NAME` - Password reset URL +- `HTK_ACCOUNTS_RESEND_CONFIRMATION` - Resend confirmation email URL + +### User Settings + +- `HTK_ACCOUNTS_DEFAULT_DISPLAY_NAME` - Default name for new users (default: `'User'`) +- `HTK_USER_PROFILE_MODEL` - Custom user profile model (default: `None`) +- `HTK_VALID_USERNAME_REGEX` - Pattern for valid usernames: `r'^[A-Za-z0-9_-]{1,30}$'` +- `HTK_USERNAME_HELP_TEXT` - Help text for username field +- `USERNAME_MAX_LENGTH` - Maximum username length (30 characters) + +### Authentication & Security + +- `HTK_ACCOUNTS_CHANGE_PASSWORD_UPDATE_SESSION_AUTH_HASH` - Update session hash on password change (default: `True`) +- `HTK_USER_ID_XOR` - XOR key for encoding user IDs (default: `314159265`) +- `HTK_USER_TOKEN_AUTH_ENCRYPTION_KEY` - Key for token auth encryption +- `HTK_USER_TOKEN_AUTH_EXPIRES_MINUTES` - Token auth expiration in minutes (default: `15`) + +### Email Settings + +- `HTK_ACCOUNT_EMAIL_SUBJECT_ACTIVATION` - Subject for activation email +- `HTK_ACCOUNT_EMAIL_SUBJECT_PASSWORD_CHANGED` - Subject for password change email +- `HTK_ACCOUNT_EMAIL_SUBJECT_PASSWORD_RESET` - Subject for password reset email +- `HTK_ACCOUNT_EMAIL_SUBJECT_WELCOME` - Subject for welcome email +- `HTK_ACCOUNT_EMAIL_BCC_ACTIVATION` - BCC activation emails (default: `True`) +- `HTK_ACCOUNT_EMAIL_BCC_WELCOME` - BCC welcome emails (default: `True`) +- `HTK_ACCOUNT_ACTIVATION_REMINDER_EMAIL_TEMPLATE` - Template for activation reminder + +### Registration & Activation + +- `HTK_ACCOUNT_ACTIVATE_UPON_REGISTRATION` - Auto-activate accounts on registration (default: `False`) +- `HTK_ACCOUNTS_REGISTER_SET_PRETTY_USERNAME_FROM_EMAIL` - Generate username from email (default: `False`) +- `HTK_ACCOUNTS_SOCIAL_AUTO_ASSOCIATE_BACKENDS` - Backends for auto-linking social accounts (default: `[]`) +- `EMAIL_ACTIVATION_KEY_EXPIRATION_HOURS` - Activation key expiration time (48 hours) +- `EMAIL_ACTIVATION_KEY_REUSE_THRESHOLD_HOURS` - Threshold before activation key can be reused + +### User Attributes & Social Auth + +- `HTK_USER_ATTRIBUTE_DEFAULTS` - Default attributes for new users (default: `{}`) +- `HTK_SOCIAL_AUTH_PROVIDERS` - List of available social auth providers +- `HTK_SOCIAL_AUTH_LOGIN_PROVIDERS` - List of providers allowed for login + +## Enums + +### ProfileAvatarType + +Avatar source types for user profiles: + +```python +from htk.apps.accounts.enums import ProfileAvatarType + +# Available avatar types with values +ProfileAvatarType.PROFILE # value: 1 +ProfileAvatarType.GRAVATAR # value: 100 +ProfileAvatarType.FACEBOOK # value: 101 +ProfileAvatarType.TWITTER # value: 102 + +# Access enum properties +avatar_type = ProfileAvatarType.GRAVATAR +print(f"{avatar_type.name}: {avatar_type.value}") # GRAVATAR: 100 +``` + +## Classes + +### SocialAuth + +Dataclass representing a social authentication provider: + +```python +from htk.apps.accounts.constants import SocialAuth, SOCIAL_AUTHS + +# Access defined social auths +for auth in SOCIAL_AUTHS: + print(f"{auth.name}: {auth.provider}") + +# Create custom SocialAuth instance +custom_auth = SocialAuth( + provider='custom-provider', + name='Custom', + bg_color='#ff0000', + fa_icon='fa-solid fa-custom' +) +``` + +Available providers in `SOCIAL_AUTHS`: Apple, Discord, Facebook, Fitbit, GitHub, Google, LinkedIn, Strava, Twitter, Withings + +## Search Constants + +- `DEFAULT_NUM_SEARCH_RESULTS` - Default number of search results (20) diff --git a/apps/accounts/forms/README.md b/apps/accounts/forms/README.md new file mode 100644 index 00000000..33978245 --- /dev/null +++ b/apps/accounts/forms/README.md @@ -0,0 +1,140 @@ +# Forms + +## Overview + +This forms module provides Django forms for handling user input, validation, and rendering form fields. + +## Quick Start + +### User Registration Form + +```python +from htk.apps.accounts.forms.auth import UserRegistrationForm + +def register_view(request): + if request.method == 'POST': + form = UserRegistrationForm(request.POST) + if form.is_valid(): + user = form.save() + return redirect('login') + else: + form = UserRegistrationForm() + + return render(request, 'accounts/register.html', {'form': form}) +``` + +### Change Password Form + +```python +from htk.apps.accounts.forms.update import ChangePasswordForm + +def change_password_view(request): + if request.method == 'POST': + form = ChangePasswordForm(request.user, request.POST) + if form.is_valid(): + user = form.save() + return redirect('account_settings') + else: + form = ChangePasswordForm(request.user) + + return render(request, 'accounts/change_password.html', {'form': form}) +``` + +### Form in Template + +```django +
+ {% csrf_token %} + {{ form.as_p }} + +
+``` + +## Available Forms + +Forms are defined in `forms.py` and provide: + +- **Field validation** - Server-side validation of input +- **Widget customization** - HTML rendering and attributes +- **Error messages** - Clear error messages for invalid input +- **Help text** - Guidance for users on each field + +## Validation + +### Field Validation + +Built-in validation for user registration: + +```python +from htk.apps.accounts.forms.auth import UserRegistrationForm + +# Validates: +# - username uniqueness +# - password strength +# - password confirmation match +# - email format (if email field present) +form = UserRegistrationForm(request.POST) +if form.is_valid(): + # All fields passed validation + user = form.save() +``` + +### Custom Validation + +```python +from htk.apps.accounts.forms.update import ChangeUsernameForm + +class ChangeUsernameForm(UserUpdateForm): + username = forms.CharField(max_length=30) + + def clean_username(self): + username = self.cleaned_data['username'] + # Custom validation: check if username available + if User.objects.filter(username=username).exists(): + raise forms.ValidationError('Username already taken') + return username +``` + +## Form Usage + +### Validate User Input + +```python +from htk.apps.accounts.forms.auth import UserRegistrationForm + +form = UserRegistrationForm(request.POST) +if form.is_valid(): + username = form.cleaned_data['username'] + user = form.save() +else: + errors = form.errors # Dict of all field errors + username_errors = form.errors['username'] # Get specific field errors +``` + +### Create Form Instance + +```python +from htk.apps.accounts.forms.update import UserUpdateForm + +# Empty form +form = UserUpdateForm() + +# With initial data +form = UserUpdateForm(initial={'email': 'user@example.com'}) + +# With POST data +form = UserUpdateForm(request.POST) + +# With instance (update) +form = UserUpdateForm(instance=request.user) +``` + +## Best Practices + +1. **Always validate** - Never trust user input +2. **Clear error messages** - Provide helpful validation feedback +3. **Use ModelForm** - For forms tied to Django models +4. **CSRF protection** - Always include {% csrf_token %} +5. **Test validation** - Test all validation rules +6. **Sanitize input** - Use Django built-in cleaning +7. **Help text** - Provide guidance for complex fields diff --git a/apps/accounts/utils/README.md b/apps/accounts/utils/README.md new file mode 100644 index 00000000..8b3dff27 --- /dev/null +++ b/apps/accounts/utils/README.md @@ -0,0 +1,191 @@ +# HTK Accounts Utils + +Utilities for user account management, authentication, email handling, encryption, and user lookups. + +## Functions by Category + +### Authentication + +**login_authenticated_user(request, authenticated_user, backend=None)** +- Logs in an authenticated user and updates locale info from request +- Updates Iterable notifications if enabled + +**get_user_token_auth_token(user, expires_minutes=None)** +- Generates a token for direct user login (typically for email links) +- Returns base64-encoded JSON with encrypted user ID, expiration, and hash + +**validate_user_token_auth_token(token)** +- Validates a user token-auth token +- Returns tuple: `(user, is_valid)` where user is None if invalid + +**validate_reset_password_token(uid_base36, token)** +- Validates Django password reset token +- Returns User if valid, None otherwise + +**reset_user_password(request, user, new_password1, new_password2=None, email_template=None)** +- Resets user password with form validation +- Returns tuple: `(success, updated_user, form)` + +### User Lookup + +**get_user_by_username(username, UserModel=None)** +- Gets user by exact username match +- Returns None if not found + +**get_user_by_email(email)** +- Gets user by confirmed email or active user email +- Raises `NonUniqueEmail` if multiple confirmed emails exist + +**get_user_by_email_with_retries(email, max_attempts=4)** +- Retries `get_user_by_email()` with exponential backoff +- Mitigates race conditions during account creation + +**get_incomplete_signup_user_by_email(email)** +- Gets inactive user by unconfirmed email +- Returns None if not found or data inconsistency + +**get_user_by_id(user_id)** +- Gets user by ID +- Returns None if not found + +**get_users_by_id(user_ids, strict=False, preserve_ordering=False)** +- Gets list of users by IDs +- If strict=True, all IDs must exist or None returned + +**get_user_emails_by_id(user_email_ids, strict=False)** +- Gets list of UserEmail objects by IDs +- Returns partial list or None based on strict mode + +### User Creation + +**create_user(first_name, last_name, email, username_prefix=None, set_password=True)** +- Creates new user with optional custom username prefix +- Associates user with email and generates random password if set_password=True +- Returns tuple: `(user, password)` + +**set_random_password(user, password_length=16)** +- Sets random password using hex UUID +- Returns generated password + +**authenticate_user(request, username, password)** +- Authenticates user by username and password +- Returns authenticated user or None + +**authenticate_user_by_email(request, email, password)** +- Authenticates user by email and password +- Looks up user by email first + +**authenticate_user_by_username_email(request, username_email, password)** +- Authenticates user by username or email (auto-detects) +- Returns authenticated user or None + +**authenticate_user_by_basic_auth_credentials(request, credentials)** +- Authenticates user from base64-encoded "username:password" +- Returns authenticated user or None + +### Email Management + +**get_user_email(user, email, is_confirmed=True)** +- Gets UserEmail object for user and email +- Returns None if not found + +**associate_user_email(user, email, replacing=None, domain=None, email_template=None, email_subject=None, email_sender=None, confirmed=False)** +- Associates email with user, sends activation email if needed +- Handles email conflicts and notifies about updates +- Returns UserEmail or None + +**extract_user_email(username_email)** +- Gets user from username or email string +- Auto-detects email format +- Returns tuple: `(user, email)` + +**notify_user_email_update(user, old_email, new_email)** +- Notifies Iterable of email change if enabled + +### Encryption + +**encrypt_uid(user)** +- Encrypts user ID using XOR key +- Returns base36-encoded encrypted ID + +**decrypt_uid(encrypted_uid)** +- Decrypts user ID from base36 string +- Returns integer user ID + +**resolve_encrypted_uid(encrypted_uid)** +- Gets User object from encrypted UID +- Returns None if invalid or not found + +### Query/Lookup + +**get_all_users(active=True)** +- Gets all users, optionally filtered by active status +- Returns QuerySet + +**get_inactive_users()** +- Gets all inactive users +- Returns QuerySet + +**get_users_with_attribute_value(key, value, as_bool=False, active=True)** +- Gets users with attribute value match +- Optionally converts value to boolean + +**get_users_currently_at_local_time(start_hour, end_hour, isoweekdays=None, active=True)** +- Gets users whose current local time is in range +- Optional day of week filtering (Monday=1, Sunday=7) + +**get_users_with_no_confirmed_emails()** +- Gets users with no confirmed email addresses +- Returns QuerySet + +**get_duplicate_emails()** +- Sanity check utility finding duplicate emails in database +- Returns sorted list of duplicate email addresses + +### Utility + +**get_user_profile_model()** +- Resolves HTK_USER_PROFILE_MODEL setting +- Returns model class + +**email_to_username_hash(email)** +- Converts email to base64-encoded SHA256 hash (max length from constant) +- Case-insensitive, handles internationalized emails + +**email_to_username_pretty_unique(email)** +- Converts email to human-readable unique username +- Extracts handle from email, appends hash if needed + +**get_local_time(dt=None, user=None)** +- Converts datetime to user's local timezone +- Uses current authenticated user if not specified + +**localized_time_for_user(naive_dt, user=None)** +- Attaches timezone to naive datetime for user +- Useful for processing user-generated content timestamps + +**create_missing_user_profiles()** +- Creates missing UserProfile objects for existing users +- One-time migration utility + +**get_social_auth_for_user(user, provider)** +- Gets first UserSocialAuth for user and provider +- Returns None if not found + +**get_social_auths_for_user(user, provider=None)** +- Gets UserSocialAuth objects for user +- Optionally filters by provider +- Returns QuerySet + +## Common Imports + +```python +from htk.apps.accounts.utils import ( + login_authenticated_user, + get_user_by_email, + authenticate_user_by_email, + create_user, + encrypt_uid, + resolve_encrypted_uid, +) +``` diff --git a/apps/addresses/README.md b/apps/addresses/README.md new file mode 100644 index 00000000..2c7b5283 --- /dev/null +++ b/apps/addresses/README.md @@ -0,0 +1,48 @@ +# Addresses App + +Postal address management with geocoding support. + +## Quick Start + +```python +from htk.apps.addresses.models import BasePostalAddress + +# Create address +address = BasePostalAddress.objects.create( + street_1='123 Main St', + city='San Francisco', + state='CA', + postal_code='94105', + country='US' +) + +# Get Google Maps static image +from htk.apps.addresses.mixins import get_static_google_map_image_url +map_url = get_static_google_map_image_url(address) +``` + +## Models + +- **`BasePostalAddress`** - Postal address with geocoding + +## Integration + +```python +# Add address to user profile +class UserProfile(BaseAbstractUserProfile): + address = OneToOneField(BasePostalAddress, null=True) +``` + +## Common Patterns + +```python +# Get full address string +full_address = f"{address.street_1}, {address.city}, {address.state} {address.postal_code}" + +# Distance calculation +from htk.utils.maths import haversine_distance +distance = haversine_distance( + lat1, lon1, + address.latitude, address.longitude +) +``` diff --git a/apps/assessments/README.md b/apps/assessments/README.md new file mode 100644 index 00000000..b3515464 --- /dev/null +++ b/apps/assessments/README.md @@ -0,0 +1,316 @@ +# Assessments App + +Quiz and assessment management system for knowledge testing. + +## Quick Start + +```python +from htk.apps.assessments.models import ( + AbstractAssessment, + AbstractAssessmentQuestion, + AbstractAssessmentQuestionAnswerOption +) + +# Create assessment +assessment = AbstractAssessment.objects.create( + name='Django Fundamentals Quiz', + description='Test your Django knowledge', + passing_score=70 +) + +# Create question +question = AbstractAssessmentQuestion.objects.create( + assessment=assessment, + text='What is Django?', + order=1 +) + +# Add answer options +AbstractAssessmentQuestionAnswerOption.objects.create( + question=question, + text='Python web framework', + is_correct=True, + order=1 +) + +AbstractAssessmentQuestionAnswerOption.objects.create( + question=question, + text='Database system', + is_correct=False, + order=2 +) +``` + +## Building Assessments + +### Create Assessment + +```python +from htk.apps.assessments.models import AbstractAssessment + +# Create quiz +assessment = AbstractAssessment.objects.create( + name='Company Values Assessment', + description='Assess your understanding of company values', + passing_score=60, + time_limit=600 # seconds +) +``` + +### Add Questions + +```python +from htk.apps.assessments.models import AbstractAssessmentQuestion + +# Single choice question +question = AbstractAssessmentQuestion.objects.create( + assessment=assessment, + text='Which is our core value?', + question_type='single_choice', + order=1 +) + +# Multiple choice question +question = AbstractAssessmentQuestion.objects.create( + assessment=assessment, + text='Select all that apply', + question_type='multiple_choice', + order=2 +) + +# Free text question +question = AbstractAssessmentQuestion.objects.create( + assessment=assessment, + text='Explain your answer', + question_type='text', + order=3 +) +``` + +### Add Answer Options + +```python +from htk.apps.assessments.models import AbstractAssessmentQuestionAnswerOption + +# Create options for question +options_data = [ + ('Innovation', True), + ('Profit Only', False), + ('Customer Focus', True), +] + +for text, is_correct in options_data: + AbstractAssessmentQuestionAnswerOption.objects.create( + question=question, + text=text, + is_correct=is_correct + ) +``` + +## Taking Assessments + +### Track User Responses + +```python +from htk.apps.assessments.models import AbstractAssessmentResponse + +# Submit answer +response = AbstractAssessmentResponse.objects.create( + assessment=assessment, + user=user, + question=question, + selected_option=option, + is_correct=option.is_correct +) +``` + +### Score Assessment + +```python +from htk.apps.assessments.models import AbstractAssessment, AbstractAssessmentResponse + +# Calculate score +assessment = AbstractAssessment.objects.get(id=assessment_id) +user_responses = AbstractAssessmentResponse.objects.filter( + assessment=assessment, + user=user +) + +total_questions = assessment.questions.count() +correct_answers = user_responses.filter(is_correct=True).count() +score_percentage = (correct_answers / total_questions) * 100 + +passed = score_percentage >= assessment.passing_score +``` + +## Common Patterns + +### Assessment Analytics + +```python +from django.db.models import Avg, Count +from htk.apps.assessments.models import AbstractAssessment, AbstractAssessmentResponse + +# Get assessment statistics +assessment = AbstractAssessment.objects.get(id=assessment_id) +responses = AbstractAssessmentResponse.objects.filter(assessment=assessment) + +stats = { + 'total_attempts': AbstractAssessmentResponse.objects.values('user').distinct().count(), + 'average_score': responses.filter(is_correct=True).count() / responses.count() * 100, + 'pass_rate': ( + AbstractAssessmentResponse.objects.filter( + assessment=assessment, + score__gte=assessment.passing_score + ).values('user').distinct().count() / + AbstractAssessmentResponse.objects.filter( + assessment=assessment + ).values('user').distinct().count() * 100 + ) +} +``` + +### Difficulty Analysis + +```python +from django.db.models import Avg, Count +from htk.apps.assessments.models import AbstractAssessmentQuestion + +# Get most difficult questions +questions = AbstractAssessmentQuestion.objects.filter( + assessment=assessment +).annotate( + correct_count=Count( + 'assessmentresponse', + filter=Q(assessmentresponse__is_correct=True) + ) +) + +# Sort by difficulty (fewer correct answers = harder) +difficult = questions.order_by('correct_count') +``` + +### Timed Assessments + +```python +from django.utils import timezone +from datetime import timedelta + +# Track time taken +start_time = timezone.now() + +# Check time limit +assessment_response = { + 'assessment': assessment, + 'user': user, + 'started_at': start_time, + 'time_limit': assessment.time_limit +} + +# Check if time expired +elapsed = (timezone.now() - start_time).total_seconds() +time_remaining = assessment.time_limit - elapsed +expired = time_remaining <= 0 +``` + +### Adaptive Questions + +```python +from htk.apps.assessments.models import AbstractAssessmentQuestion + +# Show next question based on performance +def get_next_question(user, assessment): + # Get answered questions + answered = AbstractAssessmentResponse.objects.filter( + user=user, + assessment=assessment + ).values_list('question_id', flat=True) + + # Get unanswered questions + next_question = AbstractAssessmentQuestion.objects.filter( + assessment=assessment + ).exclude( + id__in=answered + ).first() + + return next_question +``` + +### Generate Certificates + +```python +from django.utils import timezone + +# Award certificate on passing +def check_and_award_certificate(user, assessment): + from htk.apps.assessments.models import AssessmentCertificate + + score = calculate_score(user, assessment) + if score >= assessment.passing_score: + cert = AssessmentCertificate.objects.create( + user=user, + assessment=assessment, + score=score, + awarded_at=timezone.now() + ) + return cert +``` + +## Models + +### AbstractAssessment + +```python +class AbstractAssessment(models.Model): + name = CharField(max_length=200) + description = TextField(blank=True) + passing_score = IntegerField(default=60) # Percentage + time_limit = IntegerField(null=True, blank=True) # Seconds + created = DateTimeField(auto_now_add=True) +``` + +### AbstractAssessmentQuestion + +```python +class AbstractAssessmentQuestion(models.Model): + assessment = ForeignKey(AbstractAssessment, on_delete=models.CASCADE) + text = TextField() + question_type = CharField(max_length=50) # 'single_choice', 'multiple_choice', 'text' + order = IntegerField() +``` + +### AbstractAssessmentQuestionAnswerOption + +```python +class AbstractAssessmentQuestionAnswerOption(models.Model): + question = ForeignKey(AbstractAssessmentQuestion, on_delete=models.CASCADE) + text = CharField(max_length=500) + is_correct = BooleanField() + order = IntegerField() +``` + +## Configuration + +```python +# settings.py +ASSESSMENT_PASSING_SCORE = 70 +ASSESSMENT_QUESTION_TYPES = [ + ('single_choice', 'Single Choice'), + ('multiple_choice', 'Multiple Choice'), + ('text', 'Free Text'), +] + +ASSESSMENT_RANDOMIZE_QUESTIONS = True +ASSESSMENT_RANDOMIZE_OPTIONS = True +ASSESSMENT_SHOW_CORRECT_ANSWERS = True # After completion +``` + +## Best Practices + +1. **Clear questions** - Write unambiguous assessment questions +2. **Balanced difficulty** - Mix easy and hard questions +3. **Multiple options** - Provide plausible distractor options +4. **Set passing score** - Define clear passing criteria +5. **Provide feedback** - Show correct answers after completion +6. **Track analytics** - Monitor question difficulty +7. **Time limits** - Use for timed assessments diff --git a/apps/async_task/README.md b/apps/async_task/README.md new file mode 100644 index 00000000..bdef5e4d --- /dev/null +++ b/apps/async_task/README.md @@ -0,0 +1,56 @@ +# Async Task App + +Asynchronous background task execution and result tracking. + +## Quick Start + +```python +from htk.apps.async_task.models import AsyncTask + +# Create async task +task = AsyncTask.objects.create( + task_name='generate_report', + status='pending' +) + +# Execute task +task.execute() # Run in background + +# Check status +task.refresh_from_db() +if task.status == 'completed': + result = task.result +``` + +## Common Patterns + +```python +# Build result from JSON +from htk.apps.async_task.utils import build_async_task_result + +result = build_async_task_result( + task, + data={'report_id': 123, 'url': '/reports/123/'} +) + +# Extract result values +from htk.apps.async_task.utils import extract_async_task_result_json_values + +values = extract_async_task_result_json_values(task) +``` + +## Views + +```python +# Download task result +from htk.apps.async_task.views import async_download_result +# GET /async-task/123/download/ +``` + +## Configuration + +```python +# settings.py +ASYNC_TASK_TIMEOUT = 3600 # 1 hour +ASYNC_TASK_MAX_RETRIES = 3 +``` diff --git a/apps/bible/README.md b/apps/bible/README.md new file mode 100644 index 00000000..b09eac00 --- /dev/null +++ b/apps/bible/README.md @@ -0,0 +1,57 @@ +# Bible App + +Scripture data and Bible reference tools. + +## Quick Start + +```python +from htk.apps.bible.models import ( + AbstractBibleBook, + AbstractBibleChapter, + AbstractBibleVerse, + AbstractBiblePassage +) + +# Create book +book = AbstractBibleBook.objects.create(name='Genesis', abbreviation='Gen') + +# Create chapter +chapter = AbstractBibleChapter.objects.create(book=book, number=1) + +# Create verses +verse = AbstractBibleVerse.objects.create( + chapter=chapter, + number=1, + text='In the beginning God created the heavens and the earth.' +) + +# Create passage (multiple verses) +passage = AbstractBiblePassage.objects.create( + book=book, + chapter_start=1, + verse_start=1, + chapter_end=1, + verse_end=3 +) +``` + +## Common Patterns + +```python +# Get scripture references +from htk.apps.bible.utils.references import get_scripture_references_compact + +refs = get_scripture_references_compact('John 3:16') + +# Query verses +john = AbstractBibleBook.objects.get(abbreviation='John') +chapter3 = john.chapters.get(number=3) +verse16 = chapter3.verses.get(number=16) +``` + +## Models + +- **`AbstractBibleBook`** - Books of the Bible +- **`AbstractBibleChapter`** - Chapters within books +- **`AbstractBibleVerse`** - Individual verses +- **`AbstractBiblePassage`** - Multiple verses/passages diff --git a/apps/bible/constants/README.md b/apps/bible/constants/README.md new file mode 100644 index 00000000..38d2d00f --- /dev/null +++ b/apps/bible/constants/README.md @@ -0,0 +1,79 @@ +# Bible Constants + +## Overview + +This module provides constants for Bible book metadata, aliases, and translation models. It includes comprehensive lists of all 66 canonical Bible books with chapter counts and common abbreviation mappings. + +## Constants + +### Books and Metadata + +- **`BIBLE_BOOKS`** - List of all 66 canonical Bible book names +- **`BIBLE_BOOKS_DATA`** - List of dicts with book metadata: `name` and `chapters` count +- **`BIBLE_BOOKS_ALIASES`** - Dict mapping book names to lists of common abbreviations (e.g., 'Gen', 'Matt') +- **`BIBLE_BOOKS_ALIAS_MAPPINGS`** - Dict mapping all aliases and case variants to canonical book names + +### Model References + +- **`HTK_BIBLE_BOOK_MODEL`** - Default: `'bible.BibleBook'` +- **`HTK_BIBLE_CHAPTER_MODEL`** - Default: `'bible.BibleChapter'` +- **`HTK_BIBLE_VERSE_MODEL`** - Default: `'bible.BibleVerse'` +- **`HTK_BIBLE_PASSAGE_MODEL`** - Default: `'bible.BiblePassage'` +- **`HTK_BIBLE_NASB_VERSE_MODEL`** - Default: `'bible.NASBVerse'` +- **`HTK_BIBLE_TRANSLATIONS_MAP`** - Dict mapping translation codes to model strings + +## Enums + +### BibleTestament + +Bible division (Old Testament or New Testament): + +```python +from htk.apps.bible.enums import BibleTestament + +# Available testaments with values +BibleTestament.OT # value: 1 (Old Testament) +BibleTestament.NT # value: 2 (New Testament) + +# Access enum properties +testament = BibleTestament.OT +print(f"{testament.name}: {testament.value}") # OT: 1 +``` + +## Usage Examples + +### Access Book Information + +```python +from htk.apps.bible.constants import BIBLE_BOOKS, BIBLE_BOOKS_DATA + +# Get list of all book names +for book_name in BIBLE_BOOKS: + print(book_name) # Genesis, Exodus, ... + +# Get book with chapter count +genesis = BIBLE_BOOKS_DATA[0] +print(f"{genesis['name']}: {genesis['chapters']} chapters") +``` + +### Resolve Book Aliases + +```python +from htk.apps.bible.constants import BIBLE_BOOKS_ALIAS_MAPPINGS + +# Find canonical name from abbreviation +canonical = BIBLE_BOOKS_ALIAS_MAPPINGS['Matt'] # Returns 'Matthew' +canonical = BIBLE_BOOKS_ALIAS_MAPPINGS['matt'] # Case-insensitive +``` + +### Configure Models + +```python +# In Django settings.py +HTK_BIBLE_BOOK_MODEL = 'myapp.CustomBibleBook' +HTK_BIBLE_VERSE_MODEL = 'myapp.CustomVerse' +HTK_BIBLE_TRANSLATIONS_MAP = { + 'NASB': 'myapp.NASBVersion', + 'ESV': 'myapp.ESVVersion', +} +``` diff --git a/apps/bible/utils/README.md b/apps/bible/utils/README.md new file mode 100644 index 00000000..2de5a7e9 --- /dev/null +++ b/apps/bible/utils/README.md @@ -0,0 +1,116 @@ +# HTK Bible Utils + +Utilities for Bible data management including scripture lookups, references, seeding, and translations. + +## Functions by Category + +### Model Getters + +**get_bible_book_model()** +- Resolves HTK_BIBLE_BOOK_MODEL setting +- Returns BibleBook model class + +**get_bible_chapter_model()** +- Resolves HTK_BIBLE_CHAPTER_MODEL setting +- Returns BibleChapter model class + +**get_bible_verse_model()** +- Resolves HTK_BIBLE_VERSE_MODEL setting +- Returns BibleVerse model class + +**get_bible_passage_model()** +- Resolves HTK_BIBLE_PASSAGE_MODEL setting +- Returns BiblePassage model class + +### Lookup Functions + +**lookup_bible_verse(book, chapter, verse)** +- Looks up a specific verse by book name, chapter number, and verse number +- Returns BibleVerse object or None if not found + +**resolve_bible_passage_reference(reference)** +- Resolves string reference to BiblePassage object +- Calls BiblePassage.from_reference() for parsing +- Returns BiblePassage or None + +**resolve_bible_verse_reference(reference)** +- Parses reference string format: "Book Chapter:Verse" +- Uses regex pattern matching +- Returns BibleVerse or None + +**get_bible_chapter_data(book, chapter)** +- Gets chapter metadata for book and chapter number +- Returns dict with 'num_verses' key + +**get_all_chapters()** +- Gets all Bible chapters as formatted strings +- Format: "BookName ChapterNumber" +- Returns list based on BIBLE_BOOKS_DATA constant + +### Reference Functions + +**get_scripture_references_list(bible_passages)** +- Converts BiblePassage objects to string list +- Returns list of formatted scripture references + +**get_scripture_references_str(bible_passages)** +- Joins scripture references with semicolon separator +- Returns formatted string: "Psalm 119:9; John 3:16" + +**get_scripture_references_compact(bible_passages)** +- Converts passages to nested structure (implementation in progress) +- Organizes by book, then chapter, then verses +- Returns list of dicts with book/chapter/verses structure + +**get_scripture_references_str_compact(bible_passages)** +- Joins compact scripture references (implementation in progress) +- Returns semicolon-joined string + +### Seeding Functions + +**seed_bible()** +- Seeds complete Bible data +- Calls seed_bible_books() then seed_bible_chapters() + +**seed_bible_books()** +- Creates BibleBook objects for all 66 books +- Automatically assigns Old Testament (OT) vs New Testament (NT) +- Uses BIBLE_BOOKS constant for all book names + +**seed_bible_chapters()** +- Creates BibleChapter objects for all chapters +- Creates appropriate number of chapters per book +- Uses BIBLE_BOOKS_DATA constant for chapter counts + +### Translation Functions + +**get_translation_model(translation)** +- Looks up translation model from HTK_BIBLE_TRANSLATIONS_MAP setting +- Translation name is uppercased for lookup +- Returns model class or None if not found + +### Choice Functions + +**get_bible_book_choices()** +- Gets enum choices from BibleTestament enum +- Returns list of (value, name) tuples for form choices + +## Example Usage + +```python +from htk.apps.bible.utils import ( + lookup_bible_verse, + get_scripture_references_str, + seed_bible, +) + +# Lookup a verse +verse = lookup_bible_verse('John', 3, 16) + +# Format multiple passages +passages = [...] # BiblePassage objects +ref_str = get_scripture_references_str(passages) + +# Seed Bible data +seed_bible() +``` diff --git a/apps/blob_storage/README.md b/apps/blob_storage/README.md new file mode 100644 index 00000000..4a6b4d3a --- /dev/null +++ b/apps/blob_storage/README.md @@ -0,0 +1,286 @@ +# Blob Storage App + +Large binary data storage and retrieval (PDFs, images, archives, etc.). + +## Quick Start + +```python +from htk.apps.blob_storage.models import Blob + +# Store binary data +blob = Blob.objects.create( + name='document.pdf', + data=pdf_bytes, + content_type='application/pdf', + user=request.user +) + +# Retrieve data +blob = Blob.objects.get(id=blob_id) +binary_data = blob.data + +# Delete blob +blob.delete() +``` + +## Storing Blobs + +### Store File Content + +```python +from htk.apps.blob_storage.models import Blob + +# Store from file upload +uploaded_file = request.FILES['file'] +blob = Blob.objects.create( + name=uploaded_file.name, + data=uploaded_file.read(), + content_type=uploaded_file.content_type, + user=request.user +) +``` + +### Store with Metadata + +```python +from htk.apps.blob_storage.models import Blob + +# Store with additional info +blob = Blob.objects.create( + name='report.xlsx', + data=excel_bytes, + content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + user=request.user, + is_private=True, # Access control + metadata={ + 'version': '1.0', + 'created_by': 'system', + 'source': 'export' + } +) +``` + +### Store with Size Limit + +```python +from htk.apps.blob_storage.models import Blob + +MAX_BLOB_SIZE = 100 * 1024 * 1024 # 100MB + +def store_blob_safe(file_obj, user, name): + if file_obj.size > MAX_BLOB_SIZE: + raise ValueError(f'File too large: {file_obj.size}') + + blob = Blob.objects.create( + name=name, + data=file_obj.read(), + content_type=file_obj.content_type, + user=user, + size=file_obj.size + ) + return blob +``` + +## Retrieving Blobs + +### Get Blob by ID + +```python +from htk.apps.blob_storage.models import Blob +from django.http import FileResponse + +def download_blob(request, blob_id): + blob = Blob.objects.get(id=blob_id) + + # Check access + if blob.is_private and blob.user != request.user: + raise PermissionDenied + + # Return file download + response = FileResponse(blob.data) + response['Content-Type'] = blob.content_type + response['Content-Disposition'] = f'attachment; filename="{blob.name}"' + return response +``` + +### List User's Blobs + +```python +from htk.apps.blob_storage.models import Blob + +# Get blobs for user +user_blobs = Blob.objects.filter(user=request.user) + +# Filter by type +documents = user_blobs.filter(content_type='application/pdf') + +# Get recent blobs +recent = user_blobs.order_by('-created')[:10] +``` + +## Common Patterns + +### Virus Scanning + +```python +from htk.apps.blob_storage.models import Blob +import subprocess + +def scan_blob_for_virus(blob): + """Scan uploaded file with ClamAV""" + # Write to temp file + import tempfile + with tempfile.NamedTemporaryFile() as tmp: + tmp.write(blob.data) + tmp.flush() + + # Run scan + result = subprocess.run( + ['clamscan', tmp.name], + capture_output=True + ) + + return result.returncode == 0 # 0 = clean +``` + +### Compression + +```python +import zlib +from htk.apps.blob_storage.models import Blob + +def store_blob_compressed(data, name, user): + """Store blob with compression""" + compressed = zlib.compress(data) + + blob = Blob.objects.create( + name=name, + data=compressed, + content_type='application/x-gzip', + user=user, + is_compressed=True, + original_size=len(data), + compressed_size=len(compressed) + ) + return blob + +def retrieve_blob_decompressed(blob): + """Retrieve and decompress""" + if blob.is_compressed: + return zlib.decompress(blob.data) + return blob.data +``` + +### S3 Storage Integration + +```python +from htk.apps.blob_storage.models import Blob +from htk.lib.aws.s3.utils import S3Manager + +def store_blob_to_s3(file_data, name, user): + """Store blob to S3 instead of database""" + s3 = S3Manager() + + # Upload to S3 + s3_path = f'blobs/{user.id}/{name}' + s3.put_file('data-bucket', s3_path, file_data) + + # Create record pointing to S3 + blob = Blob.objects.create( + name=name, + s3_path=s3_path, # Store reference instead of data + content_type=content_type, + user=user + ) + return blob +``` + +### Expiring Blobs + +```python +from django.utils import timezone +from datetime import timedelta +from htk.apps.blob_storage.models import Blob + +def create_temporary_blob(data, name, user, ttl_hours=24): + """Create blob that expires after TTL""" + expiry = timezone.now() + timedelta(hours=ttl_hours) + + blob = Blob.objects.create( + name=name, + data=data, + user=user, + expires_at=expiry + ) + return blob + +def cleanup_expired_blobs(): + """Delete expired blobs""" + Blob.objects.filter( + expires_at__lt=timezone.now() + ).delete() +``` + +### Access Control + +```python +from htk.apps.blob_storage.models import Blob + +def get_blob_safe(user, blob_id): + """Get blob only if user has access""" + blob = Blob.objects.get(id=blob_id) + + # Check ownership + if blob.user != user: + raise PermissionDenied('Cannot access this blob') + + # Check if private + if blob.is_private and not blob.user.is_staff: + raise PermissionDenied('Blob is private') + + return blob +``` + +## Models + +### Blob + +```python +class Blob(models.Model): + user = ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE) + name = CharField(max_length=255) + data = BinaryField() + content_type = CharField(max_length=100) + is_private = BooleanField(default=False) + size = IntegerField() # bytes + expires_at = DateTimeField(null=True, blank=True) + created = DateTimeField(auto_now_add=True) + updated = DateTimeField(auto_now=True) +``` + +## Configuration + +```python +# settings.py +BLOB_STORAGE_MAX_SIZE = 100 * 1024 * 1024 # 100MB +BLOB_STORAGE_ALLOWED_TYPES = [ + 'application/pdf', + 'image/jpeg', + 'image/png', + 'application/zip', +] + +BLOB_STORAGE_ENABLE_COMPRESSION = False +BLOB_STORAGE_ENABLE_S3 = False # Use S3 instead of DB +BLOB_STORAGE_ENABLE_VIRUS_SCAN = False # ClamAV scan +``` + +## Best Practices + +1. **Validate file types** - Check MIME type and extension +2. **Scan for viruses** - Use ClamAV or similar +3. **Set size limits** - Prevent huge uploads +4. **Expire temporary files** - Clean up old blobs +5. **Use S3 for large files** - Database not ideal for large files +6. **Compress when appropriate** - Reduce storage +7. **Track access** - Log downloads diff --git a/apps/blob_storage/constants/README.md b/apps/blob_storage/constants/README.md new file mode 100644 index 00000000..60f942c7 --- /dev/null +++ b/apps/blob_storage/constants/README.md @@ -0,0 +1,30 @@ +# Blob Storage Constants + +## Overview + +This module defines configuration for the blob storage system, which handles large binary data objects. + +## Constants + +### Storage Configuration + +- **`HTK_BLOB_CONTENT_MAX_LENGTH`** - Maximum size of blob content in bytes. Default: `10485760` (10 MB) + +## Usage Examples + +### Validate Blob Size + +```python +from htk.apps.blob_storage.constants import HTK_BLOB_CONTENT_MAX_LENGTH + +file_size = os.path.getsize('large_file.bin') +if file_size > HTK_BLOB_CONTENT_MAX_LENGTH: + raise ValueError(f'File exceeds {HTK_BLOB_CONTENT_MAX_LENGTH} bytes') +``` + +### Configure Max Size + +```python +# In Django settings.py +HTK_BLOB_CONTENT_MAX_LENGTH = 50 * 1000 * 1000 # 50 MB +``` diff --git a/apps/changelog/README.md b/apps/changelog/README.md index 709809f7..271e1f29 100644 --- a/apps/changelog/README.md +++ b/apps/changelog/README.md @@ -1,71 +1,45 @@ -# Changelog +# Changelog App -This app helps to create CHANGELOG.md file containing Release Notes +Automatic changelog generation from Git history. - -## Installation - -### PyPI Packages -Install the PyPI packages in `requirements.txt` - -### Adding to Installed Apps -Add `htk.apps.changelog` to `INSTALLED_APPS` in `settings.py` - -### Setting up options -There are 3 constants can be set in `settings.py` file. - -- `HTK_CHANGELOG_FILE_PATH`: File path for CHANGELOG.md file. - Default: `CHANGELOG.md` -- `HTK_CHANGELOG_SLACK_CHANNEL`: Slack channel name to announce. - Default: `#release-notes` -- `HTK_CHANGELOG_SLACK_FULL_LOG_MESSAGE`: The message that will show at the end of the thread. - Default: `The full change log can be found at CHANGELOG.md` - -### Views -Not necessary but app allows to list release notes as a page. -Create a view function and pass the necessary content to app's view function: +## Quick Start ```python -def changelog(request): - from htk.apps.changelog.views import changelog_view - response = changelog_view(request, 'app/changelog.html', {}, render) - return response -``` +from htk.apps.changelog.utils import fetch_git_logs, fetch_origin_url -In template file predefined template fragment can be used: +# Fetch git logs +logs = fetch_git_logs() -``` -{% include 'htk/fragments/changelog/view.html' with changelog=changelog %} +# Get repository origin +origin = fetch_origin_url() ``` +## Common Patterns -## Usage -This app adds a new command to `manage.py`. It can be used with following command: - -```bash -venv/bin/python manage.py changelog -``` - -### Options +```python +from htk.apps.changelog.classes.change_log import ChangeLog -#### --slack-announce -If Slack announcement wanted `--slack-announce` can be passed to command: +# Write changelog +log = ChangeLog() +log.write_changelog('CHANGELOG.md') -```bash -venv/bin/python manage.py changelog --slack-announce +# Build GitHub issue links +from htk.apps.changelog.classes.log_entry import LogEntry +entry = LogEntry('Fix: resolve bug #123') +links = entry.build_issue_links() # ['https://github.com/owner/repo/issues/123'] ``` -#### --silent -The command prints messages upon successful execution. if `--silent` option -is present, the output will be none. +## CLI ```bash -venv/bin/python manage.py changelog --silent -# or -venv/bin/python manage.py changelog --silent --slack-announce +# Generate changelog from command line +python manage.py update_changelog ``` +## Configuration -## Standalone CLI command -If Django management command is not wanted, it is also possible to create a -standalone CLI command tool. An example can be found in `commands.py` file. +```python +# settings.py +CHANGELOG_FILE = 'CHANGELOG.md' +CHANGELOG_INCLUDE_TAGS = True +``` diff --git a/apps/changelog/classes/README.md b/apps/changelog/classes/README.md new file mode 100644 index 00000000..9e0bbb00 --- /dev/null +++ b/apps/changelog/classes/README.md @@ -0,0 +1,148 @@ +# Classes + +Classes for parsing, storing, and formatting git changelog entries and release information. + +## Imports + +```python +from htk.apps.changelog.classes import ( + ChangeLog, + LogEntry, + ReleaseVersion, +) +``` + +## ChangeLog + +Container for a git repository changelog with a list of log entries. + +```python +changelog = ChangeLog( + origin_url='https://github.com/owner/repo', + log_entries=[log_entry1, log_entry2, ...] +) +``` + +### Fields + +- `origin_url` - GitHub repository URL (e.g., `https://github.com/owner/repo`) +- `log_entries` - List of `LogEntry` objects + +### Methods + +#### write_changelog(changelog_file_name, slack_announce=False, slack_channel=None, slack_webhook_url=None, web_url=None) + +Writes changelog to a markdown file and optionally announces to Slack. + +```python +changelog.write_changelog( + 'CHANGELOG.md', + slack_announce=True, + slack_channel='#releases', + slack_webhook_url='https://hooks.slack.com/...', + web_url='https://example.com/changelog' +) +``` + +Parameters: +- `changelog_file_name` - Path to output markdown file +- `slack_announce` - Boolean to announce to Slack (default: False) +- `slack_channel` - Slack channel name (required if slack_announce=True) +- `slack_webhook_url` - Slack webhook URL for posting +- `web_url` - URL to changelog on the web (included in Slack message) + +## LogEntry + +Represents a single git commit log entry parsed from formatted git output. + +```python +log_entry = LogEntry( + origin_url='https://github.com/owner/repo', + commit_hash='abc1234567...', + date_iso='2024-01-15T10:30:00', + author='john.doe', + refs_raw='tag: v1.2.3, branch: main', + subject='feat: add new feature (#123)' +) +``` + +### Fields + +- `origin_url` - GitHub repository URL +- `commit_hash` - Full commit SHA-1 hash +- `date_iso` - ISO format date string +- `author` - Commit author username +- `refs_raw` - Raw git refs (tags, branches) +- `subject` - Commit message subject + +### Factory Methods + +#### from_line(origin_url, line) + +Parse a LogEntry from a formatted git log line (fields separated by `SEPARATOR`). + +```python +log_entry = LogEntry.from_line( + 'https://github.com/owner/repo', + 'abc1234567|john.doe|2024-01-15T10:30:00|tag: v1.2.3|feat: add feature (#123)' +) +``` + +### Properties + +- `simple_subject` - Subject with GitHub issue references removed +- `issue_links` - Markdown-formatted GitHub issue links (e.g., `[#123](url)`) +- `issue_links_slack` - Slack-formatted issue links (e.g., ``) +- `author_github_url` - GitHub profile URL for the author +- `refs` - List of parsed git refs +- `release_version` - `ReleaseVersion` object if this is a release commit, None otherwise +- `is_release` - Boolean indicating if this commit is a release +- `url` - GitHub commit URL +- `short_date` - Formatted date as YYYY.MM.DD +- `html` - Markdown-formatted log entry (used in changelog files) +- `slack_message` - Slack-formatted log entry + +### Methods + +#### build_issue_links(fmt='markdown') + +Build formatted issue links from GitHub issue references in the subject. + +```python +# Markdown format +md_links = log_entry.build_issue_links(fmt='markdown') # "[#123](url), [#124](url)" + +# Slack format +slack_links = log_entry.build_issue_links(fmt='slack') # ", " +``` + +Parameters: +- `fmt` - Format type: `'markdown'` or `'slack'` + +## ReleaseVersion + +Represents a release version parsed from a git tag reference. + +```python +release = ReleaseVersion( + origin_url='https://github.com/owner/repo', + ref='tag: v1.2.3', + date='20240115103000', + sha='abc1234567', + branch='main' +) +``` + +### Fields + +- `origin_url` - GitHub repository URL +- `ref` - Git ref string (e.g., `tag: v1.2.3`) +- `date` - Release date in YYYYMMDDHHMMSS format +- `sha` - Commit SHA-1 hash +- `branch` - Git branch name + +### Properties + +- `readable_date` - Human-readable date format (e.g., `Mon Jan 15 10:30:00 2024`) +- `tag` - Extracted tag name (e.g., `v1.2.3`) +- `url` - GitHub release URL diff --git a/apps/changelog/constants/README.md b/apps/changelog/constants/README.md new file mode 100644 index 00000000..3c5b416e --- /dev/null +++ b/apps/changelog/constants/README.md @@ -0,0 +1,72 @@ +# Changelog Constants + +## Overview + +This module defines configuration for parsing and processing changelog files, including regex patterns for release tags, GitHub issues, and repository URLs. + +## Constants + +### Configuration Settings + +- **`HTK_CHANGELOG_FILE_PATH`** - Default: `'CHANGELOG.md'` - Path to changelog file +- **`HTK_CHANGELOG_SLACK_CHANNEL_RELEASES`** - Default: `'#release-notes'` - Slack channel for release notifications +- **`HTK_CHANGELOG_VIEW_IN_WEB_URL_NAME`** - Default: `None` - URL name for changelog view (optional) +- **`HTK_CHANGELOG_VIEW_IN_WEB_URL`** - Default: `None` - Direct URL to changelog (optional) +- **`HTK_COMPANY_EMPLOYEE_GITHUB_USERNAMES_MAP`** - Default: `{}` - Map GitHub usernames to employee names + +### Regex Patterns + +- **`RELEASE_TAG_REGEXES`** - List of compiled regexes for parsing release tags: + - New format: `tag: deploy-202211031730-4acb099a93-master` + - Old format: `tag: deploy-20220710.195443` +- **`GITHUB_ISSUE_REGEX`** - Pattern matching GitHub issue references: `(#123)` +- **`ORIGIN_URL_REGEX`** - Pattern matching Git origin URLs: `git@github.com:org/repo.git` + +## Usage Examples + +### Parse Release Tags + +```python +import re +from htk.apps.changelog.constants import RELEASE_TAG_REGEXES + +tag = 'tag: deploy-202211031730-4acb099a93-master' +for pattern in RELEASE_TAG_REGEXES: + match = pattern.match(tag) + if match: + print(f"Date: {match.group('dt')}, SHA: {match.group('sha')}") + break +``` + +### Extract GitHub Issues + +```python +from htk.apps.changelog.constants import GITHUB_ISSUE_REGEX + +text = 'Fixed bug (#123) and improved feature (#456)' +for match in GITHUB_ISSUE_REGEX.finditer(text): + print(f"Issue: #{match.group('issue_num')}") +``` + +### Parse Repository URL + +```python +from htk.apps.changelog.constants import ORIGIN_URL_REGEX + +url = 'git@github.com:hacktoolkit/django.git' +match = ORIGIN_URL_REGEX.match(url) +if match: + print(f"Org: {match.group('org')}, Repo: {match.group('repository')}") +``` + +### Configure Settings + +```python +# In Django settings.py +HTK_CHANGELOG_FILE_PATH = 'docs/CHANGELOG.md' +HTK_CHANGELOG_SLACK_CHANNEL_RELEASES = '#deployments' +HTK_COMPANY_EMPLOYEE_GITHUB_USERNAMES_MAP = { + 'john_doe': 'John Doe', + 'jane_smith': 'Jane Smith', +} +``` diff --git a/apps/changelog/management/README.md b/apps/changelog/management/README.md new file mode 100644 index 00000000..ef591542 --- /dev/null +++ b/apps/changelog/management/README.md @@ -0,0 +1,64 @@ +# Management + +Django management commands for changelog operations. + +## Available Commands + +### changelog + +Updates or generates a `CHANGELOG.md` file from git commit history. + +```bash +python manage.py changelog +``` + +#### Options + +- `--slack-announce` - Announce release notes to Slack channel (default: False) +- `--silent` - Suppress command output (default: False) +- `--help` - Show this help message and exit + +#### Usage Examples + +Generate changelog and write to file: +```bash +python manage.py changelog +``` + +Generate and announce to Slack: +```bash +python manage.py changelog --slack-announce +``` + +Run silently without output: +```bash +python manage.py changelog --silent +``` + +#### Configuration + +The `changelog` command requires these Django settings: + +- `HTK_CHANGELOG_FILE_PATH` - Path to output `CHANGELOG.md` file (required) +- `HTK_CHANGELOG_SLACK_CHANNEL_RELEASES` - Slack channel for announcements (e.g., `#releases`) +- `HTK_CHANGELOG_VIEW_IN_WEB_URL_NAME` - URL name for web changelog view (optional) +- `HTK_CHANGELOG_VIEW_IN_WEB_URL` - Direct URL to web changelog (optional, overridden by URL name) + +#### Process + +1. Fetches git commit logs from the repository +2. Parses commits into `LogEntry` objects +3. Identifies release commits based on git tags +4. Generates markdown-formatted changelog +5. Writes to file specified by `HTK_CHANGELOG_FILE_PATH` +6. If `--slack-announce` is set: + - Sends formatted message to Slack channel + - Includes link to full changelog if `HTK_CHANGELOG_VIEW_IN_WEB_URL` is configured + +#### Example Output + +The generated `CHANGELOG.md` includes: +- Release headers with dates and GitHub release links +- Commit entries with author, date, commit hash +- GitHub issue references linked to pull requests +- Formatted for both markdown and Slack presentation diff --git a/apps/changelog/management/commands/README.md b/apps/changelog/management/commands/README.md new file mode 100644 index 00000000..99b67107 --- /dev/null +++ b/apps/changelog/management/commands/README.md @@ -0,0 +1,49 @@ +# Commands + +Management commands for the changelog app. + +## changelog + +Updates or generates a `CHANGELOG.md` file from git commit history. + +### Usage + +```bash +python manage.py changelog [OPTIONS] +``` + +### Options + +- `--slack-announce` - Announce release notes to Slack channel +- `--silent` - Suppress command output +- `--help` - Show help message + +### Examples + +```bash +# Generate changelog file +python manage.py changelog + +# Generate and announce to Slack +python manage.py changelog --slack-announce + +# Run silently +python manage.py changelog --silent +``` + +### Configuration Required + +- `HTK_CHANGELOG_FILE_PATH` - Output file path for changelog +- `HTK_CHANGELOG_SLACK_CHANNEL_RELEASES` - Slack channel for announcements +- `HTK_CHANGELOG_VIEW_IN_WEB_URL_NAME` - Django URL name for web view (optional) +- `HTK_CHANGELOG_VIEW_IN_WEB_URL` - Direct changelog URL (optional) + +### How It Works + +1. Fetches git logs from repository +2. Parses commits using git log format +3. Creates `LogEntry` objects from each commit +4. Groups by releases (identifies tags) +5. Generates markdown-formatted changelog +6. Writes to `HTK_CHANGELOG_FILE_PATH` +7. Optionally announces to Slack with release notes diff --git a/apps/conversations/README.md b/apps/conversations/README.md new file mode 100644 index 00000000..2f866d65 --- /dev/null +++ b/apps/conversations/README.md @@ -0,0 +1,152 @@ +# Conversations App + +User-to-user messaging and conversation threads with emoji reactions. + +## Overview + +The `conversations` app provides: + +- Create conversations between users +- Message threads with multiple participants +- Emoji reactions to messages +- Conversation history and management +- Thread participants tracking + +## Quick Start + +### Create Conversations + +```python +from htk.apps.conversations.models import BaseConversation + +# Create new conversation +convo = BaseConversation.objects.create(subject='Project Discussion') + +# Add participants +convo.add_participant(user1) +convo.add_participants([user2, user3]) + +# Send message +message = convo.add_message(sender=user1, text='Hello everyone!') +``` + +### Find Conversations + +```python +# Find all conversations for a user +conversations = BaseConversation.find_all_by_user(user) + +# Find conversation with specific participants +convo = BaseConversation.find_by_participants([user1, user2]) +``` + +### Message Management + +```python +# Get messages in conversation +messages = convo.messages.all() + +# Add reaction to message +message.add_reaction(user=user, emoji='👍') + +# Remove reaction +message.remove_reaction(user=user, emoji='👍') + +# Get reactions +reactions = message.reactions.all() +``` + +### Participants + +```python +# Get conversation participants +participants = convo.participants.all() + +# Remove participant +convo.remove_participant(user) + +# Check if user is participant +is_participant = convo.participants.filter(user=user).exists() +``` + +## Models + +- **`BaseConversation`** - Main conversation model (extend for custom behavior) +- **`BaseConversationParticipant`** - Tracks conversation participants +- **`BaseConversationMessage`** - Individual messages +- **`BaseConversationMessageReaction`** - Emoji reactions to messages + +## Common Patterns + +### Direct Message Between Users + +```python +from htk.apps.conversations.models import BaseConversation + +user1 = User.objects.get(username='alice') +user2 = User.objects.get(username='bob') + +# Find or create DM +convo = BaseConversation.find_by_participants([user1, user2]) +if not convo: + convo = BaseConversation.objects.create(subject=f'{user1} & {user2}') + convo.add_participants([user1, user2]) + +# Send message +convo.add_message(sender=user1, text='Hey Bob!') +``` + +### Group Chat + +```python +# Create group conversation +group = BaseConversation.objects.create(subject='Engineering Team') +group.add_participants([user1, user2, user3, user4]) + +# Broadcast message to group +group.add_message(sender=moderator, text='Important announcement') +``` + +### Mark Messages as Read + +```python +# Extend BaseConversationMessage to add read tracking +class Message(BaseConversationMessage): + read_by = models.ManyToManyField(User, related_name='read_messages') + + def mark_read(self, user): + self.read_by.add(user) +``` + +## Best Practices + +1. **Validate participants** before creating conversations +2. **Extend base models** to add custom fields (read status, archived, etc.) +3. **Cache conversation lists** for performance +4. **Handle emoji normalization** - `repair_emoji()` called automatically on save +5. **Clean up old conversations** - Archive or delete inactive ones + +## Signals + +Automatic signal handlers: +- `repair_emoji` - Normalizes emoji shortcodes when message is saved + +## Integration with Other Apps + +```python +# With Organizations +from htk.apps.organizations.models import Organization + +class OrgConversation(BaseConversation): + organization = ForeignKey(Organization) + +# With Notifications +from htk.apps.notifications.utils import notify + +def message_created(sender, instance, created, **kwargs): + if created: + # Notify participants + for participant in instance.conversation.participants.all(): + if participant.user != instance.sender: + notify(participant.user, f'New message in {instance.conversation}') +``` diff --git a/apps/conversations/constants/README.md b/apps/conversations/constants/README.md new file mode 100644 index 00000000..cdc5500d --- /dev/null +++ b/apps/conversations/constants/README.md @@ -0,0 +1,51 @@ +# Conversations Constants + +## Overview + +This module defines configuration for conversation and messaging systems, including model references and message length constraints. + +## Constants + +### Model References + +- **`HTK_CONVERSATION_MODEL`** - Default: `None` - Conversation model (app_label.ModelName) +- **`HTK_CONVERSATION_PARTICIPANT_MODEL`** - Default: `None` - Participant model +- **`HTK_CONVERSATION_MESSAGE_MODEL`** - Default: `None` - Message model +- **`HTK_CONVERSATION_MESSAGE_REACTION_MODEL`** - Default: `None` - Message reaction model + +### Message Configuration + +- **`HTK_CONVERSATION_MESSAGE_MAX_LENGTH`** - Default: `2048` - Maximum message length in characters + +## Usage Examples + +### Configure Models + +```python +# In Django settings.py +HTK_CONVERSATION_MODEL = 'myapp.Conversation' +HTK_CONVERSATION_PARTICIPANT_MODEL = 'myapp.Participant' +HTK_CONVERSATION_MESSAGE_MODEL = 'myapp.Message' +HTK_CONVERSATION_MESSAGE_REACTION_MODEL = 'myapp.MessageReaction' +``` + +### Validate Message Length + +```python +from htk.apps.conversations.constants import HTK_CONVERSATION_MESSAGE_MAX_LENGTH + +message = "Hello, world!" +if len(message) > HTK_CONVERSATION_MESSAGE_MAX_LENGTH: + raise ValueError(f'Message exceeds {HTK_CONVERSATION_MESSAGE_MAX_LENGTH} characters') +``` + +### Use Model References + +```python +from django.apps import apps + +from htk.apps.conversations.constants import HTK_CONVERSATION_MODEL + +Conversation = apps.get_model(HTK_CONVERSATION_MODEL) +conversations = Conversation.objects.all() +``` diff --git a/apps/cpq/README.md b/apps/cpq/README.md index 1783b298..b7b33a35 100644 --- a/apps/cpq/README.md +++ b/apps/cpq/README.md @@ -1 +1,167 @@ -# CPQ - Configure, Price, Quote +# CPQ App (Configure, Price, Quote) + +Quoting and invoicing system for B2B commerce. + +## Overview + +The `cpq` app provides: + +- Create quotes for customers +- Line items with pricing and customization +- Convert quotes to invoices +- Payment processing with Stripe +- Quote history and tracking +- Group quotes for bulk orders + +## Quick Start + +### Create Quotes + +```python +from htk.apps.cpq.models import BaseCPQQuote, BaseCPQQuoteLineItem + +# Create quote +quote = BaseCPQQuote.objects.create( + customer_name='Acme Corp', + customer_email='procurement@acme.com', + expires_at=timezone.now() + timedelta(days=30) +) + +# Add line items +line_item = BaseCPQQuoteLineItem.objects.create( + quote=quote, + description='Enterprise License', + quantity=1, + unit_price=1000.00 +) + +quote.refresh_from_db() # Updates total +``` + +### Approve & Payment + +```python +# Approve and process payment +quote.approve_and_pay( + line_item_ids=[line_item.id], + amount=1000.00, + stripe_token='tok_xxx' +) + +# Creates invoice automatically +``` + +### Group Quotes + +```python +from htk.apps.cpq.models import BaseCPQGroupQuote + +# Create group quote (for parent-child relationships) +group_quote = BaseCPQGroupQuote.objects.create( + name='Q4 Regional Sales' +) + +# Add sub-quotes +quote1.group_quote = group_quote +quote1.save() + +# Sync amounts +group_quote.sync_group_sub_quotes() +``` + +## Models + +- **`BaseCPQQuote`** - Main quote model +- **`BaseCPQGroupQuote`** - Group multiple quotes +- **`BaseCPQInvoice`** - Invoice from approved quote +- **`BaseCPQQuoteLineItem`** - Line items in quote +- **`BaseCPQInvoiceLineItem`** - Line items in invoice + +## Workflow + +``` +Create Quote + ↓ +Add Line Items + ↓ +Send to Customer + ↓ +Customer Approves + ↓ +Process Payment (Stripe) + ↓ +Create Invoice + ↓ +Customer Pays +``` + +## Common Patterns + +### Sending Quotes + +```python +from htk.apps.cpq.emailers import send_quote_email + +# Send quote to customer +send_quote_email( + quote=quote, + recipient_email='buyer@acme.com' +) +``` + +### Payment Recording + +```python +# Record Stripe payment +quote.record_payment( + charge_id='ch_xxx', + amount=1000.00, + line_items=[line_item] +) + +# Creates invoice +invoice = quote.create_invoice_for_payment( + stripe_customer=customer, + line_items=[line_item] +) +``` + +### Quote Encoding + +```python +from htk.apps.cpq.utils.crypto import compute_cpq_code, resolve_cpq_code + +# Encode quote/invoice for URL +code = compute_cpq_code(quote) + +# Decode from URL +obj = resolve_cpq_code(code) # Returns Quote or Invoice +``` + +## Dashboard & Reporting + +```python +# Built-in URL patterns +# cpq_dashboard - Overview of all quotes +# cpq_invoices_index - List of invoices +# cpq_quotes_index - List of quotes +# cpq_receivables - Payment tracking by year +``` + +## Configuration + +```python +# settings.py +CPQ_QUOTE_EXPIRY_DAYS = 30 +CPQ_REQUIRE_APPROVAL = True +CPQ_STRIPE_CONNECTED = True +``` + +## Best Practices + +1. **Set expiration dates** on all quotes +2. **Use line item descriptions** clearly +3. **Record all payments** for audit trail +4. **Send email confirmations** when quote created +5. **Track quote status** for follow-ups +6. **Use group quotes** for related deals diff --git a/apps/cpq/constants/README.md b/apps/cpq/constants/README.md new file mode 100644 index 00000000..df913a7d --- /dev/null +++ b/apps/cpq/constants/README.md @@ -0,0 +1,109 @@ +# CPQ (Configure, Price, Quote) Constants + +## Overview + +This module defines configuration for the CPQ system, including payment settings, encryption keys, and template paths for quotes and invoices. + +## Constants + +### Payment Configuration + +- **`HTK_CPQ_PAY_ONLINE`** - Default: `False` - Enable online payment processing +- **`HTK_CPQ_XOR_KEY`** - Default: `1234567890123` - XOR encryption key for quote IDs +- **`HTK_CPQ_CRYPT_SECRET`** - Default: `'Zu7ooqu8'` - Encryption secret for sensitive data +- **`HTK_CPQ_CHECK_HASH_LENGTH`** - Default: `5` - Length of hash for validation checks + +### Template Configuration + +- **`HTK_CPQ_TEMPLATE_NAME_DASHBOARD`** - Default: `'htk/fragments/cpq/invoice.html'` +- **`HTK_CPQ_TEMPLATE_NAME_INVOICE`** - Default: `'htk/fragments/cpq/invoice.html'` +- **`HTK_CPQ_TEMPLATE_NAME_QUOTE`** - Default: `'htk/fragments/cpq/quote.html'` +- **`HTK_CPQ_TEMPLATE_NAME_GROUP_QUOTE`** - Default: `'htk/fragments/cpq/group_quote.html'` +- **`HTK_CPQ_TEMPLATE_NAME_GROUP_QUOTE_ALL`** - Default: `'htk/fragments/cpq/group_quote_all.html'` +- **`HTK_CPQ_TEMPLATE_NAME_RECEIVABLES`** - Default: `'htk/fragments/cpq/receivables.html'` +- **`HTK_CPQ_TEMPLATE_NAME_IMPORT_CUSTOMERS`** - Default: `'htk/fragments/cpq/import_customers.html'` + +### URL Configuration + +- **`CPQ_APP_MODEL_NAMES`** - List of models in CPQ app for admin urls +- **`CPQ_REPORTING_URL_NAMES`** - List of reporting view URL names +- **`CPQ_TOOLS_URL_NAMES`** - List of tools/utility view URL names + +## Enums + +### CPQType + +Document types in the CPQ system: + +```python +from htk.apps.cpq.enums import CPQType + +# Available CPQ types with values +CPQType.INVOICE # value: 1 +CPQType.QUOTE # value: 2 +CPQType.GROUP_QUOTE # value: 3 + +# Access enum properties +doc_type = CPQType.INVOICE +print(f"{doc_type.name}: {doc_type.value}") # INVOICE: 1 +``` + +### InvoiceType + +Invoice categorization: + +```python +from htk.apps.cpq.enums import InvoiceType + +InvoiceType.INVOICE # value: 1 +InvoiceType.REIMBURSEMENT # value: 10 +``` + +### InvoicePaymentTerm + +Payment terms for invoices: + +```python +from htk.apps.cpq.enums import InvoicePaymentTerm + +InvoicePaymentTerm.PAYMENT_DUE_UPON_RECEIPT # value: 1 +InvoicePaymentTerm.PAYABLE_NET_14 # value: 14 +InvoicePaymentTerm.PAYABLE_NET_30 # value: 30 +``` + +## Usage Examples + +### Configure Payment Settings + +```python +# In Django settings.py +HTK_CPQ_PAY_ONLINE = True +HTK_CPQ_XOR_KEY = 9876543210987 # Use strong random value +HTK_CPQ_CRYPT_SECRET = 'your-secret-key-here' +``` + +### Create Quote with CPQType + +```python +from htk.apps.cpq.enums import CPQType + +quote_type = CPQType.QUOTE.value +# Store in database as integer value +``` + +### Set Payment Terms + +```python +from htk.apps.cpq.enums import InvoicePaymentTerm + +invoice.payment_term = InvoicePaymentTerm.PAYABLE_NET_30.value +invoice.save() +``` + +### Use Custom Templates + +```python +# In Django settings.py +HTK_CPQ_TEMPLATE_NAME_INVOICE = 'myapp/cpq/invoice_custom.html' +HTK_CPQ_TEMPLATE_NAME_QUOTE = 'myapp/cpq/quote_custom.html' +``` diff --git a/apps/cpq/utils/README.md b/apps/cpq/utils/README.md new file mode 100644 index 00000000..d44b2223 --- /dev/null +++ b/apps/cpq/utils/README.md @@ -0,0 +1,81 @@ +# HTK CPQ Utils + +Utilities for Quote and Invoice management including URL generation, crypto encoding, and accounting. + +## Functions by Category + +### Admin/Reporting Functions + +**get_admin_urls()** +- Generates Django admin URLs for all CPQ models +- Returns list of dicts with 'url', 'add_url', and 'name' keys +- Uses CPQ_APP_MODEL_NAMES constant for model list + +**get_reporting_urls()** +- Generates reporting view URLs for CPQ +- Returns list of dicts with 'url' and 'name' keys +- Uses CPQ_REPORTING_URL_NAMES constant + +**get_tools_urls()** +- Generates tools view URLs for CPQ +- Returns list of dicts with 'url' and 'name' keys +- Uses CPQ_TOOLS_URL_NAMES constant + +**get_invoice_payment_terms_choices()** +- Gets payment term choices from InvoicePaymentTerm enum +- Returns list of (value, symbolic_name) tuples for form dropdowns + +### Crypto Functions + +**compute_cpq_code(cpq)** +- Encodes CPQ object ID into checksum-protected code +- Process: XOR with key > add Luhn check digit > base36 encode > prepend MD5 hash +- Returns obfuscated string code for Quote/Invoice display + +**compute_cpq_code_check_hash(cpq_code)** +- Generates MD5 hash prefix for CPQ code validation +- Hash length from HTK_CPQ_CHECK_HASH_LENGTH setting +- Used to detect code tampering + +**is_valid_cpq_code_check_hash(cpq_code, check_hash)** +- Validates check hash matches code +- Verifies code hasn't been altered +- Returns boolean + +**resolve_cpq_code(cpq_code, cpq_type=CPQType.INVOICE)** +- Decodes CPQ code back to object +- Validates check hash and Luhn digit +- Supports INVOICE, QUOTE, and GROUP_QUOTE types +- Returns CPQ object or None if invalid + +### Accounting Functions + +**get_invoice_years()** +- Gets list of all years with invoices +- Returns list of year integers, ordered chronologically + +**get_receivables_by_year(year)** +- Gets paid invoices for specified year +- Filters by invoice_type=INVOICE and paid=True +- Returns QuerySet ordered by date + +## Example Usage + +```python +from htk.apps.cpq.utils import ( + compute_cpq_code, + resolve_cpq_code, + get_invoice_years, +) + +# Encode an invoice +invoice = Invoice.objects.get(id=123) +code = compute_cpq_code(invoice) # Returns: "abc123def456" + +# Decode a code +decoded = resolve_cpq_code(code) # Returns: Invoice object or None + +# Get accounting data +years = get_invoice_years() +paid_2024 = get_receivables_by_year(2024) +``` diff --git a/apps/customers/README.md b/apps/customers/README.md new file mode 100644 index 00000000..78408e71 --- /dev/null +++ b/apps/customers/README.md @@ -0,0 +1,180 @@ +# Customers App + +Customer management for commerce and B2B applications. + +## Quick Start + +```python +from htk.apps.customers.models import BaseCustomer, BaseOrganizationCustomer + +# Create customer +customer = BaseCustomer.objects.create( + user=user, + name='Acme Corp', + email='procurement@acme.com' +) + +# Link customer to organization +org_customer = BaseOrganizationCustomer.objects.create( + organization=org, + customer=customer, + role='procurement_manager' +) + +# Get customer's quotes +quotes = customer.cpqquote_set.all() +``` + +## Customer Models + +### BaseCustomer + +Individual customer entity (can be internal or external): + +```python +from htk.apps.customers.models import BaseCustomer + +# Create customer with user +customer = BaseCustomer.objects.create( + user=user, + name='John Doe', + email='john@example.com' +) + +# Or without user (for external customers) +external = BaseCustomer.objects.create( + name='External Partner', + email='partner@external.com' +) + +# Query customers +my_customers = BaseCustomer.objects.filter(user=request.user) +``` + +### BaseOrganizationCustomer + +Customer linked to an organization with roles: + +```python +from htk.apps.customers.models import BaseOrganizationCustomer + +# Create org customer +org_customer = BaseOrganizationCustomer.objects.create( + organization=org, + customer=customer, + role='procurement_manager' +) + +# Get all customers for org +org_customers = org.baseorganizationcustomer_set.all() + +# Filter by role +managers = org.baseorganizationcustomer_set.filter(role='procurement_manager') +``` + +## Common Patterns + +### Customer Hierarchy + +```python +from htk.apps.customers.models import BaseCustomer, BaseOrganizationCustomer + +# Create customer linked to multiple orgs +customer = BaseCustomer.objects.create(user=user, name='Acme') + +# Add to multiple organizations +for org in organizations: + BaseOrganizationCustomer.objects.create( + organization=org, + customer=customer, + role='buyer' + ) + +# Get all orgs for customer +orgs = [oc.organization for oc in customer.baseorganizationcustomer_set.all()] +``` + +### Customer Searches and Filtering + +```python +from django.db.models import Q +from htk.apps.customers.models import BaseCustomer + +# Search by name or email +customers = BaseCustomer.objects.filter( + Q(name__icontains='acme') | Q(email__icontains='acme') +) + +# Get customers with quotes +customers_with_quotes = BaseCustomer.objects.filter( + cpqquote__isnull=False +).distinct() + +# Get organization customers +from htk.apps.customers.models import BaseOrganizationCustomer +org_customers = BaseOrganizationCustomer.objects.filter( + organization=org, + role__in=['buyer', 'approver'] +) +``` + +### Customer Activity Tracking + +```python +from django.utils import timezone +from htk.apps.customers.models import BaseOrganizationCustomer + +# Track last quote/order date +customer = BaseOrganizationCustomer.objects.get(id=customer_id) +latest_quote = customer.customer.cpqquote_set.order_by('-created').first() + +# Identify inactive customers (no activity in 90 days) +from datetime import timedelta +cutoff = timezone.now() - timedelta(days=90) +inactive = BaseCustomer.objects.filter( + cpqquote__created__lt=cutoff +).distinct() +``` + +## Integration with CPQ + +Link customers to quotes and orders: + +```python +from htk.apps.cpq.models import BaseCPQQuote + +# Create quote for customer +quote = BaseCPQQuote.objects.create( + customer=customer, + name='Q2024-001', + amount=10000.00 +) + +# Get customer's quotes +quotes = customer.cpqquote_set.filter(status='sent') +total_value = sum(q.amount for q in quotes) +``` + +## Configuration + +```python +# settings.py +CUSTOMER_MODELS = { + 'base': 'myapp.models.CustomCustomer', +} + +# Custom roles for organization customers +CUSTOMER_ROLES = [ + ('buyer', 'Buyer'), + ('approver', 'Approver'), + ('procurement_manager', 'Procurement Manager'), +] +``` + +## Best Practices + +1. **Organize by roles** - Use role field for permission-based filtering +2. **Link to organizations** - Use BaseOrganizationCustomer for multi-org support +3. **Track customer hierarchy** - Maintain parent-child relationships +4. **Index by email** - Add database index on email for fast lookups +5. **Audit customer changes** - Track modifications for compliance diff --git a/apps/features/README.md b/apps/features/README.md new file mode 100644 index 00000000..36a7d9f0 --- /dev/null +++ b/apps/features/README.md @@ -0,0 +1,124 @@ +# Features App + +Feature flags for gradual rollouts, A/B testing, and feature management. + +## Overview + +The `features` app provides: + +- Enable/disable features per user, organization, or globally +- Feature flag caching for performance +- A/B testing support +- Gradual feature rollout +- Feature metrics and tracking + +## Quick Start + +### Check Feature Flags + +```python +from htk.apps.features.utils import get_feature_flag + +# Check if feature is enabled for user +if get_feature_flag('new_dashboard', user): + return render(request, 'new_dashboard.html') +else: + return render(request, 'old_dashboard.html') + +# Check global feature +if get_feature_flag('maintenance_mode'): + return redirect('/maintenance/') +``` + +### Create Feature Flags + +```python +from htk.apps.features.models import FeatureFlag + +# Global flag +flag = FeatureFlag.objects.create( + name='new_checkout', + description='New checkout flow', + is_active=True +) + +# User-specific flag +flag.enable_for_user(user) +flag.disable_for_user(other_user) + +# Percentage-based rollout +flag.set_percentage_rollout(10) # 10% of users +``` + +### A/B Testing + +```python +from htk.apps.features.utils import assign_variant + +# Assign user to variant +variant = assign_variant('new_ui_test', user) + +if variant == 'control': + template = 'ui/control.html' +else: + template = 'ui/new.html' + +return render(request, template) +``` + +## Models + +- **`FeatureFlag`** - Main feature flag model +- **`FeatureFlagUser`** - User-specific overrides +- **`FeatureFlagOrganization`** - Organization-specific settings + +## Caching + +Feature flags are automatically cached: + +```python +from htk.apps.features.cachekeys import FeatureFlagCache + +cache = FeatureFlagCache('feature_name') +cache.invalidate_cache() # Refresh when flag changes +``` + +## Best Practices + +1. **Use descriptive names** - `new_checkout_v2`, not `flag_1` +2. **Document purpose** - Add description when creating +3. **Monitor adoption** - Track which users have which features +4. **Gradual rollout** - Use percentage-based rollout before full launch +5. **Clean up old flags** - Remove completed experiments + +## Typical Flow + +1. Create flag (disabled by default) +2. Enable for internal testing +3. Enable for percentage of users +4. Monitor metrics +5. Roll out to 100% or disable +6. Remove flag code after full rollout + +## Integration Examples + +### In Templates + +```django +{% if feature_flag 'new_dashboard' %} + +{% else %} + +{% endif %} +``` + +### In Tests + +```python +def test_new_feature(self): + flag = FeatureFlag.objects.create(name='test_feature', is_active=True) + flag.enable_for_user(self.user) + + response = self.client.get('/dashboard/') + self.assertContains(response, 'New feature') +``` diff --git a/apps/feedback/README.md b/apps/feedback/README.md new file mode 100644 index 00000000..6caf8b1b --- /dev/null +++ b/apps/feedback/README.md @@ -0,0 +1,228 @@ +# Feedback App + +User feedback and review collection system. + +## Quick Start + +```python +from htk.apps.feedback.models import Feedback + +# Submit feedback +feedback = Feedback.objects.create( + user=request.user, + message='Great product!', + rating=5, + category='general' +) + +# Get user's feedback +user_feedback = Feedback.objects.filter(user=request.user) + +# Query by category +feature_requests = Feedback.objects.filter(category='feature_request') +``` + +## Submitting Feedback + +### Basic Feedback + +```python +from htk.apps.feedback.models import Feedback + +# Simple feedback +feedback = Feedback.objects.create( + user=user, + message='Love the new design!', + rating=5 +) + +# Or without user +anonymous_feedback = Feedback.objects.create( + email='user@example.com', + message='Found a bug', + rating=2, + category='bug' +) +``` + +### Categorized Feedback + +```python +from htk.apps.feedback.models import Feedback + +# Categorize feedback +categories = ['general', 'bug', 'feature_request', 'support'] + +bug_report = Feedback.objects.create( + user=user, + message='Image upload broken', + category='bug', + rating=1 +) + +feature_req = Feedback.objects.create( + user=user, + message='Please add dark mode', + category='feature_request', + rating=4 +) +``` + +## Common Patterns + +### Feedback Analytics + +```python +from django.db.models import Avg, Count +from htk.apps.feedback.models import Feedback + +# Average rating +avg_rating = Feedback.objects.aggregate( + avg=Avg('rating') +)['avg'] + +# Count by category +feedback_by_category = Feedback.objects.values('category').annotate( + count=Count('id'), + avg_rating=Avg('rating') +).order_by('-count') + +# Get top rated feedback +top_feedback = Feedback.objects.order_by('-rating')[:10] + +# Recent feedback +recent = Feedback.objects.order_by('-created')[:20] +``` + +### Feedback Dashboard + +```python +from django.db.models import Avg, Count, Q +from htk.apps.feedback.models import Feedback +from django.utils import timezone +from datetime import timedelta + +# Last 30 days stats +cutoff = timezone.now() - timedelta(days=30) +recent_feedback = Feedback.objects.filter(created__gte=cutoff) + +stats = { + 'total': recent_feedback.count(), + 'avg_rating': recent_feedback.aggregate(avg=Avg('rating'))['avg'], + 'bugs': recent_feedback.filter(category='bug').count(), + 'features': recent_feedback.filter(category='feature_request').count(), +} +``` + +### Filter by Rating + +```python +from htk.apps.feedback.models import Feedback + +# Get positive feedback +positive = Feedback.objects.filter(rating__gte=4) + +# Get negative feedback +negative = Feedback.objects.filter(rating__lte=2) + +# Get critical issues +critical = Feedback.objects.filter( + category='bug', + rating__lte=2 +) +``` + +### Integration with Notifications + +```python +from django.db.models.signals import post_save +from django.dispatch import receiver +from htk.apps.feedback.models import Feedback +from htk.apps.notifications.utils import notify + +@receiver(post_save, sender=Feedback) +def notify_team_on_feedback(sender, instance, created, **kwargs): + if created: + # Notify team on new feedback + if instance.category == 'bug': + notify( + admin_users, + f'Bug report: {instance.message}', + channel='slack' + ) + elif instance.category == 'feature_request': + notify( + product_team, + f'Feature request: {instance.message}', + channel='email' + ) +``` + +## Models + +### Feedback + +```python +class Feedback(models.Model): + user = ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE) + email = EmailField(blank=True) + message = TextField() + rating = IntegerField(default=0) # 1-5 stars + category = CharField(max_length=50, default='general') + created = DateTimeField(auto_now_add=True) + updated = DateTimeField(auto_now=True) + is_resolved = BooleanField(default=False) +``` + +## API Views + +### Submit Feedback + +``` +POST /feedback/submit/ +Content-Type: application/json + +{ + "message": "Great product!", + "rating": 5, + "category": "general" +} +``` + +### Get Feedback History + +``` +GET /feedback/my-feedback/ +``` + +Returns user's feedback history with ratings and dates. + +## Configuration + +```python +# settings.py +FEEDBACK_CATEGORIES = [ + ('general', 'General Feedback'), + ('bug', 'Bug Report'), + ('feature_request', 'Feature Request'), + ('support', 'Support Issue'), +] + +FEEDBACK_ENABLED = True +FEEDBACK_RATING_MIN = 1 +FEEDBACK_RATING_MAX = 5 + +# Notification settings +FEEDBACK_NOTIFY_ON_CRITICAL = True # Notify team on low ratings +FEEDBACK_CRITICAL_THRESHOLD = 2 # Rating threshold +``` + +## Best Practices + +1. **Categorize feedback** - Use categories for organization +2. **Track ratings** - Use 1-5 star system for quantitative data +3. **Include timestamps** - Track when feedback was submitted +4. **Allow anonymous feedback** - Don't require user account +5. **Follow up on bugs** - Mark as resolved when fixed +6. **Analyze trends** - Review feedback regularly +7. **Notify team** - Alert relevant teams of critical issues diff --git a/apps/file_storage/README.md b/apps/file_storage/README.md index d1d087fd..e7fe4fed 100644 --- a/apps/file_storage/README.md +++ b/apps/file_storage/README.md @@ -1,8 +1,323 @@ -File Storage -============ +# File Storage App -App for managing local file-system storage. +File upload, storage, and management with Django file fields. -This kinda works for small amounts of file storage, but is not really scalable unless we have a comprehensive strategy for disk space allocation and backups. +## Quick Start -It's better to just use something like htk.lib.aws.s3.models.S3MediaAsset for cloud-based storage +```python +from htk.apps.file_storage.models import StoredFile +from htk.apps.file_storage.utils import store_uploaded_file + +# Store uploaded file +file_obj = request.FILES['file'] +stored_file = store_uploaded_file(file_obj, folder='uploads', user=request.user) + +# Access file +print(stored_file.file.path) # Disk path +print(stored_file.file.url) # Web URL + +# Delete file +stored_file.delete() # Removes from disk and DB +``` + +## File Upload + +### Basic File Storage + +```python +from htk.apps.file_storage.models import StoredFile + +# Store uploaded file +uploaded_file = request.FILES['document'] +stored = StoredFile.objects.create( + file=uploaded_file, + user=request.user, + folder='documents' +) + +# Access URL for display +url = stored.file.url +``` + +### With Validation + +```python +from django.core.exceptions import ValidationError +from htk.apps.file_storage.utils import store_uploaded_file + +ALLOWED_TYPES = ['pdf', 'doc', 'docx', 'txt'] +MAX_SIZE = 10 * 1024 * 1024 # 10MB + +def upload_document(file_obj, user): + # Check size + if file_obj.size > MAX_SIZE: + raise ValidationError(f'File too large: {file_obj.size}') + + # Check type + ext = file_obj.name.split('.')[-1].lower() + if ext not in ALLOWED_TYPES: + raise ValidationError(f'File type not allowed: {ext}') + + # Store file + stored = store_uploaded_file(file_obj, folder='documents', user=user) + return stored +``` + +### Organize by Folder + +```python +from htk.apps.file_storage.models import StoredFile + +# Store in organized structure +uploaded = request.FILES['file'] + +# Store with folder +stored = StoredFile.objects.create( + file=uploaded, + user=request.user, + folder=f'{request.user.id}/documents/{timezone.now().year}' +) +``` + +## File Management + +### List Files + +```python +from htk.apps.file_storage.models import StoredFile + +# Get user's files +user_files = StoredFile.objects.filter(user=request.user) + +# Filter by folder +documents = StoredFile.objects.filter( + user=request.user, + folder__startswith='documents' +) + +# Get recent files +recent = user_files.order_by('-created')[:20] +``` + +### Download Files + +```python +from django.http import FileResponse +from htk.apps.file_storage.models import StoredFile + +def download_file(request, file_id): + stored_file = StoredFile.objects.get(id=file_id) + + # Check access + if stored_file.user != request.user: + raise PermissionDenied + + # Return file + response = FileResponse(stored_file.file.open('rb')) + response['Content-Type'] = stored_file.get_mime_type() + response['Content-Disposition'] = f'attachment; filename="{stored_file.file.name}"' + return response +``` + +### Delete Files + +```python +from htk.apps.file_storage.models import StoredFile + +# Delete single file +stored_file = StoredFile.objects.get(id=file_id) +stored_file.delete() # Removes from disk and DB + +# Delete multiple files +StoredFile.objects.filter( + user=request.user, + created__lt=timezone.now() - timedelta(days=30) +).delete() +``` + +## Common Patterns + +### File Validation + +```python +import os +from django.core.exceptions import ValidationError +from htk.apps.file_storage.models import StoredFile + +ALLOWED_EXTENSIONS = ['pdf', 'txt', 'doc', 'docx', 'xls', 'xlsx'] +MAX_FILE_SIZE = 50 * 1024 * 1024 # 50MB + +def validate_file(file_obj): + # Check size + if file_obj.size > MAX_FILE_SIZE: + raise ValidationError('File is too large') + + # Check extension + ext = os.path.splitext(file_obj.name)[1].lower().lstrip('.') + if ext not in ALLOWED_EXTENSIONS: + raise ValidationError(f'File type {ext} not allowed') + + return True +``` + +### Image Thumbnail Generation + +```python +from PIL import Image +from io import BytesIO +from htk.apps.file_storage.models import StoredFile + +def generate_thumbnail(stored_file, size=(200, 200)): + """Generate thumbnail for image file""" + if not stored_file.is_image(): + return None + + img = Image.open(stored_file.file) + img.thumbnail(size) + + thumb_io = BytesIO() + img.save(thumb_io, format='JPEG') + thumb_io.seek(0) + + # Store thumbnail + thumb = StoredFile.objects.create( + file=thumb_io, + user=stored_file.user, + folder=f'{stored_file.folder}/thumbnails', + parent=stored_file + ) + return thumb +``` + +### File Versioning + +```python +from htk.apps.file_storage.models import StoredFile + +def store_file_with_version(file_obj, name, user, folder): + """Store file with automatic versioning""" + existing = StoredFile.objects.filter( + name=name, + folder=folder, + user=user + ).order_by('-version').first() + + version = (existing.version if existing else 0) + 1 + + stored = StoredFile.objects.create( + file=file_obj, + name=f'{name}.v{version}', + user=user, + folder=folder, + version=version, + parent=existing + ) + return stored +``` + +### Storage Quota + +```python +from django.db.models import Sum +from htk.apps.file_storage.models import StoredFile + +USER_STORAGE_QUOTA = 1024 * 1024 * 1024 # 1GB + +def check_storage_quota(user): + """Check if user exceeds storage quota""" + total_size = StoredFile.objects.filter( + user=user + ).aggregate(total=Sum('file__size'))['total'] or 0 + + return total_size < USER_STORAGE_QUOTA + +def get_user_storage_usage(user): + """Get formatted storage usage""" + total_bytes = StoredFile.objects.filter( + user=user + ).aggregate(total=Sum('file__size'))['total'] or 0 + + return { + 'used_bytes': total_bytes, + 'quota_bytes': USER_STORAGE_QUOTA, + 'used_percent': (total_bytes / USER_STORAGE_QUOTA) * 100, + 'used_gb': total_bytes / (1024**3), + 'quota_gb': USER_STORAGE_QUOTA / (1024**3) + } +``` + +### Bulk Operations + +```python +from htk.apps.file_storage.models import StoredFile + +def export_user_files(user): + """Create ZIP of all user files""" + import zipfile + from io import BytesIO + + files = StoredFile.objects.filter(user=user) + + zip_buffer = BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zip_file: + for stored_file in files: + zip_file.writestr( + stored_file.file.name, + stored_file.file.read() + ) + + zip_buffer.seek(0) + return zip_buffer +``` + +## Models + +### StoredFile + +```python +class StoredFile(models.Model): + user = ForeignKey(User, on_delete=models.CASCADE) + file = FileField(upload_to='files/%Y/%m/%d/') + name = CharField(max_length=255) + folder = CharField(max_length=255, default='') + size = IntegerField() # bytes + mime_type = CharField(max_length=100) + version = IntegerField(default=1) + parent = ForeignKey('self', null=True, blank=True, on_delete=models.SET_NULL) + created = DateTimeField(auto_now_add=True) + updated = DateTimeField(auto_now=True) +``` + +## Configuration + +```python +# settings.py +FILE_STORAGE_PATH = 'files/' +FILE_STORAGE_MAX_SIZE = 50 * 1024 * 1024 # 50MB per file + +ALLOWED_FILE_TYPES = [ + 'application/pdf', + 'text/plain', + 'application/msword', + 'image/jpeg', + 'image/png', +] + +# Storage backend +STORAGES = { + 'default': { + 'BACKEND': 'django.core.files.storage.FileSystemStorage', + 'LOCATION': os.path.join(BASE_DIR, 'media') + } +} +``` + +## Best Practices + +1. **Validate files** - Check type and size before storing +2. **Organize by user** - Separate storage by user for security +3. **Set size limits** - Prevent excessive storage usage +4. **Use relative URLs** - Reference by URL, not path +5. **Cleanup old files** - Delete expired/unused files +6. **Version files** - Track changes with versions +7. **Test with S3** - Use cloud storage in production diff --git a/apps/file_storage/constants/README.md b/apps/file_storage/constants/README.md new file mode 100644 index 00000000..d2d058a8 --- /dev/null +++ b/apps/file_storage/constants/README.md @@ -0,0 +1,29 @@ +# File Storage Constants + +## Overview + +This module defines configuration for the file storage system, including security keys for protecting file access. + +## Constants + +### Security Configuration + +- **`HTK_FILE_STORAGE_SECRET`** - Default: `'CHANGE_ME_TO_A_RANDOM_STRING'` - Secret key for signing file storage tokens + +## Usage Examples + +### Configure Secret Key + +```python +# In Django settings.py +# IMPORTANT: Change this to a secure random value in production +HTK_FILE_STORAGE_SECRET = 'your-super-secret-random-key-here' +``` + +### Generate Secure Secret + +```python +# Generate a strong random secret +import secrets +HTK_FILE_STORAGE_SECRET = secrets.token_urlsafe(64) +``` diff --git a/apps/forums/README.md b/apps/forums/README.md new file mode 100644 index 00000000..c3afb8e0 --- /dev/null +++ b/apps/forums/README.md @@ -0,0 +1,57 @@ +# Forums App + +Discussion forums and message threads. + +## Quick Start + +```python +from htk.apps.forums.models import Forum, ForumThread, ForumMessage + +# Create forum +forum = Forum.objects.create( + name='General Discussion', + description='Discuss anything' +) + +# Create thread +thread = ForumThread.objects.create( + forum=forum, + title='Welcome to our forum!', + created_by=user +) + +# Add message +message = ForumMessage.objects.create( + thread=thread, + content='Hello everyone!', + created_by=user +) +``` + +## Models + +- **`Forum`** - Discussion forum +- **`ForumThread`** - Topic/thread in forum +- **`ForumMessage`** - Message in thread +- **`ForumTag`** - Tag for threads + +## Common Patterns + +```python +# Get forum stats +forum.recent_thread() # Last updated thread +forum.recent_message() # Last message + +# Tag threads +thread.tags.add('announcement', 'important') + +# Search threads +Forum.objects.filter(title__icontains='django') +``` + +## URL Patterns + +- `/forum/` - Forum index +- `/forum//` - Forum detail +- `/forum//thread/create/` - Create thread +- `/forum//thread//` - Thread detail diff --git a/apps/forums/constants/README.md b/apps/forums/constants/README.md new file mode 100644 index 00000000..4f849152 --- /dev/null +++ b/apps/forums/constants/README.md @@ -0,0 +1,49 @@ +# Forums Constants + +## Overview + +This module defines configuration for forum functionality, including model references and content display settings. + +## Constants + +### Model References + +- **`HTK_FORUM_MODEL`** - Default: `None` - Forum model (app_label.ModelName) +- **`HTK_FORUM_THREAD_MODEL`** - Default: `None` - Forum thread model +- **`HTK_FORUM_MESSAGE_MODEL`** - Default: `None` - Forum message model +- **`HTK_FORUM_TAG_MODEL`** - Default: `None` - Forum tag model + +### Content Configuration + +- **`FORUM_SNIPPET_LENGTH`** - Default: `100` - Maximum characters to display in forum snippets/previews + +## Usage Examples + +### Configure Forum Models + +```python +# In Django settings.py +HTK_FORUM_MODEL = 'forums.Forum' +HTK_FORUM_THREAD_MODEL = 'forums.Thread' +HTK_FORUM_MESSAGE_MODEL = 'forums.Message' +HTK_FORUM_TAG_MODEL = 'forums.Tag' +``` + +### Generate Thread Snippet + +```python +from htk.apps.forums.constants import FORUM_SNIPPET_LENGTH + +message = "This is a very long forum message..." +snippet = message[:FORUM_SNIPPET_LENGTH] + '...' +``` + +### Load Forum Models Dynamically + +```python +from django.apps import apps +from htk.apps.forums.constants import HTK_FORUM_MODEL, HTK_FORUM_THREAD_MODEL + +Forum = apps.get_model(HTK_FORUM_MODEL) +Thread = apps.get_model(HTK_FORUM_THREAD_MODEL) +``` diff --git a/apps/geolocations/README.md b/apps/geolocations/README.md new file mode 100644 index 00000000..59a174ab --- /dev/null +++ b/apps/geolocations/README.md @@ -0,0 +1,64 @@ +# Geolocations App + +Geolocation and proximity search. + +## Quick Start + +```python +from htk.apps.geolocations.models import AbstractGeolocation +from htk.apps.geolocations.utils import get_latlng, haversine + +# Create location +location = AbstractGeolocation.objects.create( + name='San Francisco HQ', + address='123 Market St, San Francisco, CA' +) + +# Geocode (get lat/lng) +location.geocode() # Populates latitude, longitude + +# Get latlng from address +lat, lng = get_latlng('New York, NY') + +# Find nearby locations +nearby = AbstractGeolocation.find_near_latlng( + lat=37.7749, + lng=-122.4194, + distance=10 # miles +) + +# Calculate distance +distance = location.distance_from(37.7749, -122.4194) +``` + +## Models + +- **`AbstractGeolocation`** - Location with lat/lng + +## Utilities + +```python +# Convert distance units +from htk.apps.geolocations.utils import convert_distance_to_meters, convert_meters + +meters = convert_distance_to_meters(10, 'miles') +km = convert_meters(1000, 'km') + +# Get bounding box +from htk.apps.geolocations.utils import get_bounding_box + +bbox = get_bounding_box(lat=37.7749, lng=-122.4194, radius=5) + +# WGS84 Earth radius +from htk.apps.geolocations.utils import WGS84EarthRadius + +radius = WGS84EarthRadius(lat=37.7749) +``` + +## Configuration + +```python +# settings.py +GEOLOCATIONS_CACHE_TTL = 86400 # 1 day +GEOLOCATIONS_DEFAULT_DISTANCE_UNIT = 'miles' +``` diff --git a/apps/geolocations/constants/README.md b/apps/geolocations/constants/README.md new file mode 100644 index 00000000..c1b227f7 --- /dev/null +++ b/apps/geolocations/constants/README.md @@ -0,0 +1,85 @@ +# Geolocations Constants + +## Overview + +This module provides geolocation constants for location searches, distance conversions, and WGS-84 geodetic reference parameters. + +## Constants + +### Mapbox Configuration + +- **`HTK_GEOLOCATIONS_MAPBOX_MIN_RELEVANCE_THRESHOLD`** - Default: `1` - Minimum relevance score for Mapbox results + +### Location Configuration + +- **`LOCATION_MAP_URL_FORMAT`** - Google Maps URL template: `'https://maps.google.com/?q=%s'` +- **`DEFAULT_SEARCH_RADIUS`** - Default: `10` - Default search radius (in default distance unit) +- **`DEFAULT_DISTANCE_UNIT`** - Default: `DistanceUnit.MILE` - Default unit for distances + +### Distance Conversions + +- **`FEET_PER_MILE`** - `5280.0` +- **`KM_PER_MILE`** - `1.609344` +- **`METERS_PER_KM`** - `1000.0` +- **`MILES_PER_KM`** - `0.62137119` +- **`METERS_PER_MILE`** - Calculated: `1609.344` +- **`METERS_PER_FEET`** - Calculated: `0.3048` + +### WGS-84 Reference Parameters + +- **`WGS84_a`** - Major semiaxis: `6378137.0` meters +- **`WGS84_b`** - Minor semiaxis: `6356752.3142` meters + +## Enums + +### DistanceUnit + +Units for distance measurements: + +```python +from htk.apps.geolocations.enums import DistanceUnit + +# Available units with values +DistanceUnit.MILE # value: 1 +DistanceUnit.KILOMETER # value: 2 +DistanceUnit.FEET # value: 3 +DistanceUnit.METER # value: 4 + +# Access enum properties +unit = DistanceUnit.MILE +print(f"{unit.name}: {unit.value}") # MILE: 1 +``` + +## Usage Examples + +### Convert Distances + +```python +from htk.apps.geolocations.constants import ( + KM_PER_MILE, METERS_PER_KM, FEET_PER_MILE +) + +miles = 5 +km = miles * KM_PER_MILE +meters = km * METERS_PER_KM +feet = miles * FEET_PER_MILE +``` + +### Create Location Map URL + +```python +from htk.apps.geolocations.constants import LOCATION_MAP_URL_FORMAT + +location = "1600 Pennsylvania Avenue, Washington DC" +map_url = LOCATION_MAP_URL_FORMAT % location +``` + +### Configure Search Radius + +```python +# In Django settings.py +from htk.apps.geolocations.enums import DistanceUnit + +DEFAULT_SEARCH_RADIUS = 25 # 25 miles +DEFAULT_DISTANCE_UNIT = DistanceUnit.KILOMETER +``` diff --git a/apps/i18n/README.md b/apps/i18n/README.md new file mode 100644 index 00000000..e6652691 --- /dev/null +++ b/apps/i18n/README.md @@ -0,0 +1,170 @@ +# i18n App + +Internationalization and localization for multi-language content. + +## Overview + +The `i18n` app provides: + +- Localizable strings (base content in one language) +- Localized strings (translated content) +- Multi-language support +- Language detection +- Translation management + +## Quick Start + +### Create Translatable Strings + +```python +from htk.apps.i18n.models import AbstractLocalizableString + +# Create base string +base_string = AbstractLocalizableString.objects.create( + key='greeting', + default_value='Hello' +) + +# Add translation +base_string.add_translation(language='es', value='Hola') +base_string.add_translation(language='fr', value='Bonjour') +``` + +### Look Up Translations + +```python +from htk.apps.i18n.utils.general import lookup_localization + +# Get translated string +greeting = lookup_localization('greeting', language='es') +# Returns 'Hola' or default if not found +``` + +### Retrieve All Strings + +```python +from htk.apps.i18n.utils.data import retrieve_all_strings + +# Get all translations for all languages +all_strings = retrieve_all_strings() +# { +# 'greeting': { +# 'en': 'Hello', +# 'es': 'Hola', +# 'fr': 'Bonjour' +# } +# } +``` + +## Models + +- **`AbstractLocalizableString`** - Base string in default language +- **`AbstractLocalizedString`** - Translation in specific language + +## Common Patterns + +### Language Detection + +```python +from htk.apps.i18n.utils.general import detect_user_language + +# Detect from request +language = detect_user_language(request) + +# Set user language preference +user.profile.language = language +user.profile.save() +``` + +### Bulk Translation Load + +```python +from htk.apps.i18n.utils.data import load_strings + +data = { + 'greeting': { + 'en': 'Hello', + 'es': 'Hola', + 'fr': 'Bonjour' + }, + 'goodbye': { + 'en': 'Goodbye', + 'es': 'Adiós', + 'fr': 'Au revoir' + } +} + +load_strings(data) +``` + +### Check Instrumentation + +```python +# Check if string is being used in code +if not base_string.is_instrumented(): + # String is defined but not used + # Consider removing or archiving + pass +``` + +## Supported Languages + +```python +from htk.apps.i18n.utils.data import look_up_supported_languages + +languages = look_up_supported_languages() +# ['en', 'es', 'fr', 'de', 'ja', 'zh'] +``` + +## Configuration + +```python +# settings.py +LANGUAGE_CODE = 'en' +LANGUAGES = [ + ('en', 'English'), + ('es', 'Spanish'), + ('fr', 'French'), + ('de', 'German'), + ('ja', 'Japanese'), + ('zh', 'Simplified Chinese'), +] + +# Supported for i18n app +I18N_SUPPORTED_LANGUAGES = LANGUAGES +``` + +## Best Practices + +1. **Use descriptive keys** - `app.feature.message`, not `msg_1` +2. **Keep context in values** - Provide enough info for translators +3. **Use placeholders** - For dynamic content in translations +4. **Avoid HTML in strings** - Separate content from markup +5. **Test all languages** - Verify strings render correctly +6. **Archive old strings** - Clean up unused translations +7. **Use professional translators** - Better quality than automated + +## Translation Workflow + +``` +1. Create key in default language +2. Mark translation needed +3. Send to translators +4. Add translations via admin or load_strings() +5. Deploy and test +6. Monitor for missing translations +``` + +## Integration Example + +```python +def render_with_language(request): + language = request.user.profile.language or 'en' + + context = { + 'greeting': lookup_localization('greeting', language), + 'welcome': lookup_localization('welcome_message', language), + } + + return render(request, 'page.html', context) +``` diff --git a/apps/i18n/constants/README.md b/apps/i18n/constants/README.md new file mode 100644 index 00000000..e9eafd44 --- /dev/null +++ b/apps/i18n/constants/README.md @@ -0,0 +1,55 @@ +# Internationalization (i18n) Constants + +## Overview + +This module provides configuration for localization and internationalization, including model references and language settings. + +## Constants + +### Model References + +- **`HTK_LOCALIZABLE_STRING_MODEL`** - Default: `None` - Localizable string model (app_label.ModelName) +- **`HTK_LOCALIZED_STRING_MODEL`** - Default: `None` - Localized string model (app_label.ModelName) + +### Language Configuration + +- **`HTK_LOCALIZABLE_STRING_LANGUAGE_CODES`** - Default: `['en-US']` - List of supported language codes + +### Admin Tools + +- **`HTK_ADMINTOOLS_LOCALIZATION_USAGE_CHECKS`** - Default: `[]` - List of localization usage checks + +## Usage Examples + +### Configure Language Support + +```python +# In Django settings.py +HTK_LOCALIZABLE_STRING_LANGUAGE_CODES = [ + 'en-US', + 'es-ES', + 'fr-FR', + 'de-DE', +] +``` + +### Set Custom Models + +```python +# In Django settings.py +HTK_LOCALIZABLE_STRING_MODEL = 'myapp.LocalizableString' +HTK_LOCALIZED_STRING_MODEL = 'myapp.LocalizedString' +``` + +### Load Model References + +```python +from django.apps import apps +from htk.apps.i18n.constants import ( + HTK_LOCALIZABLE_STRING_MODEL, + HTK_LOCALIZED_STRING_MODEL, +) + +LocalizableString = apps.get_model(HTK_LOCALIZABLE_STRING_MODEL) +LocalizedString = apps.get_model(HTK_LOCALIZED_STRING_MODEL) +``` diff --git a/apps/i18n/utils/README.md b/apps/i18n/utils/README.md new file mode 100644 index 00000000..e97a19ee --- /dev/null +++ b/apps/i18n/utils/README.md @@ -0,0 +1,96 @@ +# HTK i18n Utils + +Utilities for internationalization including language data management, string retrieval, and localization lookups. + +## Functions by Category + +### Data Functions + +**look_up_supported_languages()** +- Gets all language codes with translations (cached with lru_cache) +- Returns list of language codes ordered alphabetically +- Example: ['en-US', 'es-ES', 'zh-CN'] + +**retrieve_all_strings(by_language=False, language_codes=None, namespaces=None)** +- Retrieves all translated strings with flexible organization +- When by_language=False: Returns dict keyed by string key with language translations +- When by_language=True: Returns dict keyed by language code with string translations +- Optional language_codes and namespaces filtering +- Returns nested dict structure with full translation data + +**dump_strings(file_path, indent=4, by_language=False, language_codes=None)** +- Exports all strings to JSON file +- Creates directory structure if needed +- Returns count of strings dumped + +**load_strings(data, overwrite=False)** +- Imports strings from dict into LocalizableString and LocalizedString +- When overwrite=False: Only adds new translations +- When overwrite=True: Updates existing translations +- Returns tuple: (num_strings, num_translations) + +### Utility Functions + +**get_language_code_choices()** +- Gets language code choices for forms +- Returns list of (code, code) tuples from HTK_LOCALIZABLE_STRING_LANGUAGE_CODES +- Example: [('en-US', 'en-US'), ('es-ES', 'es-ES')] + +**lookup_localization(key=None, locale='en-US')** +- Looks up a localized string by key and language +- Returns localized value or fallback error string if not found +- Fallback format: '???[key]-[locale]???' + +## Data Structure Examples + +### retrieve_all_strings(by_language=False) +```json +{ + "welcome_title": { + "en-US": "Welcome", + "es-ES": "Bienvenido" + }, + "goodbye_title": { + "en-US": "Goodbye", + "es-ES": "Adiós" + } +} +``` + +### retrieve_all_strings(by_language=True) +```json +{ + "en-US": { + "welcome_title": "Welcome", + "goodbye_title": "Goodbye" + }, + "es-ES": { + "welcome_title": "Bienvenido", + "goodbye_title": "Adiós" + } +} +``` + +## Example Usage + +```python +from htk.apps.i18n.utils import ( + look_up_supported_languages, + lookup_localization, + retrieve_all_strings, + dump_strings, +) + +# Get supported languages +langs = look_up_supported_languages() + +# Lookup a string +welcome = lookup_localization('welcome_title', locale='es-ES') + +# Export strings +dump_strings('/tmp/translations.json') + +# Import strings +data = retrieve_all_strings(by_language=False) +count = load_strings(data) +``` diff --git a/apps/invitations/README.md b/apps/invitations/README.md new file mode 100644 index 00000000..f0bce16e --- /dev/null +++ b/apps/invitations/README.md @@ -0,0 +1,167 @@ +# Invitations App + +User invitation and onboarding management. + +## Overview + +The `invitations` app provides: + +- Create and send user invitations +- Track invitation lifecycle +- Email-based invitations with tokens +- Onboarding flow integration +- Invitation expiration and tracking + +## Quick Start + +### Send Invitations + +```python +from htk.apps.invitations.models import BaseInvitation + +# Create invitation +invitation = BaseInvitation.objects.create( + email='user@example.com', + invited_by=current_user, + metadata={'team': 'engineering'} +) + +# Send invitation email +invitation.send_email() +``` + +### Accept Invitations + +```python +# User clicks link in email +@login_required +def accept_invitation(request, token): + invitation = BaseInvitation.objects.get(token=token) + + # Connect user + invitation.connect_user(request.user) + + # Complete invitation flow + invitation.complete() + + return redirect('dashboard') +``` + +### Track Invitations + +```python +# Get pending invitations +pending = BaseInvitation.objects.filter(accepted=False) + +# Get invitations sent by user +my_invites = BaseInvitation.objects.filter(invited_by=user) + +# Check invitation status +if invitation.is_expired: + # Resend or delete + pass +``` + +## Models + +- **`BaseInvitation`** - Main invitation model + +Extend to add custom data: + +```python +from htk.apps.invitations.models import BaseInvitation + +class CustomInvitation(BaseInvitation): + department = CharField(max_length=100) + role = CharField(max_length=100) +``` + +## Lifecycle + +``` +Create Invitation + ↓ +Send Email with Token + ↓ +User Clicks Link + ↓ +User Logs In / Signs Up + ↓ +Accept Invitation (connect to user) + ↓ +Complete Invitation +``` + +## Common Patterns + +### Invitation with Role + +```python +# Create invitation for specific role +invitation = BaseInvitation.objects.create( + email='manager@company.com', + invited_by=admin, + metadata={'role': 'manager', 'org_id': 123} +) + +# When accepted, use metadata +user = invitation.user +role = invitation.metadata.get('role') +org_id = invitation.metadata.get('org_id') +``` + +### Batch Invitations + +```python +# Send multiple invitations +emails = ['user1@example.com', 'user2@example.com', 'user3@example.com'] + +for email in emails: + invitation = BaseInvitation.objects.create( + email=email, + invited_by=organizer + ) + invitation.send_email() +``` + +### Resend Invitations + +```python +# Resend if not accepted +if not invitation.accepted: + invitation.refresh_token() # Get new token + invitation.send_email() +``` + +## Configuration + +```python +# settings.py +INVITATIONS_EXPIRY_DAYS = 7 +INVITATIONS_EMAIL_TEMPLATE = 'emails/invitation.html' +INVITATIONS_FROM_EMAIL = 'noreply@example.com' +``` + +## Services + +The app integrates with user creation: + +```python +# Automatically called when user is created +process_user_created(user) + +# Automatically called when email is confirmed +process_user_email_confirmation(user_email) + +# Automatically called when user completes onboarding +process_user_completed(user) +``` + +## Best Practices + +1. **Set clear expiration dates** on invitations +2. **Include context in metadata** (role, team, org) +3. **Send confirmation emails** after accept +4. **Track who invited whom** for analytics +5. **Allow resending** of expired invitations +6. **Validate email** before creating invitation diff --git a/apps/invitations/constants/README.md b/apps/invitations/constants/README.md new file mode 100644 index 00000000..e03b8d46 --- /dev/null +++ b/apps/invitations/constants/README.md @@ -0,0 +1,71 @@ +# Invitations Constants + +## Overview + +This module defines configuration for invitation systems, including model references and lifecycle signal settings. + +## Constants + +### Model References + +- **`HTK_INVITATION_MODEL`** - Default: `None` - Primary invitation model (app_label.ModelName) +- **`HTK_INVITATION_MODELS`** - Default: `[]` - List of invitation models + +### Lifecycle Configuration + +- **`HTK_INVITATIONS_LIFECYCLE_SIGNALS_ENABLED`** - Default: `False` - Enable signal handlers for invitation lifecycle events + +## Enums + +### InvitationStatus + +Invitation lifecycle states: + +```python +from htk.apps.invitations.enums import InvitationStatus + +# Available invitation statuses with values +InvitationStatus.INITIAL # value: 0 (Not yet sent) +InvitationStatus.EMAIL_SENT # value: 1 (Email sent to recipient) +InvitationStatus.EMAIL_RESENT # value: 2 (Email resent to recipient) +InvitationStatus.ACCEPTED # value: 3 (Invitation accepted) +InvitationStatus.COMPLETED # value: 4 (Fully completed) + +# Access enum properties +status = InvitationStatus.EMAIL_SENT +print(f"{status.name}: {status.value}") # EMAIL_SENT: 1 + +# Check invitation status +if status == InvitationStatus.ACCEPTED: + print("Invitation was accepted") +``` + +## Usage Examples + +### Configure Invitation Models + +```python +# In Django settings.py +HTK_INVITATION_MODEL = 'invitations.Invitation' +HTK_INVITATION_MODELS = [ + 'invitations.Invitation', + 'organizations.OrganizationInvitation', +] +``` + +### Enable Lifecycle Signals + +```python +# In Django settings.py +HTK_INVITATIONS_LIFECYCLE_SIGNALS_ENABLED = True +``` + +### Load and Use Models + +```python +from django.apps import apps +from htk.apps.invitations.constants import HTK_INVITATION_MODEL + +Invitation = apps.get_model(HTK_INVITATION_MODEL) +pending = Invitation.objects.filter(accepted=False) +``` diff --git a/apps/kv_storage/README.md b/apps/kv_storage/README.md new file mode 100644 index 00000000..cace0017 --- /dev/null +++ b/apps/kv_storage/README.md @@ -0,0 +1,167 @@ +# KV Storage App + +Key-value storage for flexible, schema-less data storage. + +## Overview + +The `kv_storage` app provides: + +- Simple key-value storage backed by the database +- Flexible schema without migrations +- JSON value support +- Caching for performance +- Thread-safe operations + +## Quick Start + +### Store & Retrieve Data + +```python +from htk.apps.kv_storage.utils import kv_put, kv_get + +# Store a value +kv_put('user_preferences', {'theme': 'dark', 'notifications': True}) + +# Retrieve a value +prefs = kv_get('user_preferences') +# {'theme': 'dark', 'notifications': True} + +# Get with default +kv_get('nonexistent_key', default={}) +``` + +### Delete Values + +```python +from htk.apps.kv_storage.utils import kv_delete + +# Delete a key +kv_delete('user_preferences') + +# Check if exists before deleting +if kv_get('key'): + kv_delete('key') +``` + +### Cached Access + +```python +from htk.apps.kv_storage.utils import kv_get_cached + +# Get value from cache (fetches from DB if not cached) +value = kv_get_cached('expensive_setting') + +# Cached values auto-refresh based on TTL +``` + +## Models + +- **`AbstractKVStorage`** - Extend this for custom storage models + +Create a custom model in your app: + +```python +from htk.apps.kv_storage.models import AbstractKVStorage + +class AppConfig(AbstractKVStorage): + class Meta: + db_table = 'app_config' +``` + +## Common Patterns + +### User Settings + +```python +# Store per-user settings +user_id = user.id +kv_put(f'user_settings:{user_id}', { + 'language': 'en', + 'timezone': 'America/New_York', + 'email_digest': 'daily' +}) + +# Retrieve user settings +settings = kv_get(f'user_settings:{user_id}') +``` + +### Feature Configuration + +```python +# Store feature settings without migrations +kv_put('feature:analytics_config', { + 'sampling_rate': 0.1, + 'retention_days': 90, + 'debug': False +}) + +config = kv_get('feature:analytics_config') +``` + +### Application State + +```python +# Store transient application state +kv_put('background_job:sync_users:status', { + 'last_run': '2024-11-13T15:30:00Z', + 'processed': 1243, + 'failed': 0, + 'next_run': '2024-11-13T16:00:00Z' +}) + +status = kv_get('background_job:sync_users:status') +``` + +### Caching Complex Data + +```python +from htk.apps.kv_storage.utils import kv_put, kv_get_cached + +# Expensive operation - store result +data = expensive_computation() +kv_put('expensive_result', data) + +# Later - retrieve from cache +cached_data = kv_get_cached('expensive_result') +``` + +## Naming Conventions + +Use descriptive, hierarchical keys: + +```python +# Good +'user_settings:1234' +'feature:dark_mode:config' +'cache:homepage:data' +'state:import_job:123' + +# Avoid +'data' +'tmp' +'x' +``` + +## Best Practices + +1. **Use hierarchical keys** - `namespace:subnamespace:key` +2. **Store JSON data** - Complex values as dictionaries +3. **Cache expensive data** - Compute once, store, retrieve many times +4. **Set reasonable TTLs** - Don't cache indefinitely +5. **Document keys** - Explain what each key stores +6. **Clean up old keys** - Periodically purge unused data +7. **Use atomic operations** - Avoid race conditions + +## Performance + +```python +# Slow - multiple DB hits +for user in users: + prefs = kv_get(f'user_prefs:{user.id}') # Each hit DB + +# Better - batch get +from htk.apps.kv_storage.models import get_kv_storage_model +KVStorage = get_kv_storage_model() +all_keys = [f'user_prefs:{u.id}' for u in users] +prefs_dict = {k.key: k.value for k in KVStorage.objects.filter(key__in=all_keys)} +``` diff --git a/apps/maintenance_mode/README.md b/apps/maintenance_mode/README.md new file mode 100644 index 00000000..681f1f15 --- /dev/null +++ b/apps/maintenance_mode/README.md @@ -0,0 +1,143 @@ +# Maintenance Mode App + +Global maintenance mode for controlled downtime. + +## Overview + +The `maintenance_mode` app provides: + +- Toggle maintenance mode on/off globally +- Exception list for admin access during maintenance +- Customizable maintenance page +- Middleware integration + +## Quick Start + +### Enable Maintenance Mode + +```python +from htk.apps.maintenance_mode.utils import enable_maintenance_mode, disable_maintenance_mode + +# Enable +enable_maintenance_mode() + +# Disable +disable_maintenance_mode() + +# Check status +from htk.apps.maintenance_mode.utils import is_maintenance_mode +if is_maintenance_mode(): + # Do something + pass +``` + +### Add Exception Users + +```python +from django.contrib.auth.models import User + +# Users who can access during maintenance +admin_user = User.objects.get(username='admin') +add_maintenance_exception(admin_user) + +# Remove exception +remove_maintenance_exception(admin_user) +``` + +## Installation + +```python +# settings.py +MIDDLEWARE = [ + 'htk.apps.maintenance_mode.middleware.MaintenanceModeMiddleware', + # ... +] + +# Customize maintenance page template +MAINTENANCE_MODE_TEMPLATE = 'maintenance.html' +MAINTENANCE_MODE_STATUS_CODE = 503 # Service Unavailable +``` + +## Configuration + +```python +# settings.py +HTK_MAINTENANCE_MODE = False # Set via env or dynamically +MAINTENANCE_MODE_IGNORE_PATHS = [ + '/health/', + '/status/', +] +MAINTENANCE_MODE_IGNORE_ADMIN = True # Auto-allow superusers +``` + +## Common Patterns + +### Database Maintenance + +```python +# Before starting maintenance +enable_maintenance_mode() + +# Run migrations, backups, etc. +# ... + +# When complete +disable_maintenance_mode() +``` + +### Scheduled Maintenance + +```python +import schedule +from htk.apps.maintenance_mode.utils import enable_maintenance_mode, disable_maintenance_mode + +def maintenance_window(): + enable_maintenance_mode() + # Do maintenance work + disable_maintenance_mode() + +# Schedule for specific time +schedule.every().day.at('02:00').do(maintenance_window) +``` + +### Custom Maintenance Page + +```html + + + + + Maintenance + + +

We're undergoing maintenance

+

We'll be back online shortly. Thank you for your patience.

+

Expected completion: 02:00 AM EST

+ + +``` + +## Views & Templates + +```python +# Built-in view +# GET /maintenance/ +# Returns 503 Service Unavailable with custom template +``` + +## Best Practices + +1. **Announce in advance** - Tell users when maintenance window is scheduled +2. **Use exceptions sparingly** - Only for support staff if needed +3. **Keep page simple** - Avoid heavy assets during maintenance +4. **Monitor queue** - Don't let requests pile up +5. **Have rollback plan** - Be ready to revert changes +6. **Test before enabling** - Verify maintenance mode works + +## API Integration + +```python +# API endpoints return 503 with JSON +# GET /api/endpoint/ +# Returns: {'error': 'Service under maintenance'} +``` diff --git a/apps/maintenance_mode/constants/README.md b/apps/maintenance_mode/constants/README.md new file mode 100644 index 00000000..fe001c5c --- /dev/null +++ b/apps/maintenance_mode/constants/README.md @@ -0,0 +1,43 @@ +# Maintenance Mode Constants + +## Overview + +This module defines configuration for activating and managing maintenance mode, including the toggle setting and the associated view URL. + +## Constants + +### Maintenance Configuration + +- **`HTK_MAINTENANCE_MODE`** - Default: `False` - Enable maintenance mode (blocks regular users) +- **`HTK_MAINTENANCE_MODE_URL_NAME`** - Default: `'maintenance_mode'` - URL name for maintenance mode view + +## Usage Examples + +### Activate Maintenance Mode + +```python +# In Django settings.py +HTK_MAINTENANCE_MODE = True +HTK_MAINTENANCE_MODE_URL_NAME = 'maintenance_mode' +``` + +### Check in Middleware + +```python +from django.conf import settings +from htk.apps.maintenance_mode.constants import HTK_MAINTENANCE_MODE + +if settings.HTK_MAINTENANCE_MODE: + # Redirect user to maintenance mode page + pass +``` + +### Get Maintenance View URL + +```python +from django.urls import reverse +from django.conf import settings + +if settings.HTK_MAINTENANCE_MODE: + maintenance_url = reverse(settings.HTK_MAINTENANCE_MODE_URL_NAME) +``` diff --git a/apps/mobile/README.md b/apps/mobile/README.md new file mode 100644 index 00000000..b1b46632 --- /dev/null +++ b/apps/mobile/README.md @@ -0,0 +1,225 @@ +# Mobile App + +Mobile device detection, registration, and push notifications. + +## Quick Start + +```python +from htk.apps.mobile.models import MobileDevice +from htk.apps.mobile.utils import is_mobile_request + +# Check if request from mobile +if is_mobile_request(request): + template = 'mobile_view.html' +else: + template = 'desktop_view.html' + +# Register device +device = MobileDevice.objects.create( + user=user, + device_id='device_123_abc', + platform='ios', # 'ios' or 'android' + version='17.0', + device_name='iPhone 15' +) + +# Track active devices +active_count = user.mobiledevice_set.filter(is_active=True).count() +``` + +## Mobile Device Management + +### Register Devices + +```python +from htk.apps.mobile.models import MobileDevice + +# Create device with push token +device = MobileDevice.objects.create( + user=user, + device_id='uuid-123', + platform='android', + version='14', + push_token='firebase_token_123', + device_name='Samsung Galaxy S24' +) + +# Or update existing device +device, created = MobileDevice.objects.update_or_create( + user=user, + device_id='uuid-123', + defaults={ + 'push_token': 'new_firebase_token', + 'is_active': True + } +) +``` + +### Detect Mobile Requests + +```python +from htk.apps.mobile.utils import is_mobile_request + +def my_view(request): + if is_mobile_request(request): + # Return mobile optimized response + return JsonResponse({'mobile': True}) + else: + # Return desktop response + return JsonResponse({'mobile': False}) +``` + +### Send Push Notifications + +```python +from htk.apps.mobile.models import MobileDevice + +# Get user's devices +devices = MobileDevice.objects.filter(user=user, is_active=True) + +# Send push to all devices +for device in devices: + device.send_push_notification( + title='New Message', + message='You have a new message from John', + data={'message_id': 123, 'sender_id': 456} + ) +``` + +## Common Patterns + +### Multi-Device Support + +```python +from htk.apps.mobile.models import MobileDevice + +# Get all active devices for user +active_devices = MobileDevice.objects.filter( + user=user, + is_active=True +) + +# Send notification to all devices +message = 'Important update available' +for device in active_devices: + device.send_push_notification( + title='Update', + message=message + ) + +# Track device count +device_count = active_devices.count() +``` + +### Device Deactivation + +```python +from htk.apps.mobile.models import MobileDevice + +# Deactivate old devices +devices_to_remove = MobileDevice.objects.filter( + user=user, + last_seen__lt=timezone.now() - timedelta(days=90) +) + +for device in devices_to_remove: + device.is_active = False + device.save() +``` + +### Platform-Specific Logic + +```python +from htk.apps.mobile.models import MobileDevice + +# Send different notifications by platform +ios_devices = MobileDevice.objects.filter(user=user, platform='ios') +android_devices = MobileDevice.objects.filter(user=user, platform='android') + +# iOS specific +for device in ios_devices: + device.send_push_notification( + title='iOS Notification', + message='This is iOS specific', + sound='default', + badge=1 + ) + +# Android specific +for device in android_devices: + device.send_push_notification( + title='Android Notification', + message='This is Android specific', + priority='high', + color='#FF5722' + ) +``` + +### Track Device Usage + +```python +from django.utils import timezone +from htk.apps.mobile.models import MobileDevice + +# Update last seen +device = MobileDevice.objects.get(device_id=device_id) +device.last_seen = timezone.now() +device.save() + +# Get most recently active devices +recent = MobileDevice.objects.filter(user=user).order_by('-last_seen')[:5] + +# Identify inactive devices +inactive_cutoff = timezone.now() - timedelta(days=30) +inactive = MobileDevice.objects.filter( + user=user, + last_seen__lt=inactive_cutoff +) +``` + +## Models + +### MobileDevice + +Track user's mobile devices: + +```python +class MobileDevice(models.Model): + user = ForeignKey(User, on_delete=models.CASCADE) + device_id = CharField(max_length=200, unique=True) # UUID + platform = CharField(max_length=20) # 'ios' or 'android' + version = CharField(max_length=50) + push_token = CharField(max_length=500, blank=True) + device_name = CharField(max_length=200, blank=True) + is_active = BooleanField(default=True) + last_seen = DateTimeField(auto_now=True) + created = DateTimeField(auto_now_add=True) +``` + +## Configuration + +```python +# settings.py +MOBILE_PUSH_PROVIDER = 'firebase' # or 'apns' for iOS + +# Firebase configuration +FIREBASE_CREDENTIALS = os.environ.get('FIREBASE_CREDENTIALS') +FIREBASE_PROJECT_ID = os.environ.get('FIREBASE_PROJECT_ID') + +# Apple Push Notification Service (APNS) +APNS_CERTIFICATE_PATH = os.environ.get('APNS_CERTIFICATE_PATH') +APNS_KEY_PATH = os.environ.get('APNS_KEY_PATH') + +# Device tracking +MOBILE_DEVICE_RETENTION_DAYS = 180 # Keep inactive devices for 6 months +``` + +## Best Practices + +1. **Store push tokens securely** - Encrypt push tokens in database +2. **Handle device deactivation** - Clean up when user logs out +3. **Respect notification preferences** - Check user settings before sending +4. **Handle push errors** - Remove devices with invalid tokens +5. **Platform-specific features** - Send appropriate payloads per platform +6. **Track device identifiers** - Prevent duplicate registrations +7. **Test before production** - Use development certificates for testing diff --git a/apps/mobile/constants/README.md b/apps/mobile/constants/README.md new file mode 100644 index 00000000..a0387222 --- /dev/null +++ b/apps/mobile/constants/README.md @@ -0,0 +1,41 @@ +# Mobile Constants + +## Overview + +This module defines configuration for mobile app integration, including app store URLs. + +## Constants + +### App Store Configuration + +- **`HTK_APPSTORE_URL`** - Default: `None` - URL to mobile app on app store (iTunes/Google Play) + +## Usage Examples + +### Configure App Store URLs + +```python +# In Django settings.py +HTK_APPSTORE_URL = 'https://apps.apple.com/app/myapp/id123456789' +# For Google Play: +# HTK_APPSTORE_URL = 'https://play.google.com/store/apps/details?id=com.example.myapp' +``` + +### Get App Store Link in Template + +```python +# In Django template +{% load app_config %} +Download App +``` + +### Generate App Store Links + +```python +# In your view +from django.conf import settings + +context = { + 'app_store_url': settings.HTK_APPSTORE_URL, +} +``` diff --git a/apps/mp/README.md b/apps/mp/README.md new file mode 100644 index 00000000..9918320b --- /dev/null +++ b/apps/mp/README.md @@ -0,0 +1,208 @@ +# MP App (Materialized Properties) + +Performance optimization through materialized properties (computed fields stored in DB). + +## Quick Start + +```python +from htk.apps.mp.services import materialized_property, to_field_name, invalidate_for_instance + +# Define materialized property on model +class User(models.Model): + name = CharField(max_length=100) + followers = ManyToManyField('self') + + # Materialized property field + materialized_follower_count = IntegerField(default=0) + + @materialized_property + def follower_count(self): + """Expensive computation - materialized for O(1) access""" + return self.followers.count() + +# Access property (uses cached value) +user.materialized_follower_count # O(1) lookup + +# Invalidate when followers change +invalidate_for_instance(user, 'follower_count') +``` + +## Concept + +Instead of computing expensive properties every time (O(n) or database queries), materialize them by: +1. Defining a `materialized_*` database field to store the value +2. Creating a `@materialized_property` method with the computation logic +3. Invalidating when dependencies change +4. Periodically recalculating in background jobs + +**Benefits:** +- O(1) lookups instead of expensive queries +- Fast sorting/filtering on computed values +- Reduced database load +- Better performance at scale + +**Trade-offs:** +- Extra database field +- Stale data (until invalidated) +- Need invalidation logic +- Background job to recalculate + +## Common Patterns + +### Basic Materialized Property + +```python +from htk.apps.mp.services import materialized_property, to_field_name + +class Product(models.Model): + name = CharField(max_length=200) + reviews = ManyToManyField(Review) + materialized_review_count = IntegerField(default=0) + + @materialized_property + def review_count(self): + """Number of reviews (cached in DB)""" + return self.reviews.count() + +# Get field name for query +field_name = to_field_name('review_count') # 'materialized_review_count' + +# Sort by materialized property +products = Product.objects.order_by('-materialized_review_count') + +# Filter by materialized property +popular = Product.objects.filter(materialized_review_count__gte=10) +``` + +### Aggregation Properties + +```python +from django.db.models import Avg, Sum +from htk.apps.mp.services import materialized_property + +class Product(models.Model): + name = CharField(max_length=200) + reviews = ManyToManyField(Review, through='ProductReview') + + materialized_avg_rating = DecimalField( + max_digits=3, + decimal_places=2, + default=0.0 + ) + materialized_total_sales = DecimalField( + max_digits=10, + decimal_places=2, + default=0.0 + ) + + @materialized_property + def avg_rating(self): + """Average rating from reviews""" + result = self.reviews.aggregate( + avg=Avg('rating') + ) + return result['avg'] or 0.0 + + @materialized_property + def total_sales(self): + """Sum of all sales""" + from django.db.models import Sum + result = Order.objects.filter( + product=self + ).aggregate(total=Sum('amount')) + return result['total'] or 0.0 +``` + +### Invalidation on Signal + +```python +from django.db.models.signals import post_save, m2m_changed +from django.dispatch import receiver +from htk.apps.mp.services import invalidate_for_instance + +@receiver(post_save, sender=Review) +def invalidate_product_on_review(sender, instance, created, **kwargs): + if created: + # Invalidate when new review added + invalidate_for_instance(instance.product, 'avg_rating') + +@receiver(m2m_changed, sender=Product.followers.through) +def invalidate_follower_count(sender, instance, **kwargs): + # Invalidate when followers change + invalidate_for_instance(instance, 'follower_count') +``` + +### Batch Recalculation + +```python +from celery import shared_task +from django.core.management.base import BaseCommand + +@shared_task +def recalculate_materialized_properties(): + """Background task to recalculate all materialized properties""" + + # Recalculate all products + for product in Product.objects.all(): + product.materialized_avg_rating = product.avg_rating + product.materialized_total_sales = product.total_sales + product.save() + +# Or in management command +class Command(BaseCommand): + def handle(self, *args, **options): + recalculate_materialized_properties() +``` + +### Scheduled Updates + +```python +from celery.schedules import crontab +from celery import shared_task + +# Recalculate every hour +@shared_task +def hourly_sync_materialized(): + """Run every hour via Celery Beat""" + from django.db.models import F, Count + + # Get items that changed recently + User.objects.filter( + updated__gte=timezone.now() - timedelta(hours=1) + ).update( + materialized_follower_count=Count('followers') + ) +``` + +## Performance Comparison + +```python +# Without materialization - O(n) or complex query every access +product.reviews.count() # Executes COUNT query + +# With materialization - O(1) lookup +product.materialized_review_count # Direct field access + +# Sorting +# Without: Product.objects.annotate(review_count=Count('reviews')).order_by('-review_count') +# With: Product.objects.order_by('-materialized_review_count') # Faster! +``` + +## Best Practices + +1. **Use for expensive computations** - Only materialize costly queries +2. **Update on changes** - Invalidate when dependencies change +3. **Schedule batch updates** - Recalculate in background jobs +4. **Index materialized fields** - Add database index for fast filtering +5. **Document invalidation** - Clearly mark what invalidates each property +6. **Stale data tolerance** - Accept data may be stale between invalidations +7. **Monitor accuracy** - Verify materialized values match actual computed values + +## Multiple Invalidations + +```python +from htk.apps.mp.services import invalidate_for_instance + +# Invalidate multiple properties at once +invalidate_for_instance(user, ['follower_count', 'post_count', 'like_count']) +``` diff --git a/apps/notifications/README.md b/apps/notifications/README.md new file mode 100644 index 00000000..51a9c827 --- /dev/null +++ b/apps/notifications/README.md @@ -0,0 +1,234 @@ +# Notifications App + +Send notifications across multiple channels (email, SMS, in-app, Slack). + +## Quick Start + +```python +from htk.apps.notifications.utils import notify + +# Send email notification +notify( + user=user, + message='Order shipped!', + channel='email', + subject='Your order is on the way' +) + +# Send in-app notification (persistent) +notify( + user=user, + message='You have a new message', + channel='in_app' +) + +# Send to multiple users +users = User.objects.filter(organization=org) +for u in users: + notify(u, 'New feature available', channel='email') +``` + +## Notification Channels + +### Email Channel + +Send notifications via Django email backend: + +```python +from htk.apps.notifications.utils import notify + +notify( + user=user, + message='Welcome to our platform!', + channel='email', + subject='Welcome', + html=True # HTML email +) +``` + +### SMS Channel + +Send SMS via Twilio/Plivo: + +```python +from htk.apps.notifications.utils import notify + +notify( + user=user, + message='Your code is: 123456', + channel='sms' +) +``` + +### In-App Channel + +Store persistent notifications in database: + +```python +from htk.apps.notifications.utils import notify + +notify( + user=user, + message='You have a new message', + channel='in_app', + action_url='/messages/' +) +``` + +### Slack Channel + +Send to Slack: + +```python +from htk.apps.notifications.utils import notify + +notify( + user=user, + message='New order received', + channel='slack', + webhook_url='https://hooks.slack.com/services/...' +) +``` + +## Common Patterns + +### Multi-Channel Notifications + +```python +from htk.apps.notifications.utils import notify + +# Send via multiple channels +channels = ['email', 'in_app'] +for channel in channels: + notify(user, message, channel=channel, subject='Important') +``` + +### Conditional Notifications + +```python +from htk.apps.notifications.utils import notify + +# Send based on user preferences +if user.preferences.email_on_order: + notify(user, 'Order confirmed', channel='email') + +if user.preferences.sms_on_urgent: + notify(user, 'Urgent action needed', channel='sms') +``` + +### Signal-Based Notifications + +```python +from django.db.models.signals import post_save +from django.dispatch import receiver +from htk.apps.notifications.utils import notify + +@receiver(post_save, sender=Order) +def notify_on_order_creation(sender, instance, created, **kwargs): + if created: + notify( + instance.user, + f'Order #{instance.order_number} confirmed', + channel='email', + subject='Order Confirmation' + ) +``` + +### Scheduled Notifications + +```python +from htk.apps.notifications.utils import notify +from celery import shared_task + +@shared_task +def send_reminder_notifications(): + upcoming = Event.objects.filter( + start_time__gte=timezone.now(), + start_time__lte=timezone.now() + timedelta(hours=1), + notified=False + ) + + for event in upcoming: + notify( + event.user, + f'Reminder: {event.name} starts in 1 hour', + channel='email' + ) + event.notified = True + event.save() +``` + +### Bulk Notifications + +```python +from htk.apps.notifications.utils import notify + +# Notify multiple users +users = User.objects.filter( + organization=org, + is_active=True +) + +message = 'Scheduled maintenance tonight 2-4 AM' +for user in users: + notify(user, message, channel='email') +``` + +## Notification Models + +Access notification history: + +```python +from htk.apps.notifications.models import Notification + +# Get user's notifications +notifications = Notification.objects.filter(user=user) + +# Get unread notifications +unread = notifications.filter(read=False) + +# Mark as read +notification.read = True +notification.save() + +# Delete old notifications +import datetime +old = Notification.objects.filter( + created__lt=datetime.datetime.now() - datetime.timedelta(days=30) +) +old.delete() +``` + +## Configuration + +```python +# settings.py +NOTIFICATION_CHANNELS = ['email', 'sms', 'in_app', 'slack'] + +# Email configuration +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = 'smtp.example.com' +EMAIL_PORT = 587 +EMAIL_USE_TLS = True + +# SMS configuration +SMS_PROVIDER = 'twilio' # or 'plivo' +SMS_ACCOUNT_SID = os.environ.get('SMS_ACCOUNT_SID') +SMS_AUTH_TOKEN = os.environ.get('SMS_AUTH_TOKEN') + +# Slack configuration +SLACK_WEBHOOK_URL = os.environ.get('SLACK_WEBHOOK_URL') + +# In-app notification retention +NOTIFICATION_RETENTION_DAYS = 30 +``` + +## Best Practices + +1. **Respect user preferences** - Check notification settings before sending +2. **Use appropriate channels** - Email for important, SMS for urgent, in-app for informational +3. **Avoid notification spam** - Rate limit notifications to same user +4. **Include action URLs** - Help users take action from notifications +5. **Test with staging** - Test notifications in staging before production +6. **Batch process** - Use Celery for bulk notifications +7. **Monitor delivery** - Track sent, failed, bounced notifications diff --git a/apps/notifications/constants/README.md b/apps/notifications/constants/README.md new file mode 100644 index 00000000..a61a7565 --- /dev/null +++ b/apps/notifications/constants/README.md @@ -0,0 +1,46 @@ +# Notifications Constants + +## Overview + +This module defines configuration for notification system behavior, including predicates for dismissible alerts and key generation strategies. + +## Constants + +### Alert Configuration + +- **`HTK_NOTIFICATIONS_DISMISSIBLE_ALERT_DISPLAY_PREDICATES`** - Default: `{}` - Dict mapping alert types to display predicate functions +- **`HTK_NOTIFICATIONS_DISMISSIBLE_ALERT_KEY_GENERATORS`** - Default: `{}` - Dict mapping alert types to key generator functions + +## Usage Examples + +### Configure Alert Predicates + +```python +# In Django settings.py +def should_show_upgrade_alert(request): + return not request.user.is_premium + +def should_show_verification_alert(request): + return not request.user.email_verified + +HTK_NOTIFICATIONS_DISMISSIBLE_ALERT_DISPLAY_PREDICATES = { + 'upgrade': should_show_upgrade_alert, + 'verify_email': should_show_verification_alert, +} +``` + +### Configure Alert Key Generators + +```python +# In Django settings.py +def generate_upgrade_key(request): + return f"upgrade_alert_{request.user.id}" + +def generate_verification_key(request): + return f"verify_alert_{request.user.id}" + +HTK_NOTIFICATIONS_DISMISSIBLE_ALERT_KEY_GENERATORS = { + 'upgrade': generate_upgrade_key, + 'verify_email': generate_verification_key, +} +``` diff --git a/apps/organizations/README.md b/apps/organizations/README.md new file mode 100644 index 00000000..ebdc5e18 --- /dev/null +++ b/apps/organizations/README.md @@ -0,0 +1,186 @@ +# Organizations App + +Multi-organization support with role-based access control and membership management. + +## Overview + +The `organizations` app provides: + +- Create and manage organizations +- Role-based permissions (owner, manager, member) +- Organization invitations and member management +- Organization-specific settings +- Permission decorators for views + +## Quick Start + +### Create Organizations + +```python +from htk.apps.organizations.models import BaseOrganization + +# Create organization +org = BaseOrganization.objects.create( + name='Acme Corporation', + slug='acme-corp' +) + +# Add members +org.add_member(user1, role='owner') +org.add_member(user2, role='manager') +org.add_member(user3, role='member') +``` + +### Invite Members + +```python +from htk.apps.organizations.utils import invite_organization_member + +# Send invitation +invite_organization_member( + org=org, + inviter=owner_user, + email='newuser@example.com', + role='member' +) + +# User clicks link and accepts +# Automatically adds them to organization +``` + +### Manage Permissions + +```python +# Check if user can manage org +if user in org.get_owners(): + # Show admin panel + pass + +# Get all members +members = org.get_members() + +# Check specific permission +from htk.apps.organizations.decorators import require_organization_permission + +@require_organization_permission('edit_members') +def edit_org_members(request, org_id): + # Only org admins can access + pass +``` + +## Models + +- **`BaseOrganization`** - Main organization model +- **`BaseOrganizationMember`** - Tracks members and roles +- **`BaseOrganizationInvitation`** - Pending invitations + +## Roles & Permissions + +Default roles: +- **Owner** - Full access, manage members and settings +- **Manager** - Manage content, limited member access +- **Member** - Basic access to organization resources + +Customize in your model: + +```python +from htk.apps.organizations.models import BaseOrganization + +class Organization(BaseOrganization): + ROLE_CHOICES = ( + ('owner', 'Owner'), + ('manager', 'Manager'), + ('editor', 'Editor'), + ('viewer', 'Viewer'), + ) + + role = CharField(choices=ROLE_CHOICES, default='member') +``` + +## Common Patterns + +### Protect Views by Role + +```python +from htk.apps.organizations.decorators import require_organization_permission + +@require_organization_permission('manage_members') +def manage_org_members(request, org_id): + org = BaseOrganization.objects.get(id=org_id) + # Only users with permission can access + return render(request, 'org/manage_members.html', {'org': org}) +``` + +### Organization-Specific Data + +```python +# Attach data to organizations +class Team(models.Model): + organization = ForeignKey(BaseOrganization) + name = CharField(max_length=100) + members = ManyToManyField(User) + +# Query org's teams +teams = org.team_set.all() +``` + +### Invitation Flow + +```python +from htk.apps.organizations.models import BaseOrganizationInvitation + +# Create invitation +invite = BaseOrganizationInvitation.objects.create( + organization=org, + invited_user_email='user@example.com', + invited_by=request.user, + role='member' +) + +# Accept invitation +@login_required +def accept_invitation(request, token): + invite = BaseOrganizationInvitation.objects.get(token=token) + invite.accept() # Adds user to org + return redirect('org_dashboard') +``` + +### Bulk Operations + +```python +# Get all users with specific role across orgs +from django.db.models import Q + +admins = User.objects.filter( + organizationmember__role='owner' +).distinct() + +# Get orgs where user is owner +owned_orgs = BaseOrganization.objects.filter( + members__user=user, + members__role='owner' +) +``` + +## Settings & Configuration + +```python +# settings.py +ORGANIZATIONS_INVITATION_EXPIRY = 7 # days +ORGANIZATIONS_REQUIRE_EMAIL_VERIFICATION = True +ORGANIZATIONS_DEFAULT_ROLE = 'member' +``` + +## Best Practices + +1. **Extend BaseOrganization** for custom fields +2. **Use permission decorators** for access control +3. **Validate member additions** before saving +4. **Archive instead of delete** organizations +5. **Log organizational changes** for audit trail +6. **Cache membership** for performance + +## Signals + +Automatic signal handlers: +- `organization_invitation_created_or_updated` - Sends invitation email diff --git a/apps/organizations/constants/README.md b/apps/organizations/constants/README.md new file mode 100644 index 00000000..946cd4dd --- /dev/null +++ b/apps/organizations/constants/README.md @@ -0,0 +1,117 @@ +# Organizations Constants + +## Overview + +This module defines configuration for the organizations system, including model references, naming conventions, sorting preferences, and invitation settings. + +## Constants + +### Model References + +- **`HTK_ORGANIZATION_MODEL`** - Default: `'organizations.Organization'` +- **`HTK_ORGANIZATION_ATTRIBUTE_MODEL`** - Default: `'organizations.OrganizationAttribute'` +- **`HTK_ORGANIZATION_MEMBER_MODEL`** - Default: `'organizations.OrganizationMember'` +- **`HTK_ORGANIZATION_INVITATION_MODEL`** - Default: `'organizations.OrganizationInvitation'` +- **`HTK_ORGANIZATION_JOIN_REQUEST_MODEL`** - Default: `'organizations.OrganizationJoinRequest'` +- **`HTK_ORGANIZATION_TEAM_MODEL`** - Default: `'organizations.OrganizationTeam'` +- **`HTK_ORGANIZATION_TEAM_MEMBER_MODEL`** - Default: `'organizations.OrganizationTeamMember'` +- **`HTK_ORGANIZATION_TEAM_POSITION_MODEL`** - Default: `'organizations.OrganizationTeamPosition'` +- **`HTK_ORGANIZATION_TEAM_MEMBER_POSITION_MODEL`** - Default: `'organizations.OrganizationTeamMemberPosition'` + +### Display Configuration + +- **`HTK_ORGANIZATION_READBLE_NAME`** - Default: `'Organization'` - Human-readable singular name +- **`HTK_ORGANIZATION_SYMBOL`** - Default: `'org'` - Short symbol for URLs/codes +- **`HTK_ORGANIZATION_URL_PK_KEY`** - Default: `'org_id'` - URL parameter name for organization ID + +### Sorting Configuration + +- **`HTK_ORGANIZATION_MEMBERS_SORT_ORDER`** - Default: `('user__first_name', 'user__last_name', 'user__username')` +- **`HTK_ORGANIZATION_TEAM_MEMBERS_SORT_ORDER`** - Default: `('user__first_name', 'user__last_name', 'user__username')` + +### Invitation Configuration + +- **`HTK_ORGANIZATION_INVITATION_RESPONSE_URL_NAME`** - Default: `''` - URL name for invitation response view +- **`HTK_ORGANIZATION_MOBILE_INVITATION_RESPONSE_URL_FORMAT`** - Default: `''` - Mobile invitation response URL format +- **`HTK_ORGANIZATION_INVITATION_EMAIL_TEMPLATE_NAME`** - Default: `''` - Email template for invitations +- **`HTK_ORGANIZATION_INVITATION_EMAIL_SUBJECT`** - Default: `'You have been invited to join {}'` - Email subject line + +## Enums + +### OrganizationMemberRoles + +Roles for organization members with hierarchy: + +```python +from htk.apps.organizations.enums import OrganizationMemberRoles + +# Access role values +role = OrganizationMemberRoles.OWNER +print(f"{role.name}: {role.value}") # OWNER: 1 + +# Available roles (hidden_choices excludes SYSADMIN) +OrganizationMemberRoles.SYSADMIN # value: 0 (hidden) +OrganizationMemberRoles.OWNER # value: 1 +OrganizationMemberRoles.ADMIN # value: 10 +OrganizationMemberRoles.MEMBER # value: 100 + +# Check if role is hidden +if role.is_hidden(): + print("This role is hidden from UI") +``` + +### OrganizationTeamMemberRoles + +Roles for team members (subset of organization roles): + +```python +from htk.apps.organizations.enums import OrganizationTeamMemberRoles + +team_role = OrganizationTeamMemberRoles.ADMIN +OrganizationTeamMemberRoles.ADMIN # value: 10 +OrganizationTeamMemberRoles.MEMBER # value: 100 +``` + +## Usage Examples + +### Configure Custom Models + +```python +# In Django settings.py +HTK_ORGANIZATION_MODEL = 'myapp.Organization' +HTK_ORGANIZATION_TEAM_MODEL = 'myapp.Team' +HTK_ORGANIZATION_MEMBER_MODEL = 'myapp.Member' +``` + +### Load Models Dynamically + +```python +from django.apps import apps +from htk.apps.organizations.constants import HTK_ORGANIZATION_MODEL + +Organization = apps.get_model(HTK_ORGANIZATION_MODEL) +orgs = Organization.objects.all() +``` + +### Assign Member Roles + +```python +from htk.apps.organizations.enums import OrganizationMemberRoles + +# Assign a member role +member.role = OrganizationMemberRoles.ADMIN.value +member.save() + +# Check role permissions +if member.role == OrganizationMemberRoles.OWNER.value: + print("User can manage organization") +``` + +### Configure Invitations + +```python +# In Django settings.py +HTK_ORGANIZATION_INVITATION_RESPONSE_URL_NAME = 'org_invite_response' +HTK_ORGANIZATION_INVITATION_EMAIL_TEMPLATE_NAME = 'emails/org_invitation.html' +HTK_ORGANIZATION_INVITATION_EMAIL_SUBJECT = 'You are invited to {}' +``` diff --git a/apps/prelaunch/README.md b/apps/prelaunch/README.md new file mode 100644 index 00000000..47b88b66 --- /dev/null +++ b/apps/prelaunch/README.md @@ -0,0 +1,290 @@ +# Prelaunch App + +Early access and pre-launch signup management. + +## Quick Start + +```python +from htk.apps.prelaunch.models import PrelaunchSignup +from htk.apps.prelaunch.utils import get_unique_signups, is_prelaunch_mode + +# Create signup +signup = PrelaunchSignup.objects.create( + email='user@example.com', + referral_code='friend123' +) + +# Get or create +signup, created = PrelaunchSignup.objects.get_or_create_by_email( + email='user@example.com' +) + +# Grant early access +signup.grant_early_access() + +# Check prelaunch mode +if is_prelaunch_mode(): + # Show prelaunch message + pass +``` + +## Prelaunch Signup Management + +### Create Signups + +```python +from htk.apps.prelaunch.models import PrelaunchSignup + +# Manual signup +signup = PrelaunchSignup.objects.create( + email='user@example.com', + first_name='John', + last_name='Doe', + referral_code='code123' +) + +# Bulk import +emails = ['user1@example.com', 'user2@example.com', 'user3@example.com'] +for email in emails: + PrelaunchSignup.objects.get_or_create(email=email) +``` + +### Grant Early Access + +```python +from htk.apps.prelaunch.models import PrelaunchSignup + +# Grant access to one user +signup = PrelaunchSignup.objects.get(email='user@example.com') +signup.grant_early_access() + +# Grant to multiple users +signups = PrelaunchSignup.objects.filter(email__endswith='@company.com') +for signup in signups: + signup.grant_early_access() + +# Bulk grant +PrelaunchSignup.objects.filter( + signup_date__gte=date(2024, 1, 1) +).update(has_early_access=True) +``` + +### Track Signups + +```python +from htk.apps.prelaunch.utils import get_unique_signups + +# Get all unique signups +unique = get_unique_signups() + +# Count by day +from django.db.models import Count +from django.utils import timezone +from datetime import timedelta + +today = timezone.now().date() +week_ago = today - timedelta(days=7) + +daily_signups = PrelaunchSignup.objects.filter( + created__date__gte=week_ago +).values('created__date').annotate(count=Count('id')) +``` + +## Prelaunch Mode + +### Enable/Disable Prelaunch + +```python +from htk.apps.prelaunch.utils import is_prelaunch_mode, set_prelaunch_mode + +# Check if in prelaunch mode +if is_prelaunch_mode(): + # Restrict access + pass + +# Set prelaunch status +set_prelaunch_mode(True) # Enable prelaunch +set_prelaunch_mode(False) # Disable prelaunch +``` + +### Middleware Protection + +```python +from htk.apps.prelaunch.middleware import PrelaunchMiddleware + +# In settings.py +MIDDLEWARE = [ + # ... other middleware ... + 'htk.apps.prelaunch.middleware.PrelaunchMiddleware', +] + +# When prelaunch is active: +# - Unauthenticated users redirected to prelaunch page +# - Users with early access can proceed +# - Exception paths bypass prelaunch check +``` + +### Exception Paths + +```python +# settings.py +PRELAUNCH_EXCEPTED_PATHS = [ + '/health/', + '/api/status/', + '/static/', + '/media/', + '/prelaunch/', +] + +PRELAUNCH_REDIRECT_URL = '/prelaunch/' +``` + +## Common Patterns + +### Referral System + +```python +from htk.apps.prelaunch.models import PrelaunchSignup + +# Track referrals +referrer = PrelaunchSignup.objects.get(referral_code='ABC123') +referred = PrelaunchSignup.objects.create( + email='friend@example.com', + referred_by=referrer +) + +# Get referral count +referral_count = referrer.referred_signups.count() + +# Reward referrals +if referrer.referred_signups.filter(has_early_access=True).count() >= 5: + # Grant premium features + referrer.grant_special_status() +``` + +### Waitlist Notifications + +```python +from django.db.models.signals import post_save +from django.dispatch import receiver +from htk.apps.prelaunch.models import PrelaunchSignup +from htk.apps.notifications.utils import notify + +@receiver(post_save, sender=PrelaunchSignup) +def notify_on_signup(sender, instance, created, **kwargs): + if created: + # Send confirmation email + notify( + instance, + f'You\'ve been added to the waitlist for {position}', + channel='email', + subject='Waitlist Confirmation' + ) + +@receiver(post_save, sender=PrelaunchSignup) +def send_access_granted_email(sender, instance, **kwargs): + if instance.has_early_access: + notify( + instance, + 'You now have early access!', + channel='email', + subject='Early Access Granted' + ) +``` + +### Phased Rollout + +```python +from htk.apps.prelaunch.models import PrelaunchSignup +from django.utils import timezone +from datetime import timedelta + +# Grant access in waves +total_signups = PrelaunchSignup.objects.count() + +# Wave 1: First 10% +wave1_count = int(total_signups * 0.1) +wave1 = PrelaunchSignup.objects.filter( + has_early_access=False +).order_by('created')[:wave1_count] + +for signup in wave1: + signup.has_early_access = True + signup.save() + +# Wave 2: Next 25% after 1 week +wave2_date = timezone.now() - timedelta(days=7) +wave2_count = int(total_signups * 0.25) +wave2 = PrelaunchSignup.objects.filter( + has_early_access=False, + created__lte=wave2_date +).order_by('created')[:wave2_count] + +for signup in wave2: + signup.grant_early_access() +``` + +### Unsubscribe Management + +```python +from htk.apps.prelaunch.models import PrelaunchSignup + +# Track unsubscribes +signup = PrelaunchSignup.objects.get(email='user@example.com') +signup.is_subscribed = False +signup.save() + +# Get active signups +active = PrelaunchSignup.objects.filter(is_subscribed=True) + +# Send to active only +for signup in active: + send_email(signup.email, 'Launch announcement!') +``` + +## Models + +### PrelaunchSignup + +```python +class PrelaunchSignup(models.Model): + email = EmailField(unique=True) + first_name = CharField(max_length=100, blank=True) + last_name = CharField(max_length=100, blank=True) + referral_code = CharField(max_length=20, unique=True) + referred_by = ForeignKey('self', null=True, blank=True, on_delete=models.SET_NULL) + has_early_access = BooleanField(default=False) + is_subscribed = BooleanField(default=True) + created = DateTimeField(auto_now_add=True) + updated = DateTimeField(auto_now=True) +``` + +## Configuration + +```python +# settings.py +PRELAUNCH_ENABLED = True +PRELAUNCH_REDIRECT_URL = '/early-access/' + +# Paths that bypass prelaunch check +PRELAUNCH_EXCEPTED_PATHS = [ + '/health/', + '/api/status/', + '/static/', + '/media/', + '/prelaunch/', + '/admin/', +] + +# Early access paths (accessible with early access) +PRELAUNCH_EARLY_ACCESS_PATHS = [] +``` + +## Best Practices + +1. **Use referral codes** - Track referral source +2. **Phased rollout** - Gradually release to manage load +3. **Notify on access** - Send confirmation when granted +4. **Track metrics** - Monitor signup conversion and engagement +5. **Handle unsubscribes** - Respect user preferences +6. **Provide feedback** - Show waitlist position if possible diff --git a/apps/prelaunch/api/README.md b/apps/prelaunch/api/README.md new file mode 100644 index 00000000..d9d0bf67 --- /dev/null +++ b/apps/prelaunch/api/README.md @@ -0,0 +1,136 @@ +# API + +## Overview + +This API module provides REST API endpoints for programmatic access to the service. Endpoints support standard HTTP methods and return JSON responses. + +## Quick Start + +### Basic Request + +```python +import requests + +# Make API request +response = requests.get('https://api.example.com/endpoint/', auth=auth) +result = response.json() +``` + +### Authentication + +API endpoints require authentication. Configure credentials: + +```python +from requests.auth import HTTPBearerAuth + +auth = HTTPBearerAuth(token='your_token') +response = requests.get('https://api.example.com/endpoint/', auth=auth) +``` + +## API Endpoints + +### Available Endpoints + +Check the `views.py` file for a complete list of available endpoints and their parameters. + +### Request Format + +``` +METHOD /endpoint/ HTTP/1.1 +Host: api.example.com +Content-Type: application/json +Authorization: Bearer + +{ + "param1": "value1", + "param2": "value2" +} +``` + +### Response Format + +Successful responses return HTTP 200 with JSON data. + +Error responses include status code and error message. + +## Common Operations + +### Get Resource + +```python +response = requests.get( + 'https://api.example.com/resource/{id}/', + auth=auth +) +resource = response.json() +``` + +### Create Resource + +```python +response = requests.post( + 'https://api.example.com/resource/', + json={'field': 'value'}, + auth=auth +) +new_resource = response.json() +``` + +### Update Resource + +```python +response = requests.patch( + 'https://api.example.com/resource/{id}/', + json={'field': 'new_value'}, + auth=auth +) +updated = response.json() +``` + +### Delete Resource + +```python +response = requests.delete( + 'https://api.example.com/resource/{id}/', + auth=auth +) +# Returns 204 No Content on success +``` + +## Error Handling + +```python +import requests +from requests.exceptions import RequestException + +try: + response = requests.get(endpoint, auth=auth) + response.raise_for_status() + data = response.json() +except requests.HTTPError as e: + print(f"HTTP Error: {e.response.status_code}") +except RequestException as e: + print(f"Request Error: {e}") +``` + +## Configuration + +Configure API settings in Django settings: + +```python +# settings.py +HTK_API_ENABLED = True +HTK_API_TIMEOUT = 30 +HTK_API_MAX_RETRIES = 3 +HTK_API_RATE_LIMIT = 1000 +``` + +## Best Practices + +1. **Handle errors** - Implement proper error handling for all requests +2. **Use pagination** - For list endpoints, use limit and offset parameters +3. **Add retries** - Implement exponential backoff for transient failures +4. **Cache responses** - Cache frequently accessed data when appropriate +5. **Validate input** - Validate request parameters before sending +6. **Log requests** - Log all API calls for debugging and monitoring +7. **Set timeouts** - Always set request timeouts to prevent hanging diff --git a/apps/prelaunch/constants/README.md b/apps/prelaunch/constants/README.md new file mode 100644 index 00000000..ae215cd0 --- /dev/null +++ b/apps/prelaunch/constants/README.md @@ -0,0 +1,86 @@ +# Prelaunch Constants + +## Overview + +This module provides configuration for prelaunch/beta mode, including feature toggles, URL settings, email templates, and exception lists for views that bypass prelaunch restrictions. + +## Constants + +### Feature Toggle + +- **`HTK_PRELAUNCH_MODE`** - Default: `False` - Enable prelaunch mode to restrict access + +### Model and Form Configuration + +- **`HTK_PRELAUNCH_MODEL`** - Default: `'htk.PrelaunchSignup'` - Prelaunch signup model +- **`HTK_PRELAUNCH_FORM_CLASS`** - Default: `'htk.apps.prelaunch.forms.PrelaunchSignupForm'` - Signup form class + +### URL Configuration + +- **`HTK_PRELAUNCH_URL_NAME`** - Default: `'htk_prelaunch'` - URL name for prelaunch page +- **`HTK_PRELAUNCH_HOST_REGEXPS`** - Default: `[]` - Regex patterns for hosts to apply prelaunch + +### Exception Lists + +- **`HTK_PRELAUNCH_EXCEPTION_VIEWS`** - Views exempt from prelaunch redirect (URL names) +- **`HTK_PRELAUNCH_EXCEPTION_URLS`** - URL patterns exempt from prelaunch redirect (regex) + +### Template Configuration + +- **`HTK_PRELAUNCH_TEMPLATE`** - Default: `'htk/prelaunch.html'` - Prelaunch signup page template +- **`HTK_PRELAUNCH_SUCCESS_TEMPLATE`** - Default: `None` - Template shown after signup + +### Email Configuration + +- **`HTK_PRELAUNCH_EMAIL_TEMPLATE`** - Default: `'htk/prelaunch'` - Confirmation email template +- **`HTK_PRELAUNCH_EMAIL_SUBJECT`** - Default: `'Thanks for signing up'` - Email subject +- **`HTK_PRELAUNCH_EMAIL_BCC`** - Default: `[]` - BCC email addresses +- **`HTK_PRELAUNCH_EARLY_ACCESS_EMAIL_TEMPLATE`** - Default: `'htk/prelaunch_early_access'` - Early access template +- **`HTK_PRELAUNCH_EARLY_ACCESS_EMAIL_SUBJECT`** - Default: `'Early Access Granted'` - Early access subject + +### Admin Tools + +- **`HTK_PRELAUNCH_ADMINTOOLS_TOGGLE_URL_NAME`** - Default: `'admintools_api_prelaunch_toggle'` - URL for admin toggle + +## Usage Examples + +### Enable Prelaunch Mode + +```python +# In Django settings.py +HTK_PRELAUNCH_MODE = True +HTK_PRELAUNCH_TEMPLATE = 'myapp/prelaunch.html' +``` + +### Configure Host Restrictions + +```python +# In Django settings.py +import re + +HTK_PRELAUNCH_HOST_REGEXPS = [ + r'(dev|qa|alpha)\.example\.com', + r'demo\.example\.com', +] +``` + +### Add View Exceptions + +```python +# In Django settings.py +HTK_PRELAUNCH_EXCEPTION_VIEWS = ( + 'htk_prelaunch', + 'htk_feedback_submit', + 'robots', + 'django.contrib.sitemaps.views.sitemap', +) +``` + +### Configure Custom Emails + +```python +# In Django settings.py +HTK_PRELAUNCH_EMAIL_TEMPLATE = 'myapp/emails/prelaunch_confirmation.txt' +HTK_PRELAUNCH_EMAIL_SUBJECT = 'Welcome! You are now on the waiting list' +HTK_PRELAUNCH_EARLY_ACCESS_EMAIL_TEMPLATE = 'myapp/emails/early_access.txt' +``` diff --git a/apps/prelaunch/models/README.md b/apps/prelaunch/models/README.md new file mode 100644 index 00000000..918bda75 --- /dev/null +++ b/apps/prelaunch/models/README.md @@ -0,0 +1,167 @@ +# Models + +## Overview + +This models module defines Django models that represent the database schema. Models define fields, relationships, methods, and metadata for objects persisted to the database. + +## Quick Start + +### Query Models + +```python +from htk.apps.prelaunch.models import PrelaunchSignup + +# Get all signups +signups = PrelaunchSignup.objects.all() + +# Filter by condition +active = PrelaunchSignup.objects.filter(is_active=True) + +# Get single signup +signup = PrelaunchSignup.objects.get(id=1) # Raises DoesNotExist if not found + +# Get or None +signup = PrelaunchSignup.objects.filter(email='user@example.com').first() # Returns None if not found +``` + +### Create Objects + +```python +from htk.apps.prelaunch.models import PrelaunchSignup + +# Create and save +signup = PrelaunchSignup.objects.create( + email='user@example.com', + ip_address='192.168.1.1' +) + +# Create instance, modify, then save +signup = PrelaunchSignup(email='user@example.com') +signup.save() + +# Bulk create +signups = PrelaunchSignup.objects.bulk_create([ + PrelaunchSignup(email='user1@example.com'), + PrelaunchSignup(email='user2@example.com'), +]) +``` + +## Model Fields + +Models include various field types: + +- **CharField** - Text (limited length) +- **TextField** - Long text +- **IntegerField** - Integer numbers +- **ForeignKey** - Relationship to another model +- **ManyToManyField** - Many-to-many relationship +- **DateTimeField** - Date and time +- **BooleanField** - True/False + +### Field Options + +```python +from htk.apps.prelaunch.models import PrelaunchSignup + +# PrelaunchSignup fields include: +# - email: EmailField (unique, max_length=254) +# - is_active: BooleanField (default=True) +# - created: DateTimeField (auto_now_add=True) +# - updated: DateTimeField (auto_now=True) +# - ip_address: GenericIPAddressField (optional) + +# Query by field options +recently_created = PrelaunchSignup.objects.filter( + created__gte=timezone.now() - timedelta(days=7) +) +``` + +## Relationships + +### Foreign Key + +One-to-many relationship: + +```python +class Author(models.Model): + name = models.CharField(max_length=100) + +class Book(models.Model): + title = models.CharField(max_length=200) + author = models.ForeignKey( + Author, + on_delete=models.CASCADE, + related_name='books' + ) +``` + +### Many-to-Many + +Many-to-many relationship: + +```python +class Article(models.Model): + title = models.CharField(max_length=200) + tags = models.ManyToManyField(Tag, related_name='articles') +``` + +## Properties and Methods + +### @property + +Computed property example: + +```python +from htk.apps.prelaunch.models import PrelaunchSignup + +signup = PrelaunchSignup.objects.first() + +# Use computed properties if defined on model +# (check PrelaunchSignup.models for available @property methods) +if hasattr(signup, 'display_name'): + print(signup.display_name) +``` + +## Querysets + +### Filtering + +```python +from htk.apps.prelaunch.models import PrelaunchSignup +from django.utils import timezone +from datetime import timedelta + +# Exact match +PrelaunchSignup.objects.filter(is_active=True) + +# Greater than - signups from last week +PrelaunchSignup.objects.filter(created__gt=timezone.now() - timedelta(days=7)) + +# Contains - email domain search +PrelaunchSignup.objects.filter(email__icontains='@example.com') + +# In list - specific statuses +PrelaunchSignup.objects.filter(is_active__in=[True, False]) +``` + +### Ordering + +```python +from htk.apps.prelaunch.models import PrelaunchSignup + +# Ascending - oldest first +PrelaunchSignup.objects.all().order_by('created') + +# Descending - newest first +PrelaunchSignup.objects.all().order_by('-created') +``` + +## Best Practices + +1. **Use QuerySets** - Dont fetch unnecessary data +2. **Select related** - Use `select_related()` for ForeignKey +3. **Prefetch related** - Use `prefetch_related()` for ManyToMany +4. **Index fields** - Add `db_index=True` to frequently filtered fields +5. **Use choices** - For fields with limited values +6. **Document fields** - Use `help_text` +7. **Add Meta class** - Configure ordering, permissions, verbose names diff --git a/apps/sites/README.md b/apps/sites/README.md new file mode 100644 index 00000000..da223c1d --- /dev/null +++ b/apps/sites/README.md @@ -0,0 +1,259 @@ +# Sites App + +Multi-site Django support wrapper for managing multiple website domains. + +## Quick Start + +```python +from django.contrib.sites.models import Site +from htk.apps.sites.utils import get_current_site, get_site_name, build_absolute_url + +# Get current site +site = get_current_site(request) +domain = site.domain +name = site.name + +# Build absolute URL +url = build_absolute_url(request, '/path/to/resource/') + +# Use in templates +site_domain = request.site.domain +``` + +## Multi-Site Setup + +### Configure Sites + +```python +# settings.py +SITE_ID = 1 + +# Create multiple sites +from django.contrib.sites.models import Site + +# Site 1 +site1 = Site.objects.get_or_create( + id=1, + defaults={ + 'domain': 'example.com', + 'name': 'Example' + } +) + +# Site 2 (different domain) +site2 = Site.objects.get_or_create( + id=2, + defaults={ + 'domain': 'example.co.uk', + 'name': 'Example UK' + } +) +``` + +### Get Current Site + +```python +from django.contrib.sites.shortcuts import get_current_site +from htk.apps.sites.utils import get_current_site as htk_get_current_site + +# Django built-in +site = get_current_site(request) + +# HTK wrapper with fallback +site = htk_get_current_site(request) +``` + +## Common Patterns + +### Build URLs + +```python +from django.contrib.sites.shortcuts import get_current_site + +def build_absolute_url(request, path): + """Build absolute URL with domain""" + site = get_current_site(request) + protocol = 'https' if request.is_secure() else 'http' + return f'{protocol}://{site.domain}{path}' + +# Usage +reset_link = build_absolute_url(request, f'/auth/reset/{token}/') +``` + +### Site-Specific Settings + +```python +from django.contrib.sites.shortcuts import get_current_site + +class SiteConfig: + CONFIGS = { + 'example.com': { + 'theme': 'default', + 'language': 'en', + 'timezone': 'US/Eastern' + }, + 'example.co.uk': { + 'theme': 'uk', + 'language': 'en-GB', + 'timezone': 'Europe/London' + } + } + + @classmethod + def get_config(cls, domain): + return cls.CONFIGS.get(domain, cls.CONFIGS['example.com']) + +# Usage +def my_view(request): + site = get_current_site(request) + config = SiteConfig.get_config(site.domain) + return render(request, 'view.html', {'config': config}) +``` + +### Site-Specific Email Templates + +```python +from django.contrib.sites.shortcuts import get_current_site +from django.template.loader import render_to_string + +def send_site_email(request, user, template_name, context): + """Send email with site-specific template""" + site = get_current_site(request) + + # Try site-specific template first + try: + html = render_to_string( + f'emails/{site.domain}/{template_name}', + context + ) + except TemplateDoesNotExist: + # Fall back to default + html = render_to_string( + f'emails/{template_name}', + context + ) + + send_mail( + subject=f'Message from {site.name}', + message=html, + from_email=f'noreply@{site.domain}', + recipient_list=[user.email] + ) +``` + +### Site-Specific Content + +```python +from django.contrib.sites.models import Site +from django.db import models + +class Article(models.Model): + title = models.CharField(max_length=200) + content = models.TextField() + sites = models.ManyToManyField(Site) # Multi-site support + created = models.DateTimeField(auto_now_add=True) + +def get_site_articles(site): + """Get articles for specific site""" + return Article.objects.filter(sites=site) +``` + +### Canonical URLs + +```python +from django.contrib.sites.shortcuts import get_current_site + +def get_canonical_url(request, path): + """Get canonical URL for SEO""" + site = get_current_site(request) + protocol = 'https' + return f'{protocol}://{site.domain}{path}' + +# Usage in template +{% load sites %} + +``` + +### Redirect to Site Domain + +```python +from django.contrib.sites.shortcuts import get_current_site +from django.shortcuts import redirect + +def enforce_site_domain(request): + """Redirect if accessing from wrong domain""" + site = get_current_site(request) + + if request.get_host() != site.domain: + protocol = 'https' if request.is_secure() else 'http' + url = f'{protocol}://{site.domain}{request.path}' + return redirect(url, permanent=True) +``` + +## Template Usage + +### In Templates + +```django +{% load sites %} + + +
+

© {{ request.site.name }}

+ Home +
+ + +About Us + + +

Contact: support@{{ request.site.domain }}

+``` + +### Dynamic Base URL + +```django +{% load sites %} + +{% url 'home' as home_url %} + + Go Home + +``` + +## Configuration + +```python +# settings.py +SITE_ID = 1 # Default site ID + +# Enable sites framework +INSTALLED_APPS = [ + 'django.contrib.sites', + 'htk.apps.sites', + # ... +] + +# Site-specific settings +SITE_DOMAINS = { + 1: 'example.com', + 2: 'example.co.uk', + 3: 'example.de', +} + +SITE_THEMES = { + 'example.com': 'default', + 'example.co.uk': 'uk', + 'example.de': 'de', +} +``` + +## Best Practices + +1. **Set SITE_ID in settings** - Configure default site +2. **Use get_current_site()** - Always get site dynamically +3. **Build absolute URLs** - Include protocol and domain +4. **Site-specific content** - Use through table for flexibility +5. **Canonical URLs** - Set for SEO with multiple domains +6. **Email templates** - Override per site +7. **Settings inheritance** - Provide defaults + overrides diff --git a/apps/store/README.md b/apps/store/README.md new file mode 100644 index 00000000..c1a6b4ec --- /dev/null +++ b/apps/store/README.md @@ -0,0 +1,117 @@ +# Store App + +> E-commerce store and product management. + +## Purpose + +The store app provides product catalog, inventory, cart management, and order processing. + +## Quick Start + +```python +from htk.apps.store.models import * + +# Create and use models +# See models.py for available classes +instance = YourModel.objects.create(field='value') +``` + +## Key Components + +| Component | Purpose | +|-----------|---------| +| **Models** | Product, Inventory, Order, OrderItem models | +| **Views** | Provide web interface and API endpoints | +| **Forms** | Handle data validation and user input | +| **Serializers** | API serialization and deserialization | + +## Common Patterns + +### Basic Model Operations + +```python +from htk.apps.store.models import * + +# Create +obj = YourModel.objects.create(name='Example') + +# Read +obj = YourModel.objects.get(id=1) + +# Update +obj.name = 'Updated' +obj.save() + +# Delete +obj.delete() +``` + +### Filtering and Querying + +```python +# Filter by attributes +results = YourModel.objects.filter(status='active') + +# Order by field +ordered = YourModel.objects.all().order_by('-created_at') + +# Count results +count = YourModel.objects.filter(status='active').count() +``` + +## API Endpoints + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/api/store/` | GET | List items | +| `/api/store/` | POST | Create item | +| `/api/store/{id}/` | GET | Get item details | +| `/api/store/{id}/` | PATCH | Update item | +| `/api/store/{id}/` | DELETE | Delete item | + +## Configuration + +```python +# settings.py +HTK_STORE_ENABLED = True +# Additional settings in constants/defaults.py +``` + +## Best Practices + +- **Use ORM** - Leverage Django ORM for database queries +- **Validate input** - Use forms and serializers for validation +- **Check permissions** - Verify user has required permissions +- **Cache results** - Cache expensive queries and operations +- **Write tests** - Test models, views, forms, and API endpoints + +## Testing + +```python +from django.test import TestCase +from htk.apps.store.models import * + +class StoreTestCase(TestCase): + def setUp(self): + """Create test fixtures""" + self.obj = YourModel.objects.create(field='value') + + def test_model_creation(self): + """Test creating an object""" + self.assertIsNotNone(self.obj.id) +``` + +## Related Apps + +- `htk.apps.accounts` - User accounts + +## References + +- [Django Models](https://docs.djangoproject.com/en/stable/topics/db/models/) +- [Django Forms](https://docs.djangoproject.com/en/stable/topics/forms/) + +## Notes + +- **Status:** Production-Ready +- **Last Updated:** November 2025 +- **Maintained by:** HTK Contributors diff --git a/apps/store/constants/README.md b/apps/store/constants/README.md new file mode 100644 index 00000000..f9d624eb --- /dev/null +++ b/apps/store/constants/README.md @@ -0,0 +1,48 @@ +# Store Constants + +## Overview + +This module defines configuration for the e-commerce store system, including product and collection model references. + +## Constants + +### Model References + +- **`HTK_STORE_PRODUCT_MODEL`** - Default: `None` - Product model (app_label.ModelName) +- **`HTK_STORE_PRODUCT_COLLECTION_MODEL`** - Default: `None` - Product collection/category model + +## Usage Examples + +### Configure Store Models + +```python +# In Django settings.py +HTK_STORE_PRODUCT_MODEL = 'store.Product' +HTK_STORE_PRODUCT_COLLECTION_MODEL = 'store.Collection' +``` + +### Load Models Dynamically + +```python +from django.apps import apps +from htk.apps.store.constants import ( + HTK_STORE_PRODUCT_MODEL, + HTK_STORE_PRODUCT_COLLECTION_MODEL, +) + +Product = apps.get_model(HTK_STORE_PRODUCT_MODEL) +Collection = apps.get_model(HTK_STORE_PRODUCT_COLLECTION_MODEL) + +products = Product.objects.all() +collections = Collection.objects.all() +``` + +### Use in Queries + +```python +from django.apps import apps +from htk.apps.store.constants import HTK_STORE_PRODUCT_MODEL + +Product = apps.get_model(HTK_STORE_PRODUCT_MODEL) +featured = Product.objects.filter(featured=True) +``` diff --git a/apps/tokens/README.md b/apps/tokens/README.md new file mode 100644 index 00000000..d8c43b9e --- /dev/null +++ b/apps/tokens/README.md @@ -0,0 +1,69 @@ +# Tokens App + +API token generation, validation, and management. + +## Quick Start + +```python +from htk.apps.tokens.models import AuthToken + +# Create token for user +token = AuthToken.objects.create( + user=user, + token_string='abcd1234efgh5678', + expiry_date=timezone.now() + timedelta(days=30) +) + +# Validate token +valid = AuthToken.objects.filter( + token_string=token_str, + user=user, + expiry_date__gt=timezone.now() +).exists() +``` + +## Common Patterns + +```python +# Token authentication in views +@require_http_methods(['POST']) +def api_endpoint(request): + token_str = request.headers.get('Authorization', '').replace('Bearer ', '') + + try: + token = AuthToken.objects.get( + token_string=token_str, + expiry_date__gt=timezone.now() + ) + user = token.user + except AuthToken.DoesNotExist: + return JsonResponse({'error': 'Invalid token'}, status=401) + + # Process request for authenticated user + return JsonResponse({'user_id': user.id}) +``` + +## Models + +- **`AuthToken`** - API token with expiration +- **`TokenMetadata`** - Store token metadata + +## Security + +```python +# Generate secure tokens +import secrets +token = secrets.token_urlsafe(32) + +# Hash before storing +import hashlib +token_hash = hashlib.sha256(token.encode()).hexdigest() +``` + +## Best Practices + +1. **Hash tokens** - Never store plaintext tokens +2. **Set expiration** - All tokens should expire +3. **Regenerate on compromise** - Allow users to revoke +4. **Log token usage** - Track API access +5. **Use HTTPS** - Tokens in transit must be encrypted diff --git a/apps/url_shortener/README.md b/apps/url_shortener/README.md new file mode 100644 index 00000000..3851bce3 --- /dev/null +++ b/apps/url_shortener/README.md @@ -0,0 +1,59 @@ +# URL Shortener App + +Create and manage short URLs with base62 encoding. + +## Quick Start + +```python +from htk.apps.url_shortener.models import HTKShortUrl + +# Create short URL +short = HTKShortUrl.objects.create( + url='https://example.com/very/long/url?param=value' +) + +code = short.code # e.g., 'a7f2' +# Share as: example.com/s/a7f2 + +# Get original URL +short = HTKShortUrl.objects.get(code='a7f2') +original = short.url +``` + +## Models + +- **`HTKShortUrl`** - Short URL with base62 encoded code + +## Common Patterns + +```python +# Generate short URL code +from htk.apps.url_shortener.utils import generate_short_url_code + +code = generate_short_url_code(short.id) + +# Get recently shortened +from htk.apps.url_shortener.utils import get_recently_shortened + +recent = get_recently_shortened(limit=10) + +# Resolve raw ID +from htk.apps.url_shortener.utils import resolve_raw_id + +raw_id = resolve_raw_id('a7f2') +``` + +## URL Pattern + +```python +# GET /s// +# Redirects to original URL +``` + +## Configuration + +```python +# settings.py +SHORT_URL_DOMAIN = 'short.example.com' +SHORT_URL_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +``` diff --git a/cache/README.md b/cache/README.md new file mode 100644 index 00000000..146cfed7 --- /dev/null +++ b/cache/README.md @@ -0,0 +1,131 @@ +# Caching Framework + +Utilities for caching objects and managing cache lifecycles. + +## Overview + +The `cache` module provides: + +- Abstract base classes for cacheable objects +- Locking mechanisms to prevent cache stampedes +- Configurable cache key generation +- Automatic cache invalidation via signals + +## Core Concepts + +### CacheableObject + +Base class for objects that should be cached: + +```python +from htk.cache.classes import CacheableObject + +class UserFollowingCache(CacheableObject): + def __init__(self, user): + self.user = user + + def get_cache_key_suffix(self): + return f'user_{self.user.id}_following' + + def get_cache_payload(self): + # Return what to cache + return list(self.user.following.values_list('id', flat=True)) + + def get_cache_duration(self): + # Cache for 1 hour + return 3600 +``` + +Usage: + +```python +cache = UserFollowingCache(user) +following_ids = cache.cache_get() # Gets from cache or recomputes +cache.invalidate_cache() # Clear cache when user follows someone +``` + +### LockableObject + +Prevent cache stampedes (multiple simultaneous expensive computations): + +```python +from htk.cache.classes import LockableObject + +class ExpensiveDataCache(LockableObject): + def get_lock_key_suffix(self): + return 'expensive_computation' + + def get_lock_duration(self): + # Hold lock for 30 seconds + return 30 + + def expensive_operation(self): + with self.lock(): # Blocks until lock is acquired + # Expensive computation + result = compute_something_expensive() + return result +``` + +## Common Patterns + +### Cache with Signal Invalidation + +Automatically invalidate cache when related objects change: + +```python +from django.db.models.signals import post_save +from django.dispatch import receiver + +@receiver(post_save, sender=UserFollowing) +def invalidate_follower_cache(sender, instance, **kwargs): + cache = UserFollowersCache(instance.user) + cache.invalidate_cache() +``` + +### Time-Based Cache Expiration + +Override `get_cache_duration()` to control TTL: + +```python +class HourlyCache(CacheableObject): + def get_cache_duration(self): + return 3600 # 1 hour + +class DailyCache(CacheableObject): + def get_cache_duration(self): + return 86400 # 1 day +``` + +### Custom Cache Key Generation + +Customize cache key structure: + +```python +class CustomKeyCache(CacheableObject): + def get_cache_key(self): + # Override for special key formatting + suffix = self.get_cache_key_suffix() + return f'custom:v2:{suffix}' +``` + +## Classes + +- **`CacheableObject`** - Abstract base for cacheable objects with automatic TTL +- **`LockableObject`** - Prevent concurrent computations (cache stampede prevention) +- **`CustomCacheScheme`** - Define custom cache behavior + +## Functions + +- **`cache_store`** - Store data in cache +- **`cache_get`** - Retrieve data from cache +- **`invalidate_cache`** - Clear cache for an object +- **`lock`/`unlock`** - Acquire/release locks for concurrent access +- **`acquire`/`release`** - Aliases for lock/unlock + +## Best Practices + +1. **Use meaningful cache key suffixes** - Include relevant object IDs +2. **Set appropriate TTLs** - Balance freshness vs. performance +3. **Use locks for expensive operations** - Prevent cache stampedes +4. **Invalidate on model changes** - Use Django signals +5. **Test cache effectiveness** - Monitor hit rates in production diff --git a/cache/constants/README.md b/cache/constants/README.md new file mode 100644 index 00000000..2f08045a --- /dev/null +++ b/cache/constants/README.md @@ -0,0 +1,32 @@ +# Cache Constants + +## Overview + +This module defines configuration constants for Django cache settings. + +## Configuration Settings + +```python +from htk.cache.constants import HTK_CACHE_KEY_PREFIX + +# Prefix prepended to all cache keys (default: 'htk') +HTK_CACHE_KEY_PREFIX = 'htk' +``` + +## Customization + +Override this setting in `settings.py`: + +```python +# Use a custom cache key prefix +HTK_CACHE_KEY_PREFIX = 'myapp' +``` + +## Usage + +Cache utilities throughout the codebase use this prefix to namespace cache keys: + +```python +# Cache key will be prefixed: 'htk:user:123:data' +cache_key = f"{HTK_CACHE_KEY_PREFIX}:user:123:data" +``` diff --git a/constants/README.md b/constants/README.md new file mode 100644 index 00000000..e12d52aa --- /dev/null +++ b/constants/README.md @@ -0,0 +1,92 @@ +# Constants + +## Overview + +This module provides comprehensive constants for global configuration, time/date calculations, geographic data, HTTP utilities, and more. + +## Key Constants + +### Geographic Data + +```python +from htk.constants import ( + US_STATES, US_STATE_CODES_LOOKUP, US_STATES_LOOKUP, + ALL_US_STATES_AND_TERRITORIES +) + +# Get state abbreviation +state_code = US_STATE_CODES_LOOKUP['California'] # 'CA' + +# Get state name from code +state_name = US_STATES_LOOKUP['NY'] # 'New York' + +# List of all states (dicts with name/code pairs) +all_states = US_STATES +``` + +### Time Constants + +```python +from htk.constants import ( + TIME_1_MINUTE_SECONDS, TIME_1_HOUR_SECONDS, TIME_1_DAY_SECONDS, + ISOWEEKDAY_MONDAY, ISOWEEKDAY_WEEKDAYS, ISOWEEKDAY_WEEKENDS +) + +# Use for delays and timeouts +timeout = 2 * TIME_1_HOUR_SECONDS # 7200 seconds + +# Check if weekday +if isoweekday in ISOWEEKDAY_WEEKDAYS: + print("Weekday") +``` + +### HTTP Status Codes + +```python +from htk.constants import HTTPStatus + +# Access HTTP status codes +if response.status == HTTPStatus.OK: + # response is 200 + pass +``` + +### Character and Text Constants + +```python +from htk.constants import ALPHABET_CAPS + +# Get list of capital letters +letters = ALPHABET_CAPS # ['A', 'B', 'C', ..., 'Z'] +``` + +### Configuration Settings + +```python +from htk.constants import ( + HTK_DEFAULT_DOMAIN, HTK_SITE_NAME, HTK_DEFAULT_TIMEZONE, + HTK_DEFAULT_COUNTRY, HTK_HANDLE_MAX_LENGTH +) + +HTK_DEFAULT_DOMAIN = 'hacktoolkit.com' +HTK_SITE_NAME = 'Hacktoolkit' +HTK_DEFAULT_TIMEZONE = 'America/Los_Angeles' +HTK_DEFAULT_COUNTRY = 'US' +HTK_HANDLE_MAX_LENGTH = 64 +``` + +## Subdirectories + +- **dns/**: DNS and TLD constants +- **emails/**: Email validation patterns and common handles +- **i18n/**: International constants (countries, currencies, languages, timezones) + +## Customization + +Override defaults in `settings.py`: + +```python +HTK_SITE_NAME = 'My Site' +HTK_DEFAULT_TIMEZONE = 'UTC' +HTK_DEFAULT_DOMAIN = 'example.com' +``` diff --git a/constants/dns/README.md b/constants/dns/README.md new file mode 100644 index 00000000..b8a1f2c4 --- /dev/null +++ b/constants/dns/README.md @@ -0,0 +1,31 @@ +# DNS Constants + +## Overview + +This module defines DNS and domain-related constants including common top-level domains (TLDs). + +## Constants + +### Common TLDs + +```python +from htk.constants.dns import COMMON_TLDS + +# List of commonly-used top-level domains +common_tlds = COMMON_TLDS +# ['com', 'net', 'gov', ...] +``` + +## Usage + +```python +from htk.constants.dns import COMMON_TLDS + +def validate_tld(domain): + """Check if domain uses a common TLD.""" + parts = domain.split('.') + if parts: + tld = parts[-1].lower() + return tld in COMMON_TLDS + return False +``` diff --git a/constants/emails/README.md b/constants/emails/README.md new file mode 100644 index 00000000..60fc5d47 --- /dev/null +++ b/constants/emails/README.md @@ -0,0 +1,75 @@ +# Email Constants + +## Overview + +This module provides email validation constants, patterns for detecting invalid emails, and common email handles. + +## Constants + +### Bad Email Detection + +```python +from htk.constants.emails import ALL_BAD_EMAILS, BAD_EMAIL_REGEXPS, LOCALHOST_EMAILS + +# Set of email addresses known to be invalid or problematic +bad_emails = ALL_BAD_EMAILS # includes 'root@localhost' + +# Compiled regex patterns for detecting invalid email patterns +bad_patterns = BAD_EMAIL_REGEXPS + +# Localhost email addresses that shouldn't be used +localhost = LOCALHOST_EMAILS # {'root@localhost'} +``` + +### Common Email Handles + +```python +from htk.constants.emails import COMMON_EMAIL_HANDLES + +# Common generic email prefixes used by organizations +handles = COMMON_EMAIL_HANDLES +# ['company', 'contact', 'hello', 'hi', 'info', 'me', 'support', 'team', ...] +``` + +### Email Pattern Permutations + +```python +from htk.constants.emails import EMAIL_PERMUTATION_PATTERNS + +# 66 email pattern variations for generating possible email addresses +# Patterns use template variables like {first}, {last}, {domain} +patterns = EMAIL_PERMUTATION_PATTERNS +``` + +## Usage Examples + +### Validate Email + +```python +from htk.constants.emails import ALL_BAD_EMAILS + +def is_valid_email(email): + """Check if email is not in the bad emails list.""" + return email not in ALL_BAD_EMAILS +``` + +### Generate Email Permutations + +```python +from htk.constants.emails import EMAIL_PERMUTATION_PATTERNS + +def generate_emails(first_name, last_name, domain): + """Generate possible email addresses using permutation patterns.""" + emails = [] + for pattern in EMAIL_PERMUTATION_PATTERNS: + try: + email = pattern.format( + first=first_name.lower(), + last=last_name.lower(), + domain=domain.lower() + ) + emails.append(email) + except (KeyError, AttributeError): + pass + return emails +``` diff --git a/constants/i18n/README.md b/constants/i18n/README.md new file mode 100644 index 00000000..723af07d --- /dev/null +++ b/constants/i18n/README.md @@ -0,0 +1,111 @@ +# Internationalization Constants + +## Overview + +This module provides constants for international applications including country data, currencies, languages, greetings, and timezones. + +## Constants + +### Countries + +```python +from htk.constants.i18n import COUNTRIES_EN_NAMES_MAP + +# Dictionary mapping country codes to English names +# e.g. {'US': 'United States', 'CA': 'Canada', 'GB': 'United Kingdom', ...} +countries = COUNTRIES_EN_NAMES_MAP +``` + +### Currencies + +```python +from htk.constants.i18n import CURRENCY_CODE_TO_SYMBOLS_MAP + +# Dictionary mapping currency codes to symbols +symbols = CURRENCY_CODE_TO_SYMBOLS_MAP +# {'USD': '$', 'EUR': '€', 'GBP': '£', ...} +``` + +### Languages + +```python +from htk.constants.i18n import LANGUAGE_EN_NAMES_MAP, LANGUAGE_NAMES_MAP + +# Language codes to English names +english_names = LANGUAGE_EN_NAMES_MAP +# {'de': 'German', 'en': 'English', 'es': 'Spanish', ...} + +# Language codes to native language names +native_names = LANGUAGE_NAMES_MAP +# {'de': 'Deutsch', 'en': 'English', 'es': 'Español', ...} +``` + +### Greetings + +```python +from htk.constants.i18n import I18N_GREETINGS + +# Greetings in multiple languages +# {'am': 'ሰላም', 'ar': 'مرحبا', 'en': 'Hello', 'es': 'Hola', ...} +greetings = I18N_GREETINGS +``` + +### Timezones + +```python +from htk.constants.i18n import US_TIMEZONE_CHOICES + +# Tuple of (IANA timezone, legacy US timezone) tuples +# Used for Django form choice fields +timezone_choices = US_TIMEZONE_CHOICES +# (('America/New_York', 'Eastern Time (US & Canada)'), ...) +``` + +### English Language Helpers + +```python +from htk.constants.i18n import SPECIAL_NOUN_PLURAL_FORMS + +# Dictionary mapping nouns to their plural forms (irregular plurals) +plurals = SPECIAL_NOUN_PLURAL_FORMS +# {'child': 'children', 'person': 'people', ...} +``` + +## Usage Examples + +### Get Country Name + +```python +from htk.constants.i18n import COUNTRIES_EN_NAMES_MAP + +country_code = 'US' +country_name = COUNTRIES_EN_NAMES_MAP.get(country_code) +# 'United States' +``` + +### Format Currency + +```python +from htk.constants.i18n import CURRENCY_CODE_TO_SYMBOLS_MAP + +def format_price(amount, currency_code): + """Format price with currency symbol.""" + symbol = CURRENCY_CODE_TO_SYMBOLS_MAP.get(currency_code, '') + return f"{symbol}{amount:.2f}" + +formatted = format_price(99.99, 'USD') +# '$99.99' +``` + +### Get Plural Form + +```python +from htk.constants.i18n import SPECIAL_NOUN_PLURAL_FORMS + +def pluralize(noun): + """Get plural form of noun.""" + return SPECIAL_NOUN_PLURAL_FORMS.get(noun, f"{noun}s") + +plural = pluralize('child') +# 'children' +``` diff --git a/decorators/README.md b/decorators/README.md new file mode 100644 index 00000000..e52dc7e2 --- /dev/null +++ b/decorators/README.md @@ -0,0 +1,172 @@ +# Decorators + +## Overview + +The `decorators` module provides: + +- Function call deprecation warnings +- Signal handler control for testing +- View decorators for SEO and REST patterns +- Rate limiting for instance methods +- URL resolution helpers + +## Function Deprecation + +Mark functions as deprecated to warn users: + +```python +from htk.decorators.classes import deprecated + +@deprecated(version='2.0', alternative='new_function') +def old_function(): + pass + +# Usage triggers deprecation warning +old_function() +# DeprecationWarning: old_function is deprecated (use new_function instead) [v2.0] +``` + +## Signal Handler Control + +Disable signal handlers during testing or data loading: + +```python +from htk.decorators.classes import disable_for_loaddata + +@disable_for_loaddata +def create_user_profile(sender, instance, created, **kwargs): + if created: + UserProfile.objects.create(user=instance) + +# This handler won't fire during `python manage.py loaddata` +``` + +**Use Cases:** +- Prevent signal handlers from executing during fixture loading +- Test models without side effects +- Bulk import operations + +## View Decorators + +### SEO Redirect for RESTful Objects + +Redirect to canonical SEO URL: + +```python +from htk.decorators.classes import restful_obj_seo_redirect + +@restful_obj_seo_redirect +def product_detail(request, product_id): + product = Product.objects.get(id=product_id) + if product.slug != request.resolver_match.kwargs.get('slug'): + # Redirects to canonical URL with correct slug + pass +``` + +### Resolve Records from REST URLs + +Extract objects from URL patterns: + +```python +from htk.decorators.classes import resolve_records_from_restful_url + +@resolve_records_from_restful_url +def organization_detail(request): + # Organization is automatically resolved from URL + org = request.resolved_records['organization'] + return render(request, 'org_detail.html', {'org': org}) +``` + +## Rate Limiting + +### Rate Limit Instance Methods + +Prevent method abuse with token bucket algorithm: + +```python +from htk.decorators.rate_limiters import rate_limit_instance_method + +class UserAPI: + @rate_limit_instance_method(max_calls=10, period=60) + def send_email(self, recipient, subject, body): + # Allow max 10 calls per 60 seconds + send_email_via_provider(recipient, subject, body) + +# Usage +api = UserAPI() +for i in range(15): + api.send_email('user@example.com', f'Message {i}', body) + # Raises RateLimitError after 10 calls in 60 seconds +``` + +**Features:** +- Token bucket algorithm +- Per-instance rate limits +- Configurable time windows +- Automatic reset + +## Common Patterns + +### Deprecating Old Methods + +```python +from htk.decorators.classes import deprecated + +class UserService: + @deprecated(version='2.0', alternative='create_user_v2') + def create_user(self, email, password): + return self.create_user_v2(email, password) + + def create_user_v2(self, email, password): + # New implementation + pass +``` + +### Safe Model Operations During Testing + +```python +from django.db.models.signals import post_save +from htk.decorators.classes import disable_for_loaddata + +@receiver(post_save, sender=User) +@disable_for_loaddata +def on_user_created(sender, instance, created, **kwargs): + if created: + # This won't run during loaddata/migrations + send_welcome_email(instance.email) +``` + +### API Rate Limiting + +```python +from htk.decorators.rate_limiters import rate_limit_instance_method + +class StripePaymentGateway: + @rate_limit_instance_method(max_calls=100, period=3600) + def charge_card(self, card_token, amount): + # Max 100 charges per hour + return stripe.Charge.create( + amount=amount, + currency='usd', + source=card_token + ) +``` + +## Classes + +- **`restful_obj_seo_redirect`** - Redirect to canonical SEO URL for REST objects +- **`resolve_records_from_restful_url`** - Automatically resolve objects from URL kwargs +- **`rate_limit_instance_method`** - Rate limit instance method calls + +## Functions + +- **`deprecated`** - Mark function as deprecated with version info +- **`disable_for_loaddata`** - Disable signal handler during fixture loading + +## Best Practices + +1. **Use deprecation warnings** for API changes +2. **Disable signals in tests** with `disable_for_loaddata` +3. **Rate limit external API calls** to prevent abuse +4. **Provide alternatives** when deprecating +5. **Document rate limits** in docstrings diff --git a/extensions/README.md b/extensions/README.md new file mode 100644 index 00000000..3f95a15c --- /dev/null +++ b/extensions/README.md @@ -0,0 +1,152 @@ +# Extensions + +## Overview + +The `extensions` module provides: + +- Ordered sets with O(1) operations +- Custom data structure implementations +- Performance-optimized collections + +## OrderedSet + +A set that maintains insertion order with O(1) operations: + +```python +from htk.extensions.data_structures.ordered_set import OrderedSet + +# Create ordered set +tags = OrderedSet(['python', 'django', 'web', 'python']) +# OrderedSet(['python', 'django', 'web']) # Duplicates removed, order preserved + +# All set operations work +tags.add('api') +tags.remove('web') + +# Maintains order +list(tags) # ['python', 'django', 'api'] + +# Check membership in O(1) +'django' in tags # True +``` + +**vs Python dict:** +```python +# OrderedSet is like an ordered set +tags = OrderedSet(['a', 'b', 'c']) + +# vs dict (Python 3.7+) +tags_dict = dict.fromkeys(['a', 'b', 'c']) + +# Functionally similar, but OrderedSet is optimized for set operations +``` + +## Use Cases + +### Tracking Unique Items + +```python +from htk.extensions.data_structures.ordered_set import OrderedSet + +# Track user's visited pages (maintaining order) +visited = OrderedSet() +visited.add('/home') +visited.add('/products') +visited.add('/home') # Won't be added again +visited.add('/checkout') + +# Show visit history +breadcrumbs = list(visited) # ['/home', '/products', '/checkout'] +``` + +### Removing Duplicates While Preserving Order + +```python +from htk.extensions.data_structures.ordered_set import OrderedSet + +# Clean list while preserving order +tags = ['python', 'web', 'python', 'django', 'web', 'rest'] +unique_tags = OrderedSet(tags) + +list(unique_tags) # ['python', 'web', 'django', 'rest'] +``` + +### Set Operations + +```python +from htk.extensions.data_structures.ordered_set import OrderedSet + +set_a = OrderedSet(['a', 'b', 'c']) +set_b = OrderedSet(['b', 'c', 'd']) + +# Union +union = set_a | set_b # OrderedSet(['a', 'b', 'c', 'd']) + +# Intersection +common = set_a & set_b # OrderedSet(['b', 'c']) + +# Difference +only_in_a = set_a - set_b # OrderedSet(['a']) +``` + +## Performance Characteristics + +| Operation | Time | Space | +|-----------|------|-------| +| Add | O(1) | O(n) | +| Remove | O(1) | O(n) | +| Contains | O(1) | | +| Iterate | O(n) | | +| Union | O(n+m) | O(n+m) | +| Intersection | O(min(n,m)) | | + +## Alternatives + +- **Python set**: Fast but unordered +- **Python list**: Ordered but slow membership tests +- **OrderedSet**: Ordered AND fast membership tests + +## Best Practices + +1. **Use when you need both ordering and uniqueness** +2. **Don't use if order doesn't matter** - use `set()` instead +3. **Don't use for very large datasets** - memory overhead +4. **Use for deduplication** - clean lists while preserving order + +## Common Patterns + +### Merge Multiple Lists + +```python +from htk.extensions.data_structures.ordered_set import OrderedSet + +list1 = [1, 2, 3] +list2 = [2, 3, 4] +list3 = [3, 4, 5] + +merged = OrderedSet() +merged.update(list1) +merged.update(list2) +merged.update(list3) + +result = list(merged) # [1, 2, 3, 4, 5] +``` + +### Unique Query Results + +```python +from django.db.models import Q +from htk.extensions.data_structures.ordered_set import OrderedSet + +# Get unique users from multiple queries +query1 = User.objects.filter(country='US') +query2 = User.objects.filter(premium=True) +query3 = User.objects.filter(created__year=2024) + +all_users = OrderedSet() +all_users.update(query1) +all_users.update(query2) +all_users.update(query3) + +# Avoid duplicates without complex Q queries +``` diff --git a/extensions/data_structures/README.md b/extensions/data_structures/README.md new file mode 100644 index 00000000..f21d576d --- /dev/null +++ b/extensions/data_structures/README.md @@ -0,0 +1,180 @@ +# Data Structures + +Extended data structures with optimized performance characteristics. + +## Quick Start + +```python +from htk.extensions.data_structures import OrderedSet + +# Create ordered set (maintains insertion order + uniqueness) +tags = OrderedSet(['python', 'django', 'web', 'python']) +# Result: OrderedSet(['python', 'django', 'web']) + +# All set operations +tags.add('api') +tags.remove('web') + +# Check membership in O(1) +'django' in tags # True +list(tags) # ['python', 'django', 'api'] +``` + +## OrderedSet + +A set that maintains insertion order with O(1) operations for all standard set operations. + +### Features + +```python +from htk.extensions.data_structures import OrderedSet + +# Create from list (removes duplicates, preserves order) +items = OrderedSet([3, 1, 4, 1, 5, 9, 2, 6, 5, 3]) +print(list(items)) # [3, 1, 4, 5, 9, 2, 6] + +# Add items +items.add(7) +items.add(3) # No duplicate (already exists) + +# Remove items +items.discard(4) # Won't raise if not found +items.remove(5) # Raises KeyError if not found + +# Set operations +set_a = OrderedSet(['a', 'b', 'c']) +set_b = OrderedSet(['b', 'c', 'd']) + +union = set_a | set_b # OrderedSet(['a', 'b', 'c', 'd']) +intersection = set_a & set_b # OrderedSet(['b', 'c']) +difference = set_a - set_b # OrderedSet(['a']) + +# Iterate in order +for item in items: + print(item) + +# Check membership +'a' in set_a # O(1) lookup time +``` + +### Performance Characteristics + +| Operation | Time | Notes | +|-----------|------|-------| +| Add | O(1) | Append to list + add to set | +| Remove | O(n) | Delete from list is O(n) | +| Contains (`in`) | O(1) | Set lookup | +| Iteration | O(n) | Maintains insertion order | +| Union | O(n+m) | Merge two ordered sets | +| Intersection | O(min(n,m)) | Common elements | + +### Common Patterns + +#### Deduplication While Preserving Order + +```python +from htk.extensions.data_structures import OrderedSet + +# Remove duplicates from list +results = [5, 2, 8, 2, 5, 1, 8, 3] +unique = OrderedSet(results) +print(list(unique)) # [5, 2, 8, 1, 3] + +# vs Python set (order lost) +print(set(results)) # {1, 2, 3, 5, 8} - unordered +``` + +#### Track User Interactions + +```python +from htk.extensions.data_structures import OrderedSet + +class UserSession: + def __init__(self, user): + self.user = user + self.visited_pages = OrderedSet() + + def visit_page(self, page_url): + self.visited_pages.add(page_url) + + def get_breadcrumbs(self): + return list(self.visited_pages) + +# Usage +session = UserSession(user) +session.visit_page('/home') +session.visit_page('/products') +session.visit_page('/home') # Won't duplicate +session.visit_page('/checkout') + +breadcrumbs = session.get_breadcrumbs() +# ['/home', '/products', '/checkout'] +``` + +#### Merge Multiple Lists + +```python +from htk.extensions.data_structures import OrderedSet + +# Combine multiple lists without duplicates +recommended = [1, 2, 3, 4, 5] +wishlist = [3, 4, 5, 6, 7] +trending = [5, 8, 9] + +merged = OrderedSet() +merged.update(recommended) +merged.update(wishlist) +merged.update(trending) + +items = list(merged) # [1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +#### Query Results Deduplication + +```python +from htk.extensions.data_structures import OrderedSet +from django.db.models import Q + +# Get unique items from multiple queries +recent_users = User.objects.filter(created__gte=today).order_by('-created') +premium_users = User.objects.filter(premium=True) +search_results = User.objects.filter(Q(username__icontains=query) | Q(email__icontains=query)) + +all_users = OrderedSet() +for user in recent_users: + all_users.add(user) +for user in premium_users: + all_users.add(user) +for user in search_results: + all_users.add(user) + +result_list = list(all_users) # No duplicates, maintains order +``` + +## Use Cases + +**Use OrderedSet when you need:** +- Uniqueness (no duplicates) +- Order preservation (insertion order) +- Fast membership testing (O(1) lookup) + +**Don't use OrderedSet if:** +- Order doesn't matter → use `set()` +- You need fast removal → use `list` + `set` hybrid +- Very large datasets → memory overhead may be high + +## Alternatives + +| Structure | Order | Unique | Lookup | Notes | +|-----------|-------|--------|--------|-------| +| `set()` | ✗ | ✓ | O(1) | Fast, unordered | +| `list` | ✓ | ✗ | O(n) | Ordered, slow lookup | +| `dict.keys()` | ✓ | ✓ | O(1) | Python 3.7+, ordered | +| `OrderedSet` | ✓ | ✓ | O(1) | Best of both worlds | + +## Best Practices + +1. **Use for deduplication** - Clean lists while preserving order +2. **Avoid for large sets** - Memory overhead is 2x+ vs regular set +3. **Set operations are powerful** - Union, intersection, difference +4. **Don't rely on order across versions** - Implementation details may change diff --git a/forms/README.md b/forms/README.md new file mode 100644 index 00000000..9e7f112d --- /dev/null +++ b/forms/README.md @@ -0,0 +1,166 @@ +# Form Utilities + +Reusable base form classes and utilities for common validation patterns. + +## Overview + +The `forms` module provides: + +- Abstract base form classes for common patterns +- Form utilities for field manipulation and error handling +- Integration with model forms +- Helper functions for form processing + +## Base Form Classes + +### AbstractModelInstanceUpdateForm + +Base class for forms that update model instances: + +```python +from htk.forms.classes import AbstractModelInstanceUpdateForm + +class UserProfileForm(AbstractModelInstanceUpdateForm): + class Meta: + model = UserProfile + fields = ['bio', 'website', 'location'] +``` + +**Features:** +- Automatic model instance update +- Consistent error handling +- Field validation and cleaning + +## Form Utilities + +### Field Input Attributes + +Set attributes on form input fields: + +```python +from htk.forms.utils import set_input_attrs, set_input_placeholder_labels + +# Set custom attributes +set_input_attrs(form, {'class': 'form-control', 'data-type': 'email'}) + +# Auto-populate placeholder with label +set_input_placeholder_labels(form) +``` + +### Error Handling + +```python +from htk.forms.utils import get_form_errors, get_form_error + +# Get all errors +all_errors = get_form_errors(form) # List of all error messages + +# Get first error +first_error = get_form_error(form) # Single error string +``` + +### Model Instance Validation + +```python +from htk.forms.utils import clean_model_instance_field + +# Validate a field against model constraints +value = clean_model_instance_field(model_instance, field_name, value) +``` + +## Common Patterns + +### Custom Form with Validation + +```python +from django import forms +from htk.forms.classes import AbstractModelInstanceUpdateForm + +class RegistrationForm(AbstractModelInstanceUpdateForm): + password = forms.CharField(widget=forms.PasswordInput) + password_confirm = forms.CharField(widget=forms.PasswordInput) + + class Meta: + model = User + fields = ['username', 'email', 'password'] + + def clean(self): + cleaned_data = super().clean() + password = cleaned_data.get('password') + password_confirm = cleaned_data.get('password_confirm') + + if password != password_confirm: + raise forms.ValidationError('Passwords do not match') + + return cleaned_data + + def save(self, commit=True): + user = super().save(commit=False) + user.set_password(self.cleaned_data['password']) + if commit: + user.save() + return user +``` + +### API Form Handling + +```python +from django.http import JsonResponse +from htk.api.utils import json_response_form_error + +def api_update_user(request): + form = UserUpdateForm(request.POST, instance=request.user) + if form.is_valid(): + form.save() + return JsonResponse({'success': True}) + else: + return json_response_form_error(form) +``` + +### Dynamic Field Rendering + +```python +from htk.forms.utils import set_input_attrs + +def render_form(form, css_class='form-control'): + set_input_attrs(form, {'class': css_class}) + set_input_placeholder_labels(form) + return form +``` + +## Integration with Models + +### Inherit from Abstract Models + +```python +from htk.apps.accounts.models import BaseAbstractUserProfile + +class CustomUserProfile(BaseAbstractUserProfile): + department = models.CharField(max_length=100) + +class UserProfileForm(AbstractModelInstanceUpdateForm): + class Meta: + model = CustomUserProfile + fields = ['bio', 'website', 'department'] +``` + +## Best Practices + +1. **Extend AbstractModelInstanceUpdateForm** for model-related forms +2. **Use field validators** for single field validation +3. **Override clean()** for cross-field validation +4. **Set input attrs early** in form initialization +5. **Return JSON errors** in API views with `json_response_form_error` + +## Classes + +- **`AbstractModelInstanceUpdateForm`** - Base class for model update forms + +## Functions + +- **`save`** - Save form data to model instance +- **`clean_model_instance_field`** - Validate field against model +- **`set_input_attrs`** - Set HTML attributes on form fields +- **`set_input_placeholder_labels`** - Auto-populate field placeholders +- **`get_form_errors`** - Get all form errors as list +- **`get_form_error`** - Get first form error as string diff --git a/forms/constants/README.md b/forms/constants/README.md new file mode 100644 index 00000000..a6faa419 --- /dev/null +++ b/forms/constants/README.md @@ -0,0 +1,91 @@ +# Forms Constants + +## Overview + +This module contains configuration values and constants used throughout the forms system, including widget styling mappings and input type definitions. + +## Constants + +### Widget Class Mappings + +```python +from htk.forms.constants import HTK_FORM_WIDGET_CLASSES + +# Maps form style names to widget-specific CSS classes +# Supports 'bootstrap' and 'pure' CSS frameworks +HTK_FORM_WIDGET_CLASSES = { + 'bootstrap': { + 'default': 'form-control', + 'TextInput': 'form-control', + 'Textarea': 'form-control', + # ... other widget types + }, + 'pure': { + 'default': 'pure-input-1', + 'TextInput': 'pure-input-1', + 'Textarea': 'pure-input-1', + # ... other widget types + } +} +``` + +### Configuration Settings + +```python +from htk.forms.constants import HTK_FORM_STYLE, HTK_DEFAULT_FORM_INPUT_CLASS + +# Global form styling framework ('bootstrap' or 'pure') +HTK_FORM_STYLE = 'bootstrap' + +# Default CSS class for form inputs (used when explicit class not specified) +HTK_DEFAULT_FORM_INPUT_CLASS = 'pure-input-1' +``` + +### Text Input Types + +```python +from htk.forms.constants import TEXT_STYLE_INPUTS + +# Tuple of Django form widget types that render as text-style inputs +TEXT_STYLE_INPUTS = ('EmailInput', 'NumberInput', 'PasswordInput', 'TextInput', 'Textarea', 'URLInput') +``` + +## Usage Examples + +### Get CSS Class for Widget Type + +```python +from htk.forms.constants import HTK_FORM_WIDGET_CLASSES, HTK_FORM_STYLE + +style = HTK_FORM_STYLE +widget_type = 'TextInput' + +css_class = HTK_FORM_WIDGET_CLASSES[style].get(widget_type, HTK_FORM_WIDGET_CLASSES[style]['default']) +# css_class = 'form-control' (if style is 'bootstrap') +``` + +### Check if Input Type is Text-Based + +```python +from htk.forms.constants import TEXT_STYLE_INPUTS + +widget_class_name = 'EmailInput' +is_text_input = widget_class_name in TEXT_STYLE_INPUTS +``` + +## Customization + +Override these settings in `settings.py`: + +```python +# Use Pure CSS instead of Bootstrap +HTK_FORM_STYLE = 'pure' + +# Custom widget class mappings +HTK_FORM_WIDGET_CLASSES = { + 'custom': { + 'default': 'my-form-control', + 'TextInput': 'my-text-input', + } +} +``` diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 00000000..b1772772 --- /dev/null +++ b/lib/README.md @@ -0,0 +1,301 @@ +# Third-Party Integrations + +Ready-to-use connectors for 45+ external services and APIs. + +## Overview + +The `lib` module provides integration adapters for: + +- **Cloud Services** - AWS, Google Cloud, Azure +- **Payment & Billing** - Stripe, Zuora, PayPal +- **Communication** - Slack, Discord, Twilio, Gmail +- **Data & CRM** - Airtable, Salesforce, Hubspot +- **Commerce** - Shopify, Stripe +- **Maps & Location** - Google Maps, Mapbox, Zillow +- **Analytics & Events** - Iterable, Mixpanel +- **Business Services** - Indeed, ZipRecruiter, Yelp + +## Payment & Billing + +### Stripe +Full Stripe integration for payments, subscriptions, and invoicing: + +```python +from htk.lib.stripe_lib.models import BaseStripeCustomer +from htk.lib.stripe_lib.utils import charge_card + +# Create customer +customer = charge_card(user, amount, stripe_token) + +# Handle subscriptions +subscription = customer.create_subscription(plan_id) +customer.change_subscription_plan(subscription_id, new_plan) +``` + +**Classes:** `BaseStripeCustomer`, `BaseStripeSubscription`, `BaseStripePlan` + +### Zuora +Subscription and billing management: + +```python +from htk.lib.zuora.api import get_subscription, update_subscription + +subscription = get_subscription(subscription_id) +update_subscription(subscription_id, new_params) +``` + +## Communication + +### Slack +Send messages, handle webhooks, and integrate with Slack: + +```python +from htk.lib.slack.utils import webhook_call + +webhook_call({'text': 'Hello from HTK!', 'channel': '#notifications'}) +``` + +**Features:** +- Webhook event handling +- Message posting +- Event handlers for various Slack events +- Beacon/location tracking + +### Discord +Discord webhook integration: + +```python +from htk.lib.discord.views import discord_webhook_relay_view +``` + +### Gmail +Interact with Gmail API: + +```python +from htk.lib.google.gmail.api import GmailAPI + +gmail = GmailAPI() +messages = gmail.messages_list() +``` + +### Twilio / Plivo +SMS and messaging: + +```python +from htk.lib.plivo.utils import handle_message_event +``` + +## Cloud Storage + +### AWS S3 +Store and retrieve files from S3: + +```python +from htk.lib.aws.s3.utils import S3Manager + +s3 = S3Manager() +s3.put_file('bucket', 'key', file_obj) +s3.get_url('bucket', 'key') +``` + +**Classes:** `S3Manager`, `S3MediaAsset` + +### Google Cloud +Cloud services via Google APIs: + +```python +from htk.lib.google.sheets.api import spreadsheets_values_append +from htk.lib.google.translate.utils import translate + +translate('Hello', 'en', 'es') +``` + +## Maps & Location + +### Google Maps +Google Maps API utilities: + +```python +from htk.lib.google.maps.utils import get_map_url_for_geolocation +from htk.lib.google.geocode.api import geocode + +map_url = get_map_url_for_geolocation(latitude, longitude) +``` + +### Mapbox +Mapbox geolocation and mapping: + +```python +from htk.lib.mapbox.geocode import reverse_geocode + +address = reverse_geocode(latitude, longitude) +``` + +### Zillow / Redfin +Real estate data: + +```python +from htk.lib.zillow.utils import get_zestimate +from htk.lib.redfin.api import get_avm + +zestimate = get_zestimate(zpid) +avm = get_avm(property_id) +``` + +## E-commerce & Payments + +### Shopify +Shopify API integration: + +```python +from htk.lib.shopify_lib.api import iter_products, iter_orders + +for product in iter_products(): + print(product.name) +``` + +**Classes:** `ShopifyProduct`, `ShopifyOrder`, `ShopifyCustomer` + +### Airtable +Airtable API for spreadsheet-like data: + +```python +from htk.lib.airtable.api import AirtableAPI + +api = AirtableAPI() +records = api.fetch_records('table_name') +``` + +## Data & CRM + +### Full Contact +Person lookup and data enrichment: + +```python +from htk.lib.fullcontact.utils import find_person_by_email + +person = find_person_by_email('user@example.com') +``` + +### Indeed +Job posting and applicant tracking: + +```python +from htk.lib.indeed.api.job_sync import IndeedJobSyncAPI + +api = IndeedJobSyncAPI() +api.create_job(job_data) +``` + +### ZipRecruiter +Job posting platform: + +```python +from htk.lib.ziprecruiter.api import ZipRecruiterAPI + +api = ZipRecruiterAPI() +``` + +## Analytics & Events + +### Iterable +Email and SMS marketing automation: + +```python +from htk.lib.iterable.utils import get_iterable_api_client + +client = get_iterable_api_client() +client.track_event(user_id, event_name, data) +``` + +## Search & Enrichment + +### Yelp +Business search and reviews: + +```python +from htk.lib.yelp.api import business_lookup + +business = business_lookup('business_name', location) +``` + +### GitHub +GitHub API integration: + +```python +from htk.lib.github.utils import get_repository, sync_repository_releases + +repo = get_repository('owner/repo') +sync_repository_releases(repo) +``` + +## Utilities & Helpers + +### QR Codes +Generate QR codes: + +```python +from htk.lib.qrcode.utils import qrcode_image_response + +return qrcode_image_response('https://example.com') +``` + +### Weather +Weather data: + +```python +from htk.lib.darksky.utils import generate_weather_report +``` + +### Geolocation +IP-based location lookup: + +```python +from htk.lib.geoip.utils import get_country_code_by_ip, get_timezone_by_ip + +country = get_country_code_by_ip('8.8.8.8') +``` + +### OpenAI +Chat completions and AI: + +```python +from htk.lib.openai.adapter import chat_completion + +response = chat_completion(messages) +``` + +## Integration Patterns + +### Authentication +Most integrations require API keys in settings: + +```python +# settings.py +STRIPE_API_KEY = os.environ.get('STRIPE_API_KEY') +SLACK_WEBHOOK_URL = os.environ.get('SLACK_WEBHOOK_URL') +AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID') +``` + +### Error Handling +Use safe wrappers for API calls: + +```python +from htk.lib.stripe_lib.utils import safe_stripe_call + +try: + result = safe_stripe_call(lambda: stripe.Charge.create(...)) +except Exception as e: + log.error(f"Stripe error: {e}") +``` + +## Quick Reference by Use Case + +**Need to charge a card?** → Stripe +**Building a marketplace?** → Stripe + Airtable +**Real-time notifications?** → Slack +**Location features?** → Google Maps + Mapbox +**Email marketing?** → Iterable +**Job postings?** → Indeed + ZipRecruiter +**Data enrichment?** → FullContact +**E-commerce?** → Shopify diff --git a/lib/airtable/README.md b/lib/airtable/README.md new file mode 100644 index 00000000..b5192f90 --- /dev/null +++ b/lib/airtable/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.airtable.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_AIRTABLE_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_AIRTABLE_API_KEY = 'your_api_key' +HTK_AIRTABLE_API_SECRET = 'your_secret' +HTK_AIRTABLE_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_AIRTABLE_ENABLED = True +HTK_AIRTABLE_API_KEY = 'your_key' +HTK_AIRTABLE_TIMEOUT = 30 +HTK_AIRTABLE_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/alexa/README.md b/lib/alexa/README.md new file mode 100644 index 00000000..aa3124f9 --- /dev/null +++ b/lib/alexa/README.md @@ -0,0 +1,42 @@ +# Alexa Integration + +Alexa skill webhook events and request handling. + +## Quick Start + +```python +from htk.lib.alexa.views import alexa_skill_webhook_view + +# Webhook endpoint automatically handles Alexa events +# POST /alexa/webhook/ +``` + +## Event Handling + +```python +from htk.lib.alexa.utils import handle_event, get_event_type + +# Validate and process Alexa webhook event +event = request.json +if is_valid_alexa_skill_webhook_event(event): + event_type = get_event_type(event) + handler = get_event_handler_for_type(event_type) + response = handle_event(event) +``` + +## Custom Handlers + +```python +from htk.lib.alexa.event_handlers import default, launch + +# Built-in handlers: launch (skill opened), default (generic handler) +# zesty handler available for custom implementations +``` + +## Configuration + +```python +# settings.py +ALEXA_SKILL_ID = os.environ.get('ALEXA_SKILL_ID') +ALEXA_WEBHOOK_PATH = 'alexa/webhook/' +``` diff --git a/lib/alexa/constants/README.md b/lib/alexa/constants/README.md new file mode 100644 index 00000000..77567fb4 --- /dev/null +++ b/lib/alexa/constants/README.md @@ -0,0 +1,91 @@ +# Alexa Constants + +Configuration and constants for Amazon Alexa skill integration. + +## Configuration Settings + +```python +from htk.lib.alexa.constants import ( + HTK_ALEXA_SKILL_EVENT_TYPE_RESOLVER, + HTK_ALEXA_SKILL_EVENT_HANDLERS, + HTK_ALEXA_SKILL_EVENT_HANDLERS_EXTRAS, +) +``` + +### Event Processing + +**HTK_ALEXA_SKILL_EVENT_TYPE_RESOLVER** +- Path to function that resolves event types from Alexa requests +- Default: `'htk.lib.alexa.event_resolvers.default_event_type_resolver'` +- This function determines how to route incoming Alexa events + +**HTK_ALEXA_SKILL_EVENT_HANDLERS** +- Dictionary mapping event types to handler function paths +- Built-in handlers: + - `'launch'`: Launch event handler + - `'default'`: Default fallback handler + - `'ZestyLunchIntent'`: Intent-specific handler example +- Default handlers located in `htk.lib.alexa.event_handlers` + +**HTK_ALEXA_SKILL_EVENT_HANDLERS_EXTRAS** +- Additional custom event handlers for specific intents/events +- Default: `{}` (empty, add custom handlers here) +- Merged with default handlers at runtime + +## General Constants + +```python +from htk.lib.alexa.constants import ( + ALEXA_SKILL_WEBHOOK_PARAMS, + PERSONAL_ASSISTANT_PHRASES, +) +``` + +### Webhook Parameters + +**ALEXA_SKILL_WEBHOOK_PARAMS** +- Tuple of required parameters for Alexa skill webhook validation +- Contains: `'session'`, `'request'`, `'version'` +- Used to validate incoming request structure + +### Response Phrases + +**PERSONAL_ASSISTANT_PHRASES** +- Dictionary of phrase categories with lists of response options +- Categories can be selected based on context/intent +- Example category `'ready'`: + - "I'm ready to serve." + - "Your orders are my commands." + - "What would you like me to help with?" + +## Example Usage + +```python +from htk.lib.alexa.constants import ( + HTK_ALEXA_SKILL_EVENT_HANDLERS, + PERSONAL_ASSISTANT_PHRASES, +) + +# Get handler for an event type +handler_path = HTK_ALEXA_SKILL_EVENT_HANDLERS.get('launch') + +# Get a ready response phrase +ready_phrases = PERSONAL_ASSISTANT_PHRASES['ready'] +response = random.choice(ready_phrases) +``` + +## Configuration in settings.py + +```python +HTK_ALEXA_SKILL_EVENT_TYPE_RESOLVER = 'htk.lib.alexa.event_resolvers.default_event_type_resolver' + +HTK_ALEXA_SKILL_EVENT_HANDLERS = { + 'launch': 'htk.lib.alexa.event_handlers.launch', + 'default': 'htk.lib.alexa.event_handlers.default', + 'ZestyLunchIntent': 'htk.lib.alexa.event_handlers.zesty', +} + +HTK_ALEXA_SKILL_EVENT_HANDLERS_EXTRAS = { + 'CustomIntent': 'myapp.alexa_handlers.custom_intent', +} +``` diff --git a/lib/amazon/README.md b/lib/amazon/README.md new file mode 100644 index 00000000..639372a5 --- /dev/null +++ b/lib/amazon/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.amazon.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_AMAZON_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_AMAZON_API_KEY = 'your_api_key' +HTK_AMAZON_API_SECRET = 'your_secret' +HTK_AMAZON_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_AMAZON_ENABLED = True +HTK_AMAZON_API_KEY = 'your_key' +HTK_AMAZON_TIMEOUT = 30 +HTK_AMAZON_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/awesomebible/README.md b/lib/awesomebible/README.md new file mode 100644 index 00000000..8faca0fa --- /dev/null +++ b/lib/awesomebible/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.awesomebible.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_AWESOMEBIBLE_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_AWESOMEBIBLE_API_KEY = 'your_api_key' +HTK_AWESOMEBIBLE_API_SECRET = 'your_secret' +HTK_AWESOMEBIBLE_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_AWESOMEBIBLE_ENABLED = True +HTK_AWESOMEBIBLE_API_KEY = 'your_key' +HTK_AWESOMEBIBLE_TIMEOUT = 30 +HTK_AWESOMEBIBLE_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/aws/README.md b/lib/aws/README.md new file mode 100644 index 00000000..7a5385e5 --- /dev/null +++ b/lib/aws/README.md @@ -0,0 +1,64 @@ +# AWS Integration + +Amazon Web Services - S3, CloudFront, EC2, and more. + +## S3 Storage + +```python +from htk.lib.aws.s3.utils import S3Manager + +s3 = S3Manager() + +# Upload file +s3.put_file('my-bucket', 'path/file.jpg', file_obj) + +# Get file URL +url = s3.get_url('my-bucket', 'path/file.jpg') + +# Copy file +s3.copy_file('src-bucket', 'src-key', 'dest-bucket', 'dest-key') + +# Delete file +s3.delete_file('my-bucket', 'path/file.jpg') +``` + +## S3 Media Assets + +```python +from htk.lib.aws.s3.models import BaseS3MediaAsset + +# Create media asset +asset = BaseS3MediaAsset.objects.create( + s3_bucket='my-bucket', + s3_key='images/photo.jpg' +) + +# Store file +asset.store_file(file_obj) + +# Get URL +url = asset.get_url() + +# Clone asset +cloned = asset.clone() +``` + +## Configuration + +```python +# settings.py +AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID') +AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY') +AWS_S3_REGION_NAME = 'us-west-2' +AWS_S3_BUCKET_NAME = 'my-bucket' +``` + +## Caching + +```python +from htk.lib.aws.s3.cachekeys import S3UrlCache + +# URLs are cached for performance +cache = S3UrlCache('bucket', 'key') +url = cache.cache_get() +``` diff --git a/lib/aws/s3/README.md b/lib/aws/s3/README.md new file mode 100644 index 00000000..f09bcb1f --- /dev/null +++ b/lib/aws/s3/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.aws.s3.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_S3_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_S3_API_KEY = 'your_api_key' +HTK_S3_API_SECRET = 'your_secret' +HTK_S3_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_S3_ENABLED = True +HTK_S3_API_KEY = 'your_key' +HTK_S3_TIMEOUT = 30 +HTK_S3_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Dont exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/darksky/README.md b/lib/darksky/README.md new file mode 100644 index 00000000..0466c6d2 --- /dev/null +++ b/lib/darksky/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.darksky.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_DARKSKY_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_DARKSKY_API_KEY = 'your_api_key' +HTK_DARKSKY_API_SECRET = 'your_secret' +HTK_DARKSKY_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_DARKSKY_ENABLED = True +HTK_DARKSKY_API_KEY = 'your_key' +HTK_DARKSKY_TIMEOUT = 30 +HTK_DARKSKY_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/discord/README.md b/lib/discord/README.md new file mode 100644 index 00000000..504b2a1b --- /dev/null +++ b/lib/discord/README.md @@ -0,0 +1,27 @@ +# Discord Integration + +Discord webhook and event handling. + +## Quick Start + +```python +from htk.lib.discord.views import discord_webhook_relay_view + +# Webhook endpoint for Discord events +# POST /discord/webhook/ +``` + +## Webhook Relay + +```python +# Discord sends webhook events to your endpoint +# Automatically handles and processes events +``` + +## Configuration + +```python +# settings.py +DISCORD_WEBHOOK_URL = os.environ.get('DISCORD_WEBHOOK_URL') +DISCORD_BOT_TOKEN = os.environ.get('DISCORD_BOT_TOKEN') +``` diff --git a/lib/discord/constants/README.md b/lib/discord/constants/README.md new file mode 100644 index 00000000..d427cfe8 --- /dev/null +++ b/lib/discord/constants/README.md @@ -0,0 +1,57 @@ +# Discord Constants + +Constants for Discord webhook integration and message relay. + +## Webhook Constants + +```python +from htk.lib.discord.constants import ( + DISCORD_WEBHOOK_URL, + DISCORD_WEBHOOK_RELAY_PARAMS, +) +``` + +**DISCORD_WEBHOOK_URL** +- Template URL format for Discord webhook endpoints +- Format: `'https://discord.com/api/webhooks/{webhook_id}/{webhook_token}'` +- Replace `{webhook_id}` and `{webhook_token}` with your webhook credentials + +**DISCORD_WEBHOOK_RELAY_PARAMS** +- List of parameters required for relaying webhook messages +- Parameters: + - `'webhook_id'`: Discord webhook ID + - `'webhook_token'`: Discord webhook token + - `'content'`: Message content to send +- Used for webhook message relay and routing + +## Example Usage + +```python +from htk.lib.discord.constants import ( + DISCORD_WEBHOOK_URL, + DISCORD_WEBHOOK_RELAY_PARAMS, +) + +# Build webhook URL +webhook_id = 'your_webhook_id' +webhook_token = 'your_webhook_token' +url = DISCORD_WEBHOOK_URL.format( + webhook_id=webhook_id, + webhook_token=webhook_token +) + +# Send message using params +payload = { + 'webhook_id': webhook_id, + 'webhook_token': webhook_token, + 'content': 'Hello from HTK!' +} +``` + +## Getting Discord Webhook Credentials + +1. Go to your Discord server settings +2. Navigate to "Webhooks" section +3. Create or select a webhook +4. Copy the Webhook URL which contains both ID and token +5. Store webhook_id and webhook_token in settings diff --git a/lib/dynamic_screening_solutions/README.md b/lib/dynamic_screening_solutions/README.md new file mode 100644 index 00000000..44c3dcb0 --- /dev/null +++ b/lib/dynamic_screening_solutions/README.md @@ -0,0 +1,43 @@ +# Dynamic Screening Solutions Integration + +321Forms API for employee screening and onboarding. + +## Quick Start + +```python +from htk.lib.dynamic_screening_solutions.api import Htk321FormsAPI + +api = Htk321FormsAPI() + +# Get companies +companies = api.get_companies() + +# Get employees by company +employees = api.get_users_by_company(company_id, user_type='employee') + +# Get onboarded employees +onboarded = api.get_onboarded_employee_users_by_company(company_id) +``` + +## Operations + +```python +# Get forms and divisions +forms = api.get_forms_by_company(company_id) +divisions = api.get_divisions_by_company(company_id) + +# Get form responses +form_data = api.get_form_by_company(company_id, form_id) +user_responses = api.get_responses_by_user(user_id) + +# Validate webhook +api.validate_webhook_request(request) +``` + +## Configuration + +```python +# settings.py +DSS_API_USERNAME = os.environ.get('DSS_API_USERNAME') +DSS_API_PASSWORD = os.environ.get('DSS_API_PASSWORD') +``` diff --git a/lib/dynamic_screening_solutions/constants/README.md b/lib/dynamic_screening_solutions/constants/README.md new file mode 100644 index 00000000..e51144b7 --- /dev/null +++ b/lib/dynamic_screening_solutions/constants/README.md @@ -0,0 +1,69 @@ +# Dynamic Screening Solutions (321forms) Constants + +Configuration constants and API endpoints for 321forms HR screening integration. + +## Configuration Settings + +```python +from htk.lib.dynamic_screening_solutions.constants import ( + HTK_321FORMS_USERNAME, + HTK_321FORMS_SECRET, + HTK_321FORMS_ENTRY_POINT_URL, + HTK_321FORMS_WEBHOOK_HASH_KEY_RETRIEVER, + HTK_321FORMS_WEBHOOK_EVENT_HANDLERS, +) +``` + +## API Configuration + +```python +# settings.py +HTK_321FORMS_USERNAME = 'your-username' +HTK_321FORMS_SECRET = 'your-api-secret' +HTK_321FORMS_ENTRY_POINT_URL = 'https://api.321forms.com/v1/' +``` + +## Webhook Configuration + +```python +# Webhook event handlers +HTK_321FORMS_WEBHOOK_EVENT_HANDLERS = { + 'all_forms_submitted': 'your.module.handlers.all_forms_submitted', + 'custom_event': 'your.module.handlers.custom_event', + 'form_approved': 'your.module.handlers.form_approved', + 'form_submitted': 'your.module.handlers.form_submitted', + 'onboarding_complete': 'your.module.handlers.onboarding_complete', + 'status_change': 'your.module.handlers.status_change', +} +``` + +## API Resources + +```python +from htk.lib.dynamic_screening_solutions.constants import ( + DSS_321FORMS_API_RESOURCE_USER, + DSS_321FORMS_API_RESOURCE_USER_FORMS, + DSS_321FORMS_API_RESOURCE_COMPANY_USERS, +) + +# User resources +user_url = DSS_321FORMS_API_RESOURCE_USER % {'user_id': '123'} +user_forms_url = DSS_321FORMS_API_RESOURCE_USER_FORMS % {'user_id': '123'} + +# Company resources +company_users_url = DSS_321FORMS_API_RESOURCE_COMPANY_USERS % { + 'company_id': '456', + 'user_type': 'employee', +} +``` + +## User Types + +```python +from htk.lib.dynamic_screening_solutions.constants import ( + DSS_321FORMS_API_USER_TYPE_HR_STAFF, + DSS_321FORMS_API_USER_TYPE_HR_ADMIN, + DSS_321FORMS_API_USER_TYPE_EMPLOYEE, + DSS_321FORMS_API_USER_TYPE_EMPLOYEE_COMPLETE, +) +``` diff --git a/lib/egauge/README.md b/lib/egauge/README.md new file mode 100644 index 00000000..166665b4 --- /dev/null +++ b/lib/egauge/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.egauge.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_EGAUGE_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_EGAUGE_API_KEY = 'your_api_key' +HTK_EGAUGE_API_SECRET = 'your_secret' +HTK_EGAUGE_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_EGAUGE_ENABLED = True +HTK_EGAUGE_API_KEY = 'your_key' +HTK_EGAUGE_TIMEOUT = 30 +HTK_EGAUGE_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/esv/README.md b/lib/esv/README.md new file mode 100644 index 00000000..280974ac --- /dev/null +++ b/lib/esv/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.esv.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_ESV_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_ESV_API_KEY = 'your_api_key' +HTK_ESV_API_SECRET = 'your_secret' +HTK_ESV_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_ESV_ENABLED = True +HTK_ESV_API_KEY = 'your_key' +HTK_ESV_TIMEOUT = 30 +HTK_ESV_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/facebook/README.md b/lib/facebook/README.md new file mode 100644 index 00000000..69d29ed3 --- /dev/null +++ b/lib/facebook/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.facebook.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_FACEBOOK_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_FACEBOOK_API_KEY = 'your_api_key' +HTK_FACEBOOK_API_SECRET = 'your_secret' +HTK_FACEBOOK_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_FACEBOOK_ENABLED = True +HTK_FACEBOOK_API_KEY = 'your_key' +HTK_FACEBOOK_TIMEOUT = 30 +HTK_FACEBOOK_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/fitbit/README.md b/lib/fitbit/README.md new file mode 100644 index 00000000..bd65dc18 --- /dev/null +++ b/lib/fitbit/README.md @@ -0,0 +1,40 @@ +# Fitbit Integration + +Fitness tracking, activity, and health data. + +## Quick Start + +```python +from htk.lib.fitbit.api import FitbitAPI + +api = FitbitAPI() + +# Get activity data +steps = api.get_activity_steps_for_period(start_date, end_date) +weight_logs = api.get_weight_logs('2024-01-01') +body_fat = api.get_body_fat_logs('2024-01-01') + +# List devices +devices = api.get_devices() +``` + +## Operations + +```python +# Get historical activity +steps_past_month = api.get_activity_steps_past_month() +steps_past_year = api.get_activity_steps_past_year() + +# Custom API requests +api.post('/resource', data={'key': 'value'}) +api.get('/resource') +``` + +## Configuration + +```python +# settings.py +FITBIT_CLIENT_ID = os.environ.get('FITBIT_CLIENT_ID') +FITBIT_CLIENT_SECRET = os.environ.get('FITBIT_CLIENT_SECRET') +FITBIT_ACCESS_TOKEN = os.environ.get('FITBIT_ACCESS_TOKEN') +``` diff --git a/lib/fitbit/constants/README.md b/lib/fitbit/constants/README.md new file mode 100644 index 00000000..b92dda12 --- /dev/null +++ b/lib/fitbit/constants/README.md @@ -0,0 +1,49 @@ +# Fitbit API Constants + +API endpoints and resources for Fitbit API integration. + +## Constants + +```python +from htk.lib.fitbit.constants import FITBIT_API_BASE_URL, FITBIT_API_RESOURCES +``` + +## API Base URL + +```python +FITBIT_API_BASE_URL = 'https://api.fitbit.com' +``` + +## API Resources + +Dictionary mapping resource types to endpoint paths: + +```python +FITBIT_API_RESOURCES = { + # OAuth + 'refresh': '/oauth2/token', + 'revoke': '/oauth2/revoke', + + # Activity + 'activity-steps': lambda date, period: f'/1/user/-/activities/steps/date/{date}/{period}.json', + + # Body & Weight + 'fat': lambda date: f'/1/user/-/body/log/fat/date/{date}.json', + 'weight': lambda date: f'/1/user/-/body/log/weight/date/{date}.json', + + # Settings + 'devices': '/1/user/-/devices.json', +} +``` + +## Usage Example + +```python +from htk.lib.fitbit.constants import FITBIT_API_BASE_URL, FITBIT_API_RESOURCES + +# Build activity steps URL +date = '2023-10-15' +period = '1w' +endpoint = FITBIT_API_RESOURCES['activity-steps'](date, period) +url = f'{FITBIT_API_BASE_URL}{endpoint}' +``` diff --git a/lib/fullcontact/README.md b/lib/fullcontact/README.md new file mode 100644 index 00000000..848d5b47 --- /dev/null +++ b/lib/fullcontact/README.md @@ -0,0 +1,39 @@ +# Fullcontact Integration + +Contact enrichment and identity verification. + +## Quick Start + +```python +from htk.lib.fullcontact.utils import find_person_by_email + +person = find_person_by_email('user@example.com') +print(person.name, person.emails, person.phones) +``` + +## Operations + +```python +from htk.lib.fullcontact.api import FullContactAPIV3 + +api = FullContactAPIV3() + +# Get person by email +person = api.get_person(email='user@example.com') + +# Get batch of people +people = api.get_persons(emails=['user1@example.com', 'user2@example.com']) + +# Find valid emails +valid_emails = api.find_valid_emails(email_list) + +# Format for Slack +slack_message = person.as_slack_v3() +``` + +## Configuration + +```python +# settings.py +FULLCONTACT_API_KEY = os.environ.get('FULLCONTACT_API_KEY') +``` diff --git a/lib/fullcontact/constants/README.md b/lib/fullcontact/constants/README.md new file mode 100644 index 00000000..3e4858bd --- /dev/null +++ b/lib/fullcontact/constants/README.md @@ -0,0 +1,29 @@ +# FullContact API Constants + +Configuration constants for FullContact API integration. + +## Configuration Settings + +```python +from htk.lib.fullcontact.constants import HTK_FULLCONTACT_PERSON_CLASS +``` + +## Person Class Configuration + +Specify the Python class path for FullContact Person objects: + +```python +# settings.py +HTK_FULLCONTACT_PERSON_CLASS = 'htk.lib.fullcontact.classes.FullContactPerson' +``` + +## Usage Example + +```python +from django.utils.module_loading import import_string +from htk.lib.fullcontact.constants import HTK_FULLCONTACT_PERSON_CLASS + +# Dynamically import the Person class +PersonClass = import_string(HTK_FULLCONTACT_PERSON_CLASS) +person = PersonClass(email='user@example.com') +``` diff --git a/lib/geoip/README.md b/lib/geoip/README.md new file mode 100644 index 00000000..6607aa51 --- /dev/null +++ b/lib/geoip/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.geoip.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_GEOIP_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_GEOIP_API_KEY = 'your_api_key' +HTK_GEOIP_API_SECRET = 'your_secret' +HTK_GEOIP_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_GEOIP_ENABLED = True +HTK_GEOIP_API_KEY = 'your_key' +HTK_GEOIP_TIMEOUT = 30 +HTK_GEOIP_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/github/README.md b/lib/github/README.md index 49ade078..af0b97ba 100644 --- a/lib/github/README.md +++ b/lib/github/README.md @@ -1,24 +1,28 @@ -# GitHub Reminder Bot and Utilities +# GitHub Integration -## Basic Usage +Repository management and release tracking. -The main script, which can be run as a standalone, is `bots.py`. +## Quick Start -Usage: `python bots.py -t YOURTOKEN -o YOURORGANIZATION` +```python +from htk.lib.github.utils import get_repository, sync_repository_releases -## Scheduling +repo = get_repository('owner/repo') +sync_repository_releases(repo) +``` -If you would like to run the GitHub reminder bot on a recurring basis, you have a couple of options: +## Bots & Reminders -1. Use the out of the box scheduler script and Hacktoolkit's scheduling system, `tasks.py`: - Create a Python script that will execute in a loop, optionally using [Supervisor](http://supervisord.org/) +```python +from htk.lib.github.bots import GitHubReminderBot - ``` - import time - - while True: - GitHubReminderTask().execute_batch() - time.sleep(60 * 60) - ``` +bot = GitHubReminderBot() +reminder = bot.pull_request_reminder(org) +``` -2. Write a wrapper shell script that invokes `bots.py` with the correct arguments, schedule it with something like `crontab` +## Configuration + +```python +# settings.py +GITHUB_API_TOKEN = os.environ.get('GITHUB_API_TOKEN') +``` diff --git a/lib/github/models/README.md b/lib/github/models/README.md new file mode 100644 index 00000000..c0614212 --- /dev/null +++ b/lib/github/models/README.md @@ -0,0 +1,158 @@ +# Models + +## Overview + +This models module defines Django models that represent the database schema. Models define fields, relationships, methods, and metadata for objects persisted to the database. + +## Quick Start + +### Query Models + +```python +from htk.lib.github.models.models import Item + +# Get all objects +items = Item.objects.all() + +# Filter by condition +active = Item.objects.filter(is_active=True) + +# Get single object +item = Item.objects.get(id=1) # Raises DoesNotExist if not found + +# Get or None +item = Item.objects.filter(id=1).first() # Returns None if not found +``` + +### Create Objects + +```python +from htk.lib.github.models.models import Item + +# Create and save +item = Item.objects.create( + name='test', + description='...' +) + +# Create instance, modify, then save +item = Item(name='test') +item.save() + +# Bulk create +items = Item.objects.bulk_create([ + Item(name='item1'), + Item(name='item2'), +]) +``` + +## Model Fields + +Models include various field types: + +- **CharField** - Text (limited length) +- **TextField** - Long text +- **IntegerField** - Integer numbers +- **ForeignKey** - Relationship to another model +- **ManyToManyField** - Many-to-many relationship +- **DateTimeField** - Date and time +- **BooleanField** - True/False + +### Field Options + +```python +class Item(models.Model): + name = models.CharField( + max_length=100, + help_text='Item name', + unique=True + ) + + is_active = models.BooleanField(default=True) + + created = models.DateTimeField(auto_now_add=True) + updated = models.DateTimeField(auto_now=True) +``` + +## Relationships + +### Foreign Key + +One-to-many relationship: + +```python +class Author(models.Model): + name = models.CharField(max_length=100) + +class Book(models.Model): + title = models.CharField(max_length=200) + author = models.ForeignKey( + Author, + on_delete=models.CASCADE, + related_name='books' + ) +``` + +### Many-to-Many + +Many-to-many relationship: + +```python +class Article(models.Model): + title = models.CharField(max_length=200) + tags = models.ManyToManyField(Tag, related_name='articles') +``` + +## Properties and Methods + +### @property + +Computed property: + +```python +class Item(models.Model): + first_name = models.CharField(max_length=50) + last_name = models.CharField(max_length=50) + + @property + def full_name(self): + return f"{self.first_name} {self.last_name}" +``` + +## Querysets + +### Filtering + +```python +# Exact match +Item.objects.filter(status='active') + +# Greater than +Item.objects.filter(created__gt=datetime.now()) + +# Contains +Item.objects.filter(name__icontains='test') + +# In list +Item.objects.filter(status__in=['active', 'pending']) +``` + +### Ordering + +```python +# Ascending +Item.objects.all().order_by('name') + +# Descending +Item.objects.all().order_by('-created') +``` + +## Best Practices + +1. **Use QuerySets** - Dont fetch unnecessary data +2. **Select related** - Use `select_related()` for ForeignKey +3. **Prefetch related** - Use `prefetch_related()` for ManyToMany +4. **Index fields** - Add `db_index=True` to frequently filtered fields +5. **Use choices** - For fields with limited values +6. **Document fields** - Use `help_text` +7. **Add Meta class** - Configure ordering, permissions, verbose names diff --git a/lib/glassdoor/README.md b/lib/glassdoor/README.md new file mode 100644 index 00000000..918ccd57 --- /dev/null +++ b/lib/glassdoor/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.glassdoor.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_GLASSDOOR_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_GLASSDOOR_API_KEY = 'your_api_key' +HTK_GLASSDOOR_API_SECRET = 'your_secret' +HTK_GLASSDOOR_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_GLASSDOOR_ENABLED = True +HTK_GLASSDOOR_API_KEY = 'your_key' +HTK_GLASSDOOR_TIMEOUT = 30 +HTK_GLASSDOOR_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/google/README.md b/lib/google/README.md new file mode 100644 index 00000000..b108a247 --- /dev/null +++ b/lib/google/README.md @@ -0,0 +1,74 @@ +# Google Integration + +Gmail, Maps, Sheets, Translate, Cloud, and reCAPTCHA APIs. + +## Gmail API + +```python +from htk.lib.google.gmail.api import GmailAPI + +gmail = GmailAPI() +messages = gmail.messages_list() +threads = gmail.threads_list() + +# Modify messages +gmail.message_modify(msg_id, add_labels=['LABEL_ID']) +gmail.message_trash(msg_id) +``` + +## Maps + +```python +from htk.lib.google.maps.utils import get_map_url_for_geolocation + +# Get Google Maps URL +map_url = get_map_url_for_geolocation(lat=37.7749, lng=-122.4194) +``` + +## Geocoding + +```python +from htk.lib.google.geocode.api import geocode + +address = geocode('1600 Pennsylvania Avenue NW, Washington, DC') +# Returns: {'lat': 38.89..., 'lng': -77.03..., ...} +``` + +## Sheets API + +```python +from htk.lib.google.sheets.api import spreadsheets_values_append + +# Append to Google Sheet +spreadsheets_values_append( + spreadsheet_id='sheet_id', + range='Sheet1!A1', + values=[['Name', 'Email'], ['John', 'john@example.com']] +) +``` + +## Translation + +```python +from htk.lib.google.translate.utils import translate + +result = translate('Hello', source_language='en', target_language='es') +# Returns: 'Hola' +``` + +## reCAPTCHA + +```python +from htk.lib.google.recaptcha.utils import google_recaptcha_site_verification + +is_valid = google_recaptcha_site_verification(token) +``` + +## Configuration + +```python +# settings.py +GOOGLE_SERVER_API_KEY = os.environ.get('GOOGLE_SERVER_API_KEY') +GOOGLE_BROWSER_API_KEY = os.environ.get('GOOGLE_BROWSER_API_KEY') +GOOGLE_SHEETS_API_KEY = os.environ.get('GOOGLE_SHEETS_API_KEY') +``` diff --git a/lib/google/chat/README.md b/lib/google/chat/README.md new file mode 100644 index 00000000..f43a2695 --- /dev/null +++ b/lib/google/chat/README.md @@ -0,0 +1,53 @@ +# Google Chat Utilities + +Utilities for sending messages to Google Chat using webhooks. + +## Functions + +**google_chat_webhook_call(webhook_url, payload)** +- Sends a message to Google Chat via webhook +- Makes HTTP POST request with JSON payload +- Supports all Google Chat message formats (text, cards, etc.) +- Returns requests.Response object +- Raises exception on network/API errors + +## Example Usage + +```python +from htk.lib.google.chat.utils import google_chat_webhook_call + +# Send simple text message +webhook_url = 'https://chat.googleapis.com/v1/spaces/SPACE_ID/messages?key=KEY&token=TOKEN' +payload = { + 'text': 'Hello from HTK!' +} +response = google_chat_webhook_call(webhook_url, payload) + +# Send formatted card message +payload = { + 'cards': [{ + 'header': {'title': 'Notification'}, + 'sections': [{ + 'widgets': [{ + 'textParagraph': {'text': 'This is a card message'} + }] + }] + }] +} +response = google_chat_webhook_call(webhook_url, payload) +``` + +## Configuration + +Google Chat webhooks are configured per space in Google Chat: + +1. Open a Google Chat space +2. Go to "Apps & integrations" +3. Create a new webhook +4. Copy the webhook URL to your settings + +## Webhook URL Format + +``` +https://chat.googleapis.com/v1/spaces/{SPACE_ID}/messages?key={API_KEY}&token={WEBHOOK_TOKEN} +``` diff --git a/lib/google/chat/constants/README.md b/lib/google/chat/constants/README.md new file mode 100644 index 00000000..403f7ee5 --- /dev/null +++ b/lib/google/chat/constants/README.md @@ -0,0 +1,85 @@ +# Google Chat Constants + +Configuration defaults and constants for Google Chat integration. + +## Configuration Settings + +```python +from htk.lib.google.chat.constants import ( + HTK_GOOGLE_CHAT_NOTIFICATIONS_ENABLED, + HTK_GOOGLE_CHAT_DEBUG_SPACE, + HTK_GOOGLE_CHAT_NOTIFICATIONS_SPACES, + HTK_GOOGLE_CHAT_SPACES_WEBHOOK_URLS, +) +``` + +### Notifications + +**HTK_GOOGLE_CHAT_NOTIFICATIONS_ENABLED** +- Boolean flag to enable/disable Google Chat notifications +- Default: `False` + +**HTK_GOOGLE_CHAT_DEBUG_SPACE** +- Debug space ID for testing notifications +- Default: `'#test'` + +### Spaces + +**HTK_GOOGLE_CHAT_NOTIFICATIONS_SPACES** +- Dictionary mapping alert severity levels to Google Chat space IDs +- Severity levels: `critical`, `severe`, `danger`, `warning`, `info`, `debug` +- Default mapping: + - `'critical'`: `'#alerts-p0-critical'` + - `'severe'`: `'#alerts-p1-severe'` + - `'danger'`: `'#alerts-p2-danger'` + - `'warning'`: `'#alerts-p3-warning'` + - `'info'`: `'#alerts-p4-info'` + - `'debug'`: `'#alerts-p5-debug'` + +**HTK_GOOGLE_CHAT_SPACES_WEBHOOK_URLS** +- Dictionary mapping space IDs to their webhook URLs for posting messages +- Default: `{}` (empty, must be configured per deployment) +- Example configuration: + ```python + HTK_GOOGLE_CHAT_SPACES_WEBHOOK_URLS = { + '#alerts-p0-critical': 'https://chat.googleapis.com/v1/spaces/...', + '#alerts-p1-severe': 'https://chat.googleapis.com/v1/spaces/...', + } + ``` + +## Example Usage + +```python +from htk.lib.google.chat.constants import ( + HTK_GOOGLE_CHAT_NOTIFICATIONS_SPACES, + HTK_GOOGLE_CHAT_SPACES_WEBHOOK_URLS, +) + +# Get the critical alerts space +critical_space = HTK_GOOGLE_CHAT_NOTIFICATIONS_SPACES['critical'] + +# Get webhook URL for that space +webhook_url = HTK_GOOGLE_CHAT_SPACES_WEBHOOK_URLS.get(critical_space) +``` + +## Configuration in settings.py + +```python +HTK_GOOGLE_CHAT_NOTIFICATIONS_ENABLED = True + +HTK_GOOGLE_CHAT_DEBUG_SPACE = '#test-alerts' + +HTK_GOOGLE_CHAT_NOTIFICATIONS_SPACES = { + 'critical': '#alerts-critical', + 'severe': '#alerts-severe', + 'danger': '#alerts-danger', + 'warning': '#alerts-warning', + 'info': '#alerts-info', + 'debug': '#alerts-debug', +} + +HTK_GOOGLE_CHAT_SPACES_WEBHOOK_URLS = { + '#alerts-critical': 'https://chat.googleapis.com/v1/spaces/SPACES_ID_CRITICAL/messages?key=KEY&token=TOKEN', + '#alerts-severe': 'https://chat.googleapis.com/v1/spaces/SPACES_ID_SEVERE/messages?key=KEY&token=TOKEN', +} +``` diff --git a/lib/google/cloud_messaging/README.md b/lib/google/cloud_messaging/README.md new file mode 100644 index 00000000..bcd7c811 --- /dev/null +++ b/lib/google/cloud_messaging/README.md @@ -0,0 +1,36 @@ +# Google Cloud Messaging (GCM) Utilities + +Utilities for Google Cloud Messaging integration and push notification delivery. + +## Functions + +**get_gcm_client()** +- Initializes and returns a GCM (Google Cloud Messaging) client +- Reads API key from `HTK_GCM_API_KEY` setting +- Returns GCM instance if key is configured, None otherwise +- Uses the `gcm` library for message delivery + +## Example Usage + +```python +from htk.lib.google.cloud_messaging.utils import get_gcm_client + +# Get GCM client +client = get_gcm_client() + +if client: + # Send message to device + response = client.plaintext_request( + registration_ids=['device_token_1', 'device_token_2'], + data={'message': 'Hello World'} + ) +``` + +## Configuration + +```python +# settings.py +HTK_GCM_API_KEY = 'your_gcm_api_key' +``` + +- `HTK_GCM_API_KEY` - Google Cloud Messaging API key for authentication diff --git a/lib/google/constants/README.md b/lib/google/constants/README.md new file mode 100644 index 00000000..53fd1268 --- /dev/null +++ b/lib/google/constants/README.md @@ -0,0 +1,7 @@ +# Google API Constants + +This module imports and re-exports constants from external Google API libraries. + +The constants are imported from `social_core.backends.google` and are not defined locally in this module. + +Refer to the python-social-auth documentation for available Google OAuth constants and configuration options. diff --git a/lib/google/geocode/README.md b/lib/google/geocode/README.md index 7929b73c..a97964f3 100644 --- a/lib/google/geocode/README.md +++ b/lib/google/geocode/README.md @@ -1,3 +1,137 @@ +# Integration -Google Geocoding API -https://developers.google.com/maps/documentation/geocoding/ +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.google.geocode.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_GEOCODE_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_GEOCODE_API_KEY = 'your_api_key' +HTK_GEOCODE_API_SECRET = 'your_secret' +HTK_GEOCODE_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_GEOCODE_ENABLED = True +HTK_GEOCODE_API_KEY = 'your_key' +HTK_GEOCODE_TIMEOUT = 30 +HTK_GEOCODE_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Dont exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/google/gmail/README.md b/lib/google/gmail/README.md new file mode 100644 index 00000000..a1b88c1e --- /dev/null +++ b/lib/google/gmail/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.google.gmail.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_GMAIL_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_GMAIL_API_KEY = 'your_api_key' +HTK_GMAIL_API_SECRET = 'your_secret' +HTK_GMAIL_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_GMAIL_ENABLED = True +HTK_GMAIL_API_KEY = 'your_key' +HTK_GMAIL_TIMEOUT = 30 +HTK_GMAIL_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Dont exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/google/maps/README.md b/lib/google/maps/README.md new file mode 100644 index 00000000..cc6438fd --- /dev/null +++ b/lib/google/maps/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.google.maps.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_MAPS_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_MAPS_API_KEY = 'your_api_key' +HTK_MAPS_API_SECRET = 'your_secret' +HTK_MAPS_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_MAPS_ENABLED = True +HTK_MAPS_API_KEY = 'your_key' +HTK_MAPS_TIMEOUT = 30 +HTK_MAPS_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Dont exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/google/recaptcha/README.md b/lib/google/recaptcha/README.md new file mode 100644 index 00000000..43559ec7 --- /dev/null +++ b/lib/google/recaptcha/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.google.recaptcha.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_RECAPTCHA_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_RECAPTCHA_API_KEY = 'your_api_key' +HTK_RECAPTCHA_API_SECRET = 'your_secret' +HTK_RECAPTCHA_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_RECAPTCHA_ENABLED = True +HTK_RECAPTCHA_API_KEY = 'your_key' +HTK_RECAPTCHA_TIMEOUT = 30 +HTK_RECAPTCHA_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Dont exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/google/sheets/README.md b/lib/google/sheets/README.md new file mode 100644 index 00000000..4fba7993 --- /dev/null +++ b/lib/google/sheets/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.google.sheets.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_SHEETS_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_SHEETS_API_KEY = 'your_api_key' +HTK_SHEETS_API_SECRET = 'your_secret' +HTK_SHEETS_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_SHEETS_ENABLED = True +HTK_SHEETS_API_KEY = 'your_key' +HTK_SHEETS_TIMEOUT = 30 +HTK_SHEETS_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Dont exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/google/translate/README.md b/lib/google/translate/README.md new file mode 100644 index 00000000..4d8f67e6 --- /dev/null +++ b/lib/google/translate/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.google.translate.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_TRANSLATE_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_TRANSLATE_API_KEY = 'your_api_key' +HTK_TRANSLATE_API_SECRET = 'your_secret' +HTK_TRANSLATE_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_TRANSLATE_ENABLED = True +HTK_TRANSLATE_API_KEY = 'your_key' +HTK_TRANSLATE_TIMEOUT = 30 +HTK_TRANSLATE_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Dont exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/google/youtube/README.md b/lib/google/youtube/README.md new file mode 100644 index 00000000..f519809b --- /dev/null +++ b/lib/google/youtube/README.md @@ -0,0 +1,61 @@ +# YouTube API Utilities + +Utilities for working with YouTube videos including ID extraction and duration retrieval. + +## Functions + +**extract_youtube_video_id(youtube_video_url)** +- Extracts YouTube video ID from various URL formats +- Supports multiple URL patterns (youtu.be, youtube.com/watch, youtube.com/shorts, etc.) +- Returns video ID string or None if extraction fails +- Uses regex pattern matching with error handling and rollbar logging + +**build_youtube_api_url(youtube_video_id)** +- Builds a complete YouTube Data API v3 request URL +- Uses `settings.HTK_GOOGLE_SERVER_API_KEY` for authentication +- Returns formatted URL with query parameters for content details +- Raises exception if API key is not configured + +**get_youtube_video_duration(youtube_video_url)** +- Gets video duration in ISO 8601 format (e.g., "PT5M30S") +- Automatically extracts video ID from URL +- Fetches data from YouTube Data API v3 +- Returns duration string or None if unable to retrieve +- Logs errors to rollbar on failure + +## Constants + +```python +from htk.lib.google.youtube.constants import ( + GOOGLE_APIS_YOUTUBE_BASE_URL, + GOOGLE_APIS_YOUTUBE_VIDEOS_URL, +) +``` + +- `GOOGLE_APIS_YOUTUBE_BASE_URL` - Base URL for YouTube Data API v3 +- `GOOGLE_APIS_YOUTUBE_VIDEOS_URL` - Videos endpoint URL + +## Example Usage + +```python +from htk.lib.google.youtube.utils import ( + extract_youtube_video_id, + get_youtube_video_duration, +) + +# Extract video ID from URL +video_id = extract_youtube_video_id('https://www.youtube.com/watch?v=dQw4w9WgXcQ') + +# Get video duration +duration = get_youtube_video_duration('https://youtu.be/dQw4w9WgXcQ') +# Returns: "PT3M32S" (3 minutes 32 seconds) +``` + +## Configuration + +```python +# settings.py +HTK_GOOGLE_SERVER_API_KEY = ['your_api_key', ...] +``` + +- `HTK_GOOGLE_SERVER_API_KEY` - List of Google API keys with YouTube Data API access enabled diff --git a/lib/gravatar/README.md b/lib/gravatar/README.md new file mode 100644 index 00000000..4815d83b --- /dev/null +++ b/lib/gravatar/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.gravatar.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_GRAVATAR_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_GRAVATAR_API_KEY = 'your_api_key' +HTK_GRAVATAR_API_SECRET = 'your_secret' +HTK_GRAVATAR_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_GRAVATAR_ENABLED = True +HTK_GRAVATAR_API_KEY = 'your_key' +HTK_GRAVATAR_TIMEOUT = 30 +HTK_GRAVATAR_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/indeed/README.md b/lib/indeed/README.md new file mode 100644 index 00000000..c14cc79a --- /dev/null +++ b/lib/indeed/README.md @@ -0,0 +1,41 @@ +# Indeed Integration + +Job posting and application tracking. + +## Quick Start + +```python +from htk.lib.indeed.api import IndeedJobSyncAPI, IndeedDispositionSyncAPI + +job_api = IndeedJobSyncAPI() +disposition_api = IndeedDispositionSyncAPI() + +# Sync job +job_data = {'title': 'Software Engineer', 'description': '...'} +job_api.create_job(job_data) + +# Update application status +disposition_api.post_disposition(applicant_id, 'hired') +``` + +## Operations + +```python +from htk.lib.indeed.api import get_access_token, generate_access_token + +# Token management +token = get_access_token() +new_token = generate_access_token() + +# Job operations +job_api.update_job(job_id, updated_data) +job_api.delete_job(job_id) +``` + +## Configuration + +```python +# settings.py +INDEED_CLIENT_ID = os.environ.get('INDEED_CLIENT_ID') +INDEED_CLIENT_SECRET = os.environ.get('INDEED_CLIENT_SECRET') +``` diff --git a/lib/indeed/api/README.md b/lib/indeed/api/README.md new file mode 100644 index 00000000..d9d0bf67 --- /dev/null +++ b/lib/indeed/api/README.md @@ -0,0 +1,136 @@ +# API + +## Overview + +This API module provides REST API endpoints for programmatic access to the service. Endpoints support standard HTTP methods and return JSON responses. + +## Quick Start + +### Basic Request + +```python +import requests + +# Make API request +response = requests.get('https://api.example.com/endpoint/', auth=auth) +result = response.json() +``` + +### Authentication + +API endpoints require authentication. Configure credentials: + +```python +from requests.auth import HTTPBearerAuth + +auth = HTTPBearerAuth(token='your_token') +response = requests.get('https://api.example.com/endpoint/', auth=auth) +``` + +## API Endpoints + +### Available Endpoints + +Check the `views.py` file for a complete list of available endpoints and their parameters. + +### Request Format + +``` +METHOD /endpoint/ HTTP/1.1 +Host: api.example.com +Content-Type: application/json +Authorization: Bearer + +{ + "param1": "value1", + "param2": "value2" +} +``` + +### Response Format + +Successful responses return HTTP 200 with JSON data. + +Error responses include status code and error message. + +## Common Operations + +### Get Resource + +```python +response = requests.get( + 'https://api.example.com/resource/{id}/', + auth=auth +) +resource = response.json() +``` + +### Create Resource + +```python +response = requests.post( + 'https://api.example.com/resource/', + json={'field': 'value'}, + auth=auth +) +new_resource = response.json() +``` + +### Update Resource + +```python +response = requests.patch( + 'https://api.example.com/resource/{id}/', + json={'field': 'new_value'}, + auth=auth +) +updated = response.json() +``` + +### Delete Resource + +```python +response = requests.delete( + 'https://api.example.com/resource/{id}/', + auth=auth +) +# Returns 204 No Content on success +``` + +## Error Handling + +```python +import requests +from requests.exceptions import RequestException + +try: + response = requests.get(endpoint, auth=auth) + response.raise_for_status() + data = response.json() +except requests.HTTPError as e: + print(f"HTTP Error: {e.response.status_code}") +except RequestException as e: + print(f"Request Error: {e}") +``` + +## Configuration + +Configure API settings in Django settings: + +```python +# settings.py +HTK_API_ENABLED = True +HTK_API_TIMEOUT = 30 +HTK_API_MAX_RETRIES = 3 +HTK_API_RATE_LIMIT = 1000 +``` + +## Best Practices + +1. **Handle errors** - Implement proper error handling for all requests +2. **Use pagination** - For list endpoints, use limit and offset parameters +3. **Add retries** - Implement exponential backoff for transient failures +4. **Cache responses** - Cache frequently accessed data when appropriate +5. **Validate input** - Validate request parameters before sending +6. **Log requests** - Log all API calls for debugging and monitoring +7. **Set timeouts** - Always set request timeouts to prevent hanging diff --git a/lib/iterable/README.md b/lib/iterable/README.md new file mode 100644 index 00000000..63ab4817 --- /dev/null +++ b/lib/iterable/README.md @@ -0,0 +1,45 @@ +# Iterable Integration + +Email and SMS marketing automation. + +## Quick Start + +```python +from htk.lib.iterable.utils import get_iterable_api_client + +client = get_iterable_api_client() + +# Track event +client.track_event(user_id, 'purchase', {'amount': 100}) + +# Notify sign up +client.notify_sign_up(user) + +# Trigger workflow +client.trigger_workflow(user_id, workflow_id) + +# Update email +client.update_user_email(user_id, new_email) +``` + +## Common Operations + +```python +# Get campaign/list/workflow IDs +campaign_id = get_campaign_id('welcome_series') +list_id = get_list_id('active_users') +workflow_id = get_workflow_id('onboarding') + +# Get person data +person = client.get_person(email='user@example.com') + +# Get batch of people +people = client.get_persons(['user1@example.com', 'user2@example.com']) +``` + +## Configuration + +```python +# settings.py +ITERABLE_API_KEY = os.environ.get('ITERABLE_API_KEY') +``` diff --git a/lib/iterable/constants/README.md b/lib/iterable/constants/README.md new file mode 100644 index 00000000..d2c89226 --- /dev/null +++ b/lib/iterable/constants/README.md @@ -0,0 +1,127 @@ +# Iterable Constants + +Configuration settings for Iterable email and marketing automation integration. + +## API Configuration + +```python +from htk.lib.iterable.constants import ( + HTK_ITERABLE_API_KEY, + HTK_ITERABLE_ENABLED, +) +``` + +**HTK_ITERABLE_API_KEY** +- API key for Iterable authentication +- Default: `None` (must be configured in settings) +- Required to enable Iterable integration + +**HTK_ITERABLE_ENABLED** +- Boolean flag to enable/disable Iterable integration +- Default: `False` +- Set to `True` to activate email campaigns and workflows + +## Campaign Configuration + +```python +from htk.lib.iterable.constants import HTK_ITERABLE_CAMPAIGN_IDS +``` + +**HTK_ITERABLE_CAMPAIGN_IDS** +- Nested dictionary mapping campaign categories to campaign IDs +- Structure: + - `'triggered'`: Triggered campaigns + - `'transactional'`: Transactional emails + - `'account'`: Account-related campaigns + - `'sign_up_confirm_email'`: Sign-up confirmation campaign ID + - `'confirm_email_resend'`: Email confirmation resend campaign ID + - `'notifications'`: Notification campaigns + - `'account'`: Account notifications + - `'recurring'`: Recurring campaigns +- Default: Campaign IDs are `None` (must be configured) + +## List and Workflow Configuration + +```python +from htk.lib.iterable.constants import ( + HTK_ITERABLE_LIST_IDS, + HTK_ITERABLE_WORKFLOW_IDS, +) +``` + +**HTK_ITERABLE_LIST_IDS** +- Dictionary mapping list names to Iterable list IDs +- Default: `{}` (empty, add your lists here) +- Used for subscribing/managing users on specific lists + +**HTK_ITERABLE_WORKFLOW_IDS** +- Dictionary mapping workflow names to workflow IDs +- Workflows: + - `'account.sign_up'`: New account signup workflow + - `'account.activation'`: Account activation workflow + - `'account.login'`: User login workflow +- Default: Workflow IDs are `None` (must be configured) + +## Options + +```python +from htk.lib.iterable.constants import HTK_ITERABLE_OPTIONS +``` + +**HTK_ITERABLE_OPTIONS** +- Configuration options for Iterable behavior +- Options: + - `'override_welcome_email'`: Boolean to override default welcome email (default: `False`) + +## Example Usage + +```python +from htk.lib.iterable.constants import ( + HTK_ITERABLE_API_KEY, + HTK_ITERABLE_CAMPAIGN_IDS, + HTK_ITERABLE_WORKFLOW_IDS, +) + +# Get campaign ID for sign-up confirmation +campaign_id = HTK_ITERABLE_CAMPAIGN_IDS['triggered']['transactional']['account']['sign_up_confirm_email'] + +# Get workflow ID for account signup +workflow_id = HTK_ITERABLE_WORKFLOW_IDS['account.sign_up'] +``` + +## Configuration in settings.py + +```python +HTK_ITERABLE_API_KEY = 'your_iterable_api_key' +HTK_ITERABLE_ENABLED = True + +HTK_ITERABLE_CAMPAIGN_IDS = { + 'triggered': { + 'transactional': { + 'account': { + 'sign_up_confirm_email': 12345, + 'confirm_email_resend': 12346, + }, + }, + 'notifications': { + 'account': {}, + }, + 'recurring': {}, + }, +} + +HTK_ITERABLE_LIST_IDS = { + 'newsletter': 98765, + 'customers': 98766, +} + +HTK_ITERABLE_WORKFLOW_IDS = { + 'account.sign_up': 54321, + 'account.activation': 54322, + 'account.login': 54323, +} + +HTK_ITERABLE_OPTIONS = { + 'override_welcome_email': False, +} +``` diff --git a/lib/linkedin/README.md b/lib/linkedin/README.md new file mode 100644 index 00000000..08538bb6 --- /dev/null +++ b/lib/linkedin/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.linkedin.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_LINKEDIN_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_LINKEDIN_API_KEY = 'your_api_key' +HTK_LINKEDIN_API_SECRET = 'your_secret' +HTK_LINKEDIN_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_LINKEDIN_ENABLED = True +HTK_LINKEDIN_API_KEY = 'your_key' +HTK_LINKEDIN_TIMEOUT = 30 +HTK_LINKEDIN_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/literalword/README.md b/lib/literalword/README.md new file mode 100644 index 00000000..3180ea3a --- /dev/null +++ b/lib/literalword/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.literalword.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_LITERALWORD_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_LITERALWORD_API_KEY = 'your_api_key' +HTK_LITERALWORD_API_SECRET = 'your_secret' +HTK_LITERALWORD_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_LITERALWORD_ENABLED = True +HTK_LITERALWORD_API_KEY = 'your_key' +HTK_LITERALWORD_TIMEOUT = 30 +HTK_LITERALWORD_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/mailchimp/README.md b/lib/mailchimp/README.md new file mode 100644 index 00000000..40fd172a --- /dev/null +++ b/lib/mailchimp/README.md @@ -0,0 +1,33 @@ +# Mailchimp Integration + +Email marketing and subscriber management. + +## Quick Start + +```python +from htk.lib.mailchimp.utils import get_api_url, get_api_data_center + +# Determine API endpoint from API key +data_center = get_api_data_center(api_key) +api_url = get_api_url(api_key) +``` + +## Operations + +```python +# Manage lists +api.create_list('Newsletter', contact={'company': 'Acme'}) +api.subscribe_to_list(list_id, email) +api.unsubscribe_from_list(list_id, email) + +# Send campaigns +api.create_campaign(list_id, campaign_data) +api.send_campaign(campaign_id) +``` + +## Configuration + +```python +# settings.py +MAILCHIMP_API_KEY = os.environ.get('MAILCHIMP_API_KEY') +``` diff --git a/lib/mapbox/README.md b/lib/mapbox/README.md new file mode 100644 index 00000000..313a0ecb --- /dev/null +++ b/lib/mapbox/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.mapbox.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_MAPBOX_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_MAPBOX_API_KEY = 'your_api_key' +HTK_MAPBOX_API_SECRET = 'your_secret' +HTK_MAPBOX_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_MAPBOX_ENABLED = True +HTK_MAPBOX_API_KEY = 'your_key' +HTK_MAPBOX_TIMEOUT = 30 +HTK_MAPBOX_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/mongodb/README.md b/lib/mongodb/README.md new file mode 100644 index 00000000..2dca0eb5 --- /dev/null +++ b/lib/mongodb/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.mongodb.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_MONGODB_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_MONGODB_API_KEY = 'your_api_key' +HTK_MONGODB_API_SECRET = 'your_secret' +HTK_MONGODB_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_MONGODB_ENABLED = True +HTK_MONGODB_API_KEY = 'your_key' +HTK_MONGODB_TIMEOUT = 30 +HTK_MONGODB_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/mongodb/constants/README.md b/lib/mongodb/constants/README.md new file mode 100644 index 00000000..0d4fbb6e --- /dev/null +++ b/lib/mongodb/constants/README.md @@ -0,0 +1,30 @@ +# MongoDB Constants + +Configuration constants for MongoDB database connections. + +## Configuration Settings + +```python +from htk.lib.mongodb.constants import HTK_MONGODB_CONNECTION, HTK_MONGODB_NAME +``` + +## Database Configuration + +Configure MongoDB connection in Django settings: + +```python +# settings.py +HTK_MONGODB_CONNECTION = 'mongodb://localhost:27017/' +HTK_MONGODB_NAME = 'your_database_name' +``` + +## Usage Example + +```python +from htk.lib.mongodb.constants import HTK_MONGODB_CONNECTION, HTK_MONGODB_NAME +from pymongo import MongoClient + +# Connect to MongoDB +client = MongoClient(HTK_MONGODB_CONNECTION) +db = client[HTK_MONGODB_NAME] +``` diff --git a/lib/oembed/README.md b/lib/oembed/README.md new file mode 100644 index 00000000..a39eee2e --- /dev/null +++ b/lib/oembed/README.md @@ -0,0 +1,26 @@ +# oEmbed Integration + +Embed media content from URLs (YouTube, Vimeo, etc). + +## Quick Start + +```python +from htk.lib.oembed.utils import get_oembed_html, get_oembed_type + +# Get embedding HTML for a URL +html = get_oembed_html('https://www.youtube.com/watch?v=dQw4w9WgXcQ') + +# Get oEmbed type +embed_type = get_oembed_type('https://www.youtube.com/watch?v=dQw4w9WgXcQ') +# Returns: YouTube, Vimeo, etc + +# Get HTML for specific service +vimeo_html = get_oembed_html_for_service('vimeo', 'https://vimeo.com/123456') +``` + +## Configuration + +```python +# settings.py +OEMBED_ENABLED = True +``` diff --git a/lib/ohmygreen/README.md b/lib/ohmygreen/README.md new file mode 100644 index 00000000..f62be277 --- /dev/null +++ b/lib/ohmygreen/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.ohmygreen.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_OHMYGREEN_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_OHMYGREEN_API_KEY = 'your_api_key' +HTK_OHMYGREEN_API_SECRET = 'your_secret' +HTK_OHMYGREEN_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_OHMYGREEN_ENABLED = True +HTK_OHMYGREEN_API_KEY = 'your_key' +HTK_OHMYGREEN_TIMEOUT = 30 +HTK_OHMYGREEN_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/ohmygreen/constants/README.md b/lib/ohmygreen/constants/README.md new file mode 100644 index 00000000..3d883cdc --- /dev/null +++ b/lib/ohmygreen/constants/README.md @@ -0,0 +1,35 @@ +# OhMyGreen API Constants + +API endpoints and resources for OhMyGreen catering service integration. + +## Constants + +```python +from htk.lib.ohmygreen.constants import OHMYGREEN_API_BASE_URL, OHMYGREEN_API_RESOURCES +``` + +## API Base URL + +```python +OHMYGREEN_API_BASE_URL = 'https://www.ohmygreen.com/api' +``` + +## API Resources + +Dictionary mapping resource types to endpoint URLs: + +```python +OHMYGREEN_API_RESOURCES = { + 'menu': 'https://www.ohmygreen.com/api/catering/menus', +} +``` + +## Usage Example + +```python +from htk.lib.ohmygreen.constants import OHMYGREEN_API_RESOURCES + +# Fetch menu data +menu_url = OHMYGREEN_API_RESOURCES['menu'] +response = requests.get(menu_url) +``` diff --git a/lib/openai/README.md b/lib/openai/README.md new file mode 100644 index 00000000..6a61d543 --- /dev/null +++ b/lib/openai/README.md @@ -0,0 +1,31 @@ +# OpenAI Integration + +Chat completions and AI capabilities. + +## Quick Start + +```python +from htk.lib.openai.adapter import chat_completion + +response = chat_completion([ + {'role': 'user', 'content': 'What is Python?'} +]) +``` + +## System Prompts + +```python +from htk.lib.openai.models.system_prompt import BaseOpenAISystemPrompt + +prompt = BaseOpenAISystemPrompt.objects.create( + name='helpful_assistant', + content='You are a helpful assistant...' +) +``` + +## Configuration + +```python +# settings.py +OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY') +``` diff --git a/lib/openai/constants/README.md b/lib/openai/constants/README.md new file mode 100644 index 00000000..edf99923 --- /dev/null +++ b/lib/openai/constants/README.md @@ -0,0 +1,68 @@ +# OpenAI Constants + +Configuration settings and prompt instructions for OpenAI integration. + +## Configuration Settings + +```python +from htk.lib.openai.constants import HTK_OPENAI_SYSTEM_PROMPT_MODEL +``` + +**HTK_OPENAI_SYSTEM_PROMPT_MODEL** +- Model identifier for system prompt generation +- Default: `None` (must be configured in settings) +- Examples: `'gpt-4'`, `'gpt-3.5-turbo'`, etc. + +## Prompt Instructions + +```python +from htk.lib.openai.constants import ( + OPENAI_PROMPT_INSTRUCTION_FORMAT__TARGET_READING_GRADE_LEVEL, + OPENAI_PROMPT_INSTRUCTION__EXPECTED_RESPONSE_FORMAT, + OPENAI_PROMPT_INSTRUCTION__JSON_RESPONSE, +) +``` + +### Reading Level Instruction + +**OPENAI_PROMPT_INSTRUCTION_FORMAT__TARGET_READING_GRADE_LEVEL** +- Template for specifying target reading grade level in prompts +- Format: `'Target reading grade level: {target_reading_grade_level}'` +- Use: Fill in the placeholder with desired grade level (e.g., 8, 10, 12) + +### Response Format Instructions + +**OPENAI_PROMPT_INSTRUCTION__EXPECTED_RESPONSE_FORMAT** +- Instruction text indicating response format section +- Text: `'Expected response format:'` +- Precedes format specifications in system prompts + +**OPENAI_PROMPT_INSTRUCTION__JSON_RESPONSE** +- Strict instruction for JSON-only responses +- Ensures response is valid JSON that can be parsed with `json.loads()` +- Useful for structured data extraction and API responses + +## Example Usage + +```python +from htk.lib.openai.constants import ( + OPENAI_PROMPT_INSTRUCTION_FORMAT__TARGET_READING_GRADE_LEVEL, + OPENAI_PROMPT_INSTRUCTION__JSON_RESPONSE, +) + +# Build prompt with reading level +reading_level_instruction = OPENAI_PROMPT_INSTRUCTION_FORMAT__TARGET_READING_GRADE_LEVEL.format( + target_reading_grade_level=8 +) + +# Create system prompt with JSON requirement +system_prompt = f"""You are a helpful assistant. +{OPENAI_PROMPT_INSTRUCTION__JSON_RESPONSE} +""" +``` + +## Configuration in settings.py + +```python +HTK_OPENAI_SYSTEM_PROMPT_MODEL = 'gpt-4' +``` diff --git a/lib/openai/models/README.md b/lib/openai/models/README.md new file mode 100644 index 00000000..ff407fb5 --- /dev/null +++ b/lib/openai/models/README.md @@ -0,0 +1,158 @@ +# Models + +## Overview + +This models module defines Django models that represent the database schema. Models define fields, relationships, methods, and metadata for objects persisted to the database. + +## Quick Start + +### Query Models + +```python +from htk.lib.openai.models.models import Item + +# Get all objects +items = Item.objects.all() + +# Filter by condition +active = Item.objects.filter(is_active=True) + +# Get single object +item = Item.objects.get(id=1) # Raises DoesNotExist if not found + +# Get or None +item = Item.objects.filter(id=1).first() # Returns None if not found +``` + +### Create Objects + +```python +from htk.lib.openai.models.models import Item + +# Create and save +item = Item.objects.create( + name='test', + description='...' +) + +# Create instance, modify, then save +item = Item(name='test') +item.save() + +# Bulk create +items = Item.objects.bulk_create([ + Item(name='item1'), + Item(name='item2'), +]) +``` + +## Model Fields + +Models include various field types: + +- **CharField** - Text (limited length) +- **TextField** - Long text +- **IntegerField** - Integer numbers +- **ForeignKey** - Relationship to another model +- **ManyToManyField** - Many-to-many relationship +- **DateTimeField** - Date and time +- **BooleanField** - True/False + +### Field Options + +```python +class Item(models.Model): + name = models.CharField( + max_length=100, + help_text='Item name', + unique=True + ) + + is_active = models.BooleanField(default=True) + + created = models.DateTimeField(auto_now_add=True) + updated = models.DateTimeField(auto_now=True) +``` + +## Relationships + +### Foreign Key + +One-to-many relationship: + +```python +class Author(models.Model): + name = models.CharField(max_length=100) + +class Book(models.Model): + title = models.CharField(max_length=200) + author = models.ForeignKey( + Author, + on_delete=models.CASCADE, + related_name='books' + ) +``` + +### Many-to-Many + +Many-to-many relationship: + +```python +class Article(models.Model): + title = models.CharField(max_length=200) + tags = models.ManyToManyField(Tag, related_name='articles') +``` + +## Properties and Methods + +### @property + +Computed property: + +```python +class Item(models.Model): + first_name = models.CharField(max_length=50) + last_name = models.CharField(max_length=50) + + @property + def full_name(self): + return f"{self.first_name} {self.last_name}" +``` + +## Querysets + +### Filtering + +```python +# Exact match +Item.objects.filter(status='active') + +# Greater than +Item.objects.filter(created__gt=datetime.now()) + +# Contains +Item.objects.filter(name__icontains='test') + +# In list +Item.objects.filter(status__in=['active', 'pending']) +``` + +### Ordering + +```python +# Ascending +Item.objects.all().order_by('name') + +# Descending +Item.objects.all().order_by('-created') +``` + +## Best Practices + +1. **Use QuerySets** - Dont fetch unnecessary data +2. **Select related** - Use `select_related()` for ForeignKey +3. **Prefetch related** - Use `prefetch_related()` for ManyToMany +4. **Index fields** - Add `db_index=True` to frequently filtered fields +5. **Use choices** - For fields with limited values +6. **Document fields** - Use `help_text` +7. **Add Meta class** - Configure ordering, permissions, verbose names diff --git a/lib/plivo/README.md b/lib/plivo/README.md new file mode 100644 index 00000000..1193f57b --- /dev/null +++ b/lib/plivo/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.plivo.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_PLIVO_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_PLIVO_API_KEY = 'your_api_key' +HTK_PLIVO_API_SECRET = 'your_secret' +HTK_PLIVO_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_PLIVO_ENABLED = True +HTK_PLIVO_API_KEY = 'your_key' +HTK_PLIVO_TIMEOUT = 30 +HTK_PLIVO_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/plivo/constants/README.md b/lib/plivo/constants/README.md new file mode 100644 index 00000000..0a57e395 --- /dev/null +++ b/lib/plivo/constants/README.md @@ -0,0 +1,52 @@ +# Plivo SMS Constants + +Webhook parameters and message formatting for Plivo SMS integration. + +## Constants + +```python +from htk.lib.plivo.constants import ( + PLIVO_MESSAGE_WEBHOOK_PARAMS, + PLIVO_SLACK_DEFAULT_MESSAGE_FORMAT, +) +``` + +## Webhook Parameters + +Expected parameters in Plivo SMS webhook callbacks: + +```python +PLIVO_MESSAGE_WEBHOOK_PARAMS = ( + 'From', # Sender phone number (e.g., 14085551212) + 'TotalRate', + 'Text', # Message content + 'To', # Recipient phone number (e.g., 14151234567) + 'Units', + 'TotalAmount', + 'Type', # Message type (e.g., 'sms') + 'MessageUUID', # Unique message identifier +) +``` + +## Slack Message Format + +Default format string for posting Plivo messages to Slack: + +```python +PLIVO_SLACK_DEFAULT_MESSAGE_FORMAT = 'Plivo Message from *%(From)s* (%(Type)s; %(MessageUUID)s)\n>>> %(Text)s' +``` + +## Usage Example + +```python +from htk.lib.plivo.constants import PLIVO_SLACK_DEFAULT_MESSAGE_FORMAT + +# Format a Plivo webhook for Slack +webhook_data = { + 'From': '14085551212', + 'Type': 'sms', + 'MessageUUID': 'abc-123', + 'Text': 'Hello world', +} +slack_message = PLIVO_SLACK_DEFAULT_MESSAGE_FORMAT % webhook_data +``` diff --git a/lib/qrcode/README.md b/lib/qrcode/README.md new file mode 100644 index 00000000..5718d14b --- /dev/null +++ b/lib/qrcode/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.qrcode.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_QRCODE_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_QRCODE_API_KEY = 'your_api_key' +HTK_QRCODE_API_SECRET = 'your_secret' +HTK_QRCODE_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_QRCODE_ENABLED = True +HTK_QRCODE_API_KEY = 'your_key' +HTK_QRCODE_TIMEOUT = 30 +HTK_QRCODE_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/qrcode/constants/README.md b/lib/qrcode/constants/README.md new file mode 100644 index 00000000..0da3fd5c --- /dev/null +++ b/lib/qrcode/constants/README.md @@ -0,0 +1,34 @@ +# QR Code Constants + +Configuration constants for QR code generation and validation. + +## Configuration Settings + +```python +from htk.lib.qrcode.constants import HTK_QR_SECRET +``` + +## QR Secret + +Secret key used for signing/validating QR codes: + +```python +# settings.py +HTK_QR_SECRET = 'your-secure-secret-key' +``` + +The default value should always be overridden in production: + +```python +# Default (DO NOT USE IN PRODUCTION) +HTK_QR_SECRET = 'PLEASE_OVERRIDE_THIS_IN_DJANGO_SETTINGS' +``` + +## Usage Example + +```python +from htk.lib.qrcode.constants import HTK_QR_SECRET + +# Use the secret for signing QR code data +signature = generate_signature(data, HTK_QR_SECRET) +``` diff --git a/lib/rabbitmq/README.md b/lib/rabbitmq/README.md new file mode 100644 index 00000000..98f875cf --- /dev/null +++ b/lib/rabbitmq/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.rabbitmq.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_RABBITMQ_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_RABBITMQ_API_KEY = 'your_api_key' +HTK_RABBITMQ_API_SECRET = 'your_secret' +HTK_RABBITMQ_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_RABBITMQ_ENABLED = True +HTK_RABBITMQ_API_KEY = 'your_key' +HTK_RABBITMQ_TIMEOUT = 30 +HTK_RABBITMQ_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/redfin/README.md b/lib/redfin/README.md new file mode 100644 index 00000000..b021101b --- /dev/null +++ b/lib/redfin/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.redfin.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_REDFIN_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_REDFIN_API_KEY = 'your_api_key' +HTK_REDFIN_API_SECRET = 'your_secret' +HTK_REDFIN_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_REDFIN_ENABLED = True +HTK_REDFIN_API_KEY = 'your_key' +HTK_REDFIN_TIMEOUT = 30 +HTK_REDFIN_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/sfbart/README.md b/lib/sfbart/README.md new file mode 100644 index 00000000..5d2eddf2 --- /dev/null +++ b/lib/sfbart/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.sfbart.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_SFBART_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_SFBART_API_KEY = 'your_api_key' +HTK_SFBART_API_SECRET = 'your_secret' +HTK_SFBART_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_SFBART_ENABLED = True +HTK_SFBART_API_KEY = 'your_key' +HTK_SFBART_TIMEOUT = 30 +HTK_SFBART_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/shopify_lib/README.md b/lib/shopify_lib/README.md new file mode 100644 index 00000000..2e74998b --- /dev/null +++ b/lib/shopify_lib/README.md @@ -0,0 +1,50 @@ +# Shopify Integration + +E-commerce API for products, orders, customers, and fulfillment. + +## Quick Start + +```python +from htk.lib.shopify_lib.api import iter_products, iter_orders, iter_customers + +# Iterate products +for product in iter_products(): + print(product.title, product.handle) + +# Iterate orders +for order in iter_orders(): + print(order.id, order.total_price) + +# Iterate customers +for customer in iter_customers(): + print(customer.email, customer.first_name) +``` + +## Models + +- **`ShopifyProduct`** - Product with variants and images +- **`ShopifyOrder`** - Order with fulfillments +- **`ShopifyCustomer`** - Customer with addresses +- **`ShopifyFulfillment`** - Order fulfillment +- **`ShopifyTransaction`** - Payment transaction + +## Archiving + +```python +from htk.lib.shopify_lib.archivers import archive_all, archive_item_type + +# Archive all data from Shopify +archive_all() + +# Archive specific type +archive_item_type('Product') +``` + +## Configuration + +```python +# settings.py +SHOPIFY_STORE_NAME = os.environ.get('SHOPIFY_STORE_NAME') +SHOPIFY_API_KEY = os.environ.get('SHOPIFY_API_KEY') +SHOPIFY_API_PASSWORD = os.environ.get('SHOPIFY_API_PASSWORD') +``` diff --git a/lib/shopify_lib/constants/README.md b/lib/shopify_lib/constants/README.md new file mode 100644 index 00000000..c0691a74 --- /dev/null +++ b/lib/shopify_lib/constants/README.md @@ -0,0 +1,62 @@ +# Shopify Library Constants + +Configuration constants for Shopify API integration. + +## Configuration Settings + +```python +from htk.lib.shopify_lib.constants import ( + HTK_SHOPIFY_SHOP_NAME, + HTK_SHOPIFY_API_KEY, + HTK_SHOPIFY_API_SECRET, + HTK_SHOPIFY_SHARED_SECRET, + HTK_SHOPIFY_MONGODB_COLLECTIONS, + HTK_SHOPIFY_MONGODB_ITEM_PK, + HTK_SHOPIFY_SQL_MODELS, +) +``` + +## API Authentication + +Configure Shopify API credentials in Django settings: + +```python +# settings.py +HTK_SHOPIFY_SHOP_NAME = 'your-shop-name' +HTK_SHOPIFY_API_KEY = 'your-api-key' +HTK_SHOPIFY_API_SECRET = 'your-api-secret' +HTK_SHOPIFY_SHARED_SECRET = 'your-shared-secret' +``` + +## MongoDB Collections + +Map Shopify object types to MongoDB collections: + +```python +HTK_SHOPIFY_MONGODB_COLLECTIONS = { + 'product': 'product', + 'product_tag': 'product_tag', + 'product_image': 'product_image', + 'product_variant': 'product_variant', + 'customer': 'customer', + 'customer_address': 'customer_address', + 'order': 'order', + 'order_line_item': 'order_line_item', + 'fulfillment': 'fulfillment', + 'refund': 'refund', + 'transaction': 'transaction', +} +``` + +## SQL Models + +Map Shopify object types to Django model paths: + +```python +HTK_SHOPIFY_SQL_MODELS = { + 'product': 'shopify.ShopifyProduct', + 'product_variant': 'shopify.ShopifyProductVariant', + 'customer': 'shopify.ShopifyCustomer', + 'order': 'shopify.ShopifyOrder', +} +``` diff --git a/lib/slack/README.md b/lib/slack/README.md new file mode 100644 index 00000000..dc923f4c --- /dev/null +++ b/lib/slack/README.md @@ -0,0 +1,77 @@ +# Slack Integration + +Send messages, handle webhooks, and respond to Slack events. + +## Quick Start + +```python +from htk.lib.slack.utils import webhook_call + +# Send message +webhook_call({ + 'text': 'Hello from Django!', + 'channel': '#notifications' +}) + +# With formatting +webhook_call({ + 'text': 'Important update', + 'attachments': [{ + 'color': 'good', + 'title': 'Task Complete', + 'text': 'Deployment finished successfully' + }] +}) +``` + +## Event Handlers + +```python +from htk.lib.slack.event_handlers import default, weather, github_prs + +# Built-in handlers for various events +# Customize in event_handlers.py +``` + +## Webhook Handling + +```python +from htk.lib.slack.views import slack_webhook_view + +# POST /slack/webhook/ +# Automatically routes to event handlers +``` + +## Common Patterns + +```python +# Get event type +from htk.lib.slack.utils import get_event_type +event_type = get_event_type(event) + +# Handle events +from htk.lib.slack.utils import handle_event +response = handle_event(event) + +# Validate webhook +from htk.lib.slack.utils import is_valid_webhook_event +if is_valid_webhook_event(event): + # Process + pass +``` + +## Configuration + +```python +# settings.py +SLACK_WEBHOOK_URL = os.environ.get('SLACK_WEBHOOK_URL') +SLACK_BOT_TOKEN = os.environ.get('SLACK_BOT_TOKEN') +SLACK_SIGNING_SECRET = os.environ.get('SLACK_SIGNING_SECRET') +``` + +## Features + +- Beacon/location tracking +- Multiple event handlers +- Webhook validation +- Error response handling diff --git a/lib/slack/beacon/README.md b/lib/slack/beacon/README.md new file mode 100644 index 00000000..aa1894ea --- /dev/null +++ b/lib/slack/beacon/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.slack.beacon.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_BEACON_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_BEACON_API_KEY = 'your_api_key' +HTK_BEACON_API_SECRET = 'your_secret' +HTK_BEACON_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_BEACON_ENABLED = True +HTK_BEACON_API_KEY = 'your_key' +HTK_BEACON_TIMEOUT = 30 +HTK_BEACON_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Dont exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/slack/constants/README.md b/lib/slack/constants/README.md new file mode 100644 index 00000000..f91e6b60 --- /dev/null +++ b/lib/slack/constants/README.md @@ -0,0 +1,161 @@ +# Slack Constants + +Configuration settings and constants for Slack integration and bot event handling. + +## Event Handler Configuration + +```python +from htk.lib.slack.constants import ( + HTK_SLACK_EVENT_TYPE_RESOLVER, + HTK_SLACK_EVENT_HANDLERS, + HTK_SLACK_EVENT_HANDLERS_EXTRAS, + HTK_SLACK_EVENT_HANDLER_USAGES, + HTK_SLACK_EVENT_HANDLER_USAGES_EXTRA, +) +``` + +**HTK_SLACK_EVENT_TYPE_RESOLVER** +- Path to function that resolves event types from Slack requests +- Default: `'htk.lib.slack.event_resolvers.default_event_type_resolver'` + +**HTK_SLACK_EVENT_HANDLERS** +- Dictionary mapping event/command types to handler function paths +- Built-in handlers: + - `'default'`, `'bart'`, `'beacon'`, `'bible'`, `'emaildig'`, `'findemail'`, `'geoip'`, `'githubprs'`, `'help'`, `'ohmygreen'`, `'stock'`, `'utcnow'`, `'weather'`, `'zesty'` +- Default handlers located in `htk.lib.slack.event_handlers` + +**HTK_SLACK_EVENT_HANDLERS_EXTRAS** +- Additional custom event handlers +- Default: `{}` (empty) + +**HTK_SLACK_EVENT_HANDLER_USAGES** +- Dictionary mapping handlers to usage/help documentation paths +- Provides help text for each command + +**HTK_SLACK_EVENT_HANDLER_USAGES_EXTRA** +- Additional custom usage documentation +- Default: `{}` (empty) + +## Trigger Commands + +```python +from htk.lib.slack.constants import HTK_SLACK_TRIGGER_COMMAND_WORDS +``` + +**HTK_SLACK_TRIGGER_COMMAND_WORDS** +- Tuple of trigger words that are also commands +- Default: `('bart', 'bible', 'findemail', 'stock', 'weather')` +- Used for parsing Slack message text + +## Notifications Configuration + +```python +from htk.lib.slack.constants import ( + HTK_SLACK_NOTIFICATIONS_ENABLED, + HTK_SLACK_BOT_ENABLED, +) +``` + +**HTK_SLACK_NOTIFICATIONS_ENABLED** +- Boolean flag to enable/disable Slack notifications +- Default: `False` + +**HTK_SLACK_BOT_ENABLED** +- Boolean flag to enable/disable Slack bot functionality +- Default: `False` + +## Notification Channels + +```python +from htk.lib.slack.constants import ( + HTK_SLACK_NOTIFICATION_CHANNELS, + HTK_SLACK_DEBUG_CHANNEL, + HTK_SLACK_CELERY_NOTIFICATIONS_CHANNEL, +) +``` + +**HTK_SLACK_NOTIFICATION_CHANNELS** +- Dictionary mapping alert severity levels to Slack channel IDs +- Severity levels: `'critical'`, `'severe'`, `'danger'`, `'warning'`, `'info'`, `'debug'` +- Default channels: + - `'critical'`: `'#alerts-p0-critical'` + - `'severe'`: `'#alerts-p1-severe'` + - `'danger'`: `'#alerts-p2-danger'` + - `'warning'`: `'#alerts-p3-warning'` + - `'info'`: `'#alerts-p4-info'` + - `'debug'`: `'#alerts-p5-debug'` + +**HTK_SLACK_DEBUG_CHANNEL** +- Debug channel for testing notifications +- Default: `'#test'` + +**HTK_SLACK_CELERY_NOTIFICATIONS_CHANNEL** +- Channel for Celery task notifications +- Default: `None` + +## URL and Task Configuration + +```python +from htk.lib.slack.constants import ( + HTK_SLACK_BEACON_URL_NAME, + HTK_SLACK_CELERY_TASK_GITHUB_PRS, +) +``` + +**HTK_SLACK_BEACON_URL_NAME** +- Django URL name for Beacon feature +- Default: `None` + +**HTK_SLACK_CELERY_TASK_GITHUB_PRS** +- Celery task path for GitHub PR notifications +- Default: `None` + +## Webhook Parameters + +```python +from htk.lib.slack.constants import SLACK_WEBHOOK_PARAMS +``` + +**SLACK_WEBHOOK_PARAMS** +- Tuple of required parameters for Slack webhook validation +- Parameters: `token`, `team_id`, `team_domain`, `channel_id`, `channel_name`, `timestamp`, `user_id`, `user_name`, `text`, `trigger_word` + +## Example Usage + +```python +from htk.lib.slack.constants import ( + HTK_SLACK_NOTIFICATION_CHANNELS, + HTK_SLACK_EVENT_HANDLERS, +) + +# Send to critical alerts channel +critical_channel = HTK_SLACK_NOTIFICATION_CHANNELS['critical'] + +# Get handler for an event +handler_path = HTK_SLACK_EVENT_HANDLERS.get('bible') +``` + +## Configuration in settings.py + +```python +HTK_SLACK_NOTIFICATIONS_ENABLED = True +HTK_SLACK_BOT_ENABLED = True + +HTK_SLACK_NOTIFICATION_CHANNELS = { + 'critical': '#alerts-critical', + 'severe': '#alerts-severe', + 'danger': '#alerts-danger', + 'warning': '#alerts-warning', + 'info': '#alerts-info', + 'debug': '#test-alerts', +} + +HTK_SLACK_DEBUG_CHANNEL = '#debug' + +HTK_SLACK_EVENT_HANDLERS_EXTRAS = { + 'custom_command': 'myapp.slack_handlers.custom_command', +} + +HTK_SLACK_BEACON_URL_NAME = 'beacon_view' +HTK_SLACK_CELERY_TASK_GITHUB_PRS = 'myapp.tasks.github_prs' +``` diff --git a/lib/songselect/README.md b/lib/songselect/README.md new file mode 100644 index 00000000..80ca5a25 --- /dev/null +++ b/lib/songselect/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.songselect.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_SONGSELECT_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_SONGSELECT_API_KEY = 'your_api_key' +HTK_SONGSELECT_API_SECRET = 'your_secret' +HTK_SONGSELECT_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_SONGSELECT_ENABLED = True +HTK_SONGSELECT_API_KEY = 'your_key' +HTK_SONGSELECT_TIMEOUT = 30 +HTK_SONGSELECT_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/stripe_lib/README.md b/lib/stripe_lib/README.md index 43bbc66a..9f5b9cd8 100644 --- a/lib/stripe_lib/README.md +++ b/lib/stripe_lib/README.md @@ -1,50 +1,68 @@ -Stripe Lib for django-htk -========================= - -Testing - -https://stripe.com/docs/testing - -In test mode, you can use these test cards to simulate a successful transaction: - -Number Card type -4242 4242 4242 4242 Visa -4012 8888 8888 1881 Visa -4000 0566 5566 5556 Visa (debit) -5555 5555 5555 4444 MasterCard -5200 8282 8282 8210 MasterCard (debit) -5105 1051 0510 5100 MasterCard (prepaid) -3782 822463 10005 American Express -3714 496353 98431 American Express -6011 1111 1111 1117 Discover -6011 0009 9013 9424 Discover -3056 9309 0259 04 Diners Club -3852 0000 0232 37 Diners Club -3530 1113 3330 0000 JCB -3566 0020 2036 0505 JCB - -In addition, these cards will produce specific responses that are useful for testing different scenarios: - -Number Description -4000 0000 0000 0010 With default account settings, charge will succeed but address_line1_check and address_zip_check will both fail. -4000 0000 0000 0028 With default account settings, charge will succeed but address_line1_check will fail. -4000 0000 0000 0036 With default account settings, charge will succeed but address_zip_check will fail. -4000 0000 0000 0044 With default account settings, charge will succeed but address_zip_check and address_line1_check will both be unchecked. -4000 0000 0000 0101 With default account settings, charge will succeed but cvc_check will fail if a CVC is entered. -4000 0000 0000 0341 Attaching this card to a Customer object will succeed, but attempts to charge the customer will fail. -4000 0000 0000 0002 Charges with this card will always be declined with a card_declined code. -4000 0000 0000 0127 Charge will be declined with an incorrect_cvc code. -4000 0000 0000 0069 Charge will be declined with an expired_card code. -4000 0000 0000 0119 Charge will be declined with a processing_error code. - -Additional test mode validation: By default, passing address or CVC data with the card number will cause the address and CVC checks to succeed. If not specified, the value of the checks will be null. Any expiration date in the future will be considered valid. - -How do I test specific error codes? - -Some suggestions: - -card_declined: Use this special card number - 4000000000000002. -incorrect_number: Use a number that fails the Luhn check, e.g. 4242424242424241. -invalid_expiry_month: Use an invalid month e.g. 13. -invalid_expiry_year: Use a year in the past e.g. 1970. -invalid_cvc: Use a two digit number e.g. 99. +# Stripe Integration + +Payment processing, subscriptions, and invoice management. + +## Quick Start + +```python +from htk.apps.stripe_lib.utils import create_customer, charge_card + +# Create customer +customer = create_customer(user, stripe_token) + +# Charge card (one-time) +charge = charge_card(customer, amount=1000, currency='usd') + +# Create subscription +subscription = customer.create_subscription(plan_id='price_xxx') + +# Change plan +customer.change_subscription_plan(subscription_id, new_plan='price_yyy') +``` + +## Models + +- **`BaseStripeCustomer`** - Stripe customer linked to user +- **`BaseStripeSubscription`** - Recurring subscription +- **`BaseStripeProduct`** - Product/plan +- **`BaseStripePrice`** - Pricing + +## Common Patterns + +```python +# List charges +charges = customer.get_charges() + +# Create invoice +invoice = customer.create_invoice() + +# Add card +customer.add_card(stripe_token) + +# Cancel subscription +customer.cancel_subscription(subscription_id) +``` + +## Webhooks + +```python +from htk.lib.stripe_lib.utils import handle_event + +# POST /stripe/webhook/ +# Automatically handles Stripe events +``` + +## Configuration + +```python +# settings.py +STRIPE_API_KEY = os.environ.get('STRIPE_SECRET_KEY') +STRIPE_PUBLIC_KEY = os.environ.get('STRIPE_PUBLIC_KEY') +``` + +## Forms + +```python +from htk.lib.stripe_lib.forms import CreditCardForm +# Render credit card form safely +``` diff --git a/lib/stripe_lib/constants/README.md b/lib/stripe_lib/constants/README.md new file mode 100644 index 00000000..6c57708d --- /dev/null +++ b/lib/stripe_lib/constants/README.md @@ -0,0 +1,96 @@ +# Stripe Library Constants + +## Overview + +This module provides configuration and reference constants for Stripe payment API integration. + +## Configuration Settings + +```python +from htk.lib.stripe_lib.constants import ( + HTK_STRIPE_LIVE_MODE, + HTK_STRIPE_EVENT_HANDLERS, + HTK_STRIPE_LOG_UNHANDLED_EVENTS, + HTK_STRIPE_LOG_TEST_MODE_EVENTS, + HTK_STRIPE_EVENT_LOGGER, + HTK_STRIPE_CUSTOMER_HOLDER_RELATED_NAME +) + +# Use production or test Stripe keys +HTK_STRIPE_LIVE_MODE = True + +# Maps Stripe event types to handler function paths +HTK_STRIPE_EVENT_HANDLERS = {} + +# Log Stripe events that don't have handlers +HTK_STRIPE_LOG_UNHANDLED_EVENTS = True + +# Log test mode events in production +HTK_STRIPE_LOG_TEST_MODE_EVENTS = True + +# Where to log events ('rollbar', etc.) +HTK_STRIPE_EVENT_LOGGER = 'rollbar' + +# Django model relationship name for customer owner +HTK_STRIPE_CUSTOMER_HOLDER_RELATED_NAME = 'customer' +``` + +## Stripe ID Prefixes + +```python +from htk.lib.stripe_lib.constants import ( + STRIPE_ID_PREFIX_CARD, + STRIPE_ID_PREFIX_CHARGE, + STRIPE_ID_PREFIX_CUSTOMER, + STRIPE_ID_PREFIX_TOKEN +) + +# Stripe object ID prefixes +STRIPE_ID_PREFIX_CARD = 'card_' +STRIPE_ID_PREFIX_CHARGE = 'ch_' +STRIPE_ID_PREFIX_CUSTOMER = 'cus_' +STRIPE_ID_PREFIX_TOKEN = 'tok_' +``` + +## Test Cards + +```python +from htk.lib.stripe_lib.constants import STRIPE_TEST_CARDS, DEFAULT_STRIPE_CURRENCY + +# Test card numbers for different card types +test_cards = STRIPE_TEST_CARDS +# { +# 'visa': '4242424242424242', +# 'visa_debit': '4000056655665556', +# 'mc': '5555555555554444', +# 'amex': '378282246310005', +# ... +# } + +# Default currency for Stripe charges +DEFAULT_STRIPE_CURRENCY = 'usd' +``` + +## Usage + +```python +from htk.lib.stripe_lib.constants import STRIPE_TEST_CARDS, DEFAULT_STRIPE_CURRENCY + +# Use test card in development +test_card = STRIPE_TEST_CARDS.get('visa') + +# Create charge with default currency +charge_amount_cents = 9999 # $99.99 +``` + +## Customization + +Override settings in `settings.py`: + +```python +HTK_STRIPE_LIVE_MODE = False # Use test mode +HTK_STRIPE_EVENT_HANDLERS = { + 'charge.succeeded': 'myapp.handlers.handle_charge_succeeded', + 'charge.failed': 'myapp.handlers.handle_charge_failed', +} +``` diff --git a/lib/twitter/README.md b/lib/twitter/README.md new file mode 100644 index 00000000..be3fc57f --- /dev/null +++ b/lib/twitter/README.md @@ -0,0 +1,31 @@ +# Twitter Integration + +Twitter API for tweets, users, and social data. + +## Quick Start + +```python +from htk.lib.twitter.utils import search_tweets, lookup_users_by_id, get_followers + +# Search tweets +results = search_tweets(keyword='#python') + +# Look up users +users = lookup_users_by_id([123456, 789012]) + +# Get followers +followers = get_followers(user_id='12345') + +# Get followers IDs +follower_ids = get_followers_ids(user_id='12345') +``` + +## Configuration + +```python +# settings.py +TWITTER_API_KEY = os.environ.get('TWITTER_API_KEY') +TWITTER_API_SECRET = os.environ.get('TWITTER_API_SECRET') +TWITTER_ACCESS_TOKEN = os.environ.get('TWITTER_ACCESS_TOKEN') +TWITTER_ACCESS_TOKEN_SECRET = os.environ.get('TWITTER_ACCESS_TOKEN_SECRET') +``` diff --git a/lib/yahoo/README.md b/lib/yahoo/README.md new file mode 100644 index 00000000..d071b6f8 --- /dev/null +++ b/lib/yahoo/README.md @@ -0,0 +1,30 @@ +# Yahoo Integration + +Yahoo services including finance, sports, and groups. + +## Quick Start + +```python +from htk.lib.yahoo.finance.utils import get_stock_price, get_stock_info_and_historical_data +from htk.lib.yahoo.sports.fantasy.utils import get_yahoo_fantasy_sports_client_for_user + +# Get stock price +price = get_stock_price('AAPL') + +# Get stock info and history +info = get_stock_info_and_historical_data('AAPL') + +# Get fantasy sports client +client = get_yahoo_fantasy_sports_client_for_user(user) +leagues = client.get_user_leagues(user_id) +rosters = client.get_user_leagues_rosters(user_id) +``` + +## Configuration + +```python +# settings.py +YAHOO_API_KEY = os.environ.get('YAHOO_API_KEY') +YAHOO_OAUTH_CLIENT_ID = os.environ.get('YAHOO_OAUTH_CLIENT_ID') +YAHOO_OAUTH_CLIENT_SECRET = os.environ.get('YAHOO_OAUTH_CLIENT_SECRET') +``` diff --git a/lib/yahoo/finance/README.md b/lib/yahoo/finance/README.md new file mode 100644 index 00000000..1bc881fe --- /dev/null +++ b/lib/yahoo/finance/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.yahoo.finance.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_FINANCE_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_FINANCE_API_KEY = 'your_api_key' +HTK_FINANCE_API_SECRET = 'your_secret' +HTK_FINANCE_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_FINANCE_ENABLED = True +HTK_FINANCE_API_KEY = 'your_key' +HTK_FINANCE_TIMEOUT = 30 +HTK_FINANCE_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Dont exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/yahoo/groups/README.md b/lib/yahoo/groups/README.md new file mode 100644 index 00000000..d80979c0 --- /dev/null +++ b/lib/yahoo/groups/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.yahoo.groups.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_GROUPS_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_GROUPS_API_KEY = 'your_api_key' +HTK_GROUPS_API_SECRET = 'your_secret' +HTK_GROUPS_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_GROUPS_ENABLED = True +HTK_GROUPS_API_KEY = 'your_key' +HTK_GROUPS_TIMEOUT = 30 +HTK_GROUPS_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Dont exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/yahoo/sports/README.md b/lib/yahoo/sports/README.md new file mode 100644 index 00000000..01b78aca --- /dev/null +++ b/lib/yahoo/sports/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.yahoo.sports.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_SPORTS_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_SPORTS_API_KEY = 'your_api_key' +HTK_SPORTS_API_SECRET = 'your_secret' +HTK_SPORTS_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_SPORTS_ENABLED = True +HTK_SPORTS_API_KEY = 'your_key' +HTK_SPORTS_TIMEOUT = 30 +HTK_SPORTS_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Dont exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/yahoo/sports/fantasy/README.md b/lib/yahoo/sports/fantasy/README.md index d8e24d80..037f1c7f 100644 --- a/lib/yahoo/sports/fantasy/README.md +++ b/lib/yahoo/sports/fantasy/README.md @@ -1,6 +1,137 @@ -Yahoo Fantasy Sports API Django/Python Wrapper -============================================== +# Integration -To test, add this entry into the root URLs conf, and visit that resource: +## Overview - `url(r'^lib/yahoo_fantasy/', include('htk.lib.yahoo.fantasysports.urls')),` +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.yahoo.sports.fantasy.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_FANTASY_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_FANTASY_API_KEY = 'your_api_key' +HTK_FANTASY_API_SECRET = 'your_secret' +HTK_FANTASY_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_FANTASY_ENABLED = True +HTK_FANTASY_API_KEY = 'your_key' +HTK_FANTASY_TIMEOUT = 30 +HTK_FANTASY_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Dont exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/yahoo/sports/scripts/README.md b/lib/yahoo/sports/scripts/README.md new file mode 100644 index 00000000..ab928d5f --- /dev/null +++ b/lib/yahoo/sports/scripts/README.md @@ -0,0 +1,51 @@ +# Yahoo Sports Player Data Scripts + +Scripts and utilities for downloading and parsing Yahoo Sports player data. + +## Classes + +**YahooSportsPlayer** +- Represents a Yahoo Sports player with career information +- Attributes: `player_id`, `player_name`, `position`, `team_abbrev`, `team_name` +- Factory method: `player_from_table_row(tr)` - Creates player from HTML table row + +## Functions + +**download_players(sport, letter)** +- Downloads Yahoo Sports players for a specific sport and starting letter +- Supports sports: `nfl`, `mlb`, `nba`, `nhl` +- Returns list of YahooSportsPlayer objects +- Uses BeautifulSoup to parse HTML response +- Filters rows by CSS class `ysprow1` and `ysprow2` + +## Example Usage + +```python +from htk.lib.yahoo.sports.scripts.download_player_ids import ( + download_players, + YahooSportsPlayer, +) + +# Download all NFL players starting with 'A' +players = download_players('nfl', 'A') + +for player in players: + print(f"{player.player_name} - {player.position} ({player.team_abbrev})") + # Example: "Aaron Rodgers - QB (GB)" +``` + +## Script Execution + +The script includes a main execution block that downloads all players for all sports: + +```bash +python download_player_ids.py +``` + +This will download and print player data for: +- NFL (National Football League) +- MLB (Major League Baseball) +- NBA (National Basketball Association) +- NHL (National Hockey League) + +Players are fetched in alphabetical order with a 0.2 second delay between sport requests to avoid rate limiting. diff --git a/lib/yelp/README.md b/lib/yelp/README.md new file mode 100644 index 00000000..6d4ec465 --- /dev/null +++ b/lib/yelp/README.md @@ -0,0 +1,37 @@ +# Yelp Integration + +Business information and review data. + +## Quick Start + +```python +from htk.lib.yelp.api import business_lookup + +# Get business details +business = business_lookup(business_id='google-san-francisco') +print(business['name'], business['rating'], business['reviews']) +``` + +## Operations + +```python +from htk.lib.yelp.utils import YelpAPI + +api = YelpAPI() + +# Business search +results = api.search('restaurants', location='San Francisco') + +# Get reviews +reviews = api.get_reviews(business_id) + +# Get photos +photos = api.get_photos(business_id) +``` + +## Configuration + +```python +# settings.py +YELP_API_KEY = os.environ.get('YELP_API_KEY') +``` diff --git a/lib/yelp/constants/README.md b/lib/yelp/constants/README.md new file mode 100644 index 00000000..690db469 --- /dev/null +++ b/lib/yelp/constants/README.md @@ -0,0 +1,30 @@ +# Yelp API Constants + +Configuration constants for Yelp API integration. + +## Configuration Settings + +```python +from htk.lib.yelp.constants import HTK_YELP_CLIENT_ID, HTK_YELP_API_KEY +``` + +## API Authentication + +Configure Yelp API credentials in Django settings: + +```python +# settings.py +HTK_YELP_CLIENT_ID = 'your-client-id' +HTK_YELP_API_KEY = 'your-api-key' +``` + +## Usage Example + +```python +from htk.lib.yelp.constants import HTK_YELP_API_KEY + +# Use the API key for authentication +headers = { + 'Authorization': f'Bearer {HTK_YELP_API_KEY}', +} +``` diff --git a/lib/zesty/README.md b/lib/zesty/README.md new file mode 100644 index 00000000..43b7208c --- /dev/null +++ b/lib/zesty/README.md @@ -0,0 +1,28 @@ +# Zesty Integration + +Meal planning and corporate catering service. + +## Quick Start + +```python +from htk.lib.zesty.classes import ZestyMeals, get_meal_today, get_pretty_menu + +# Get today's meal +meal = get_meal_today(zesty_id) + +# Get meals for date range +meals = get_meals(start_date, end_date) + +# Get pretty menu for display +menu = get_pretty_menu(meal, date) + +# Get menu as SSML (for voice assistants) +ssml = get_menu_ssml(meal, date) +``` + +## Configuration + +```python +# settings.py +ZESTY_API_KEY = os.environ.get('ZESTY_API_KEY') +``` diff --git a/lib/zillow/README.md b/lib/zillow/README.md new file mode 100644 index 00000000..575c3027 --- /dev/null +++ b/lib/zillow/README.md @@ -0,0 +1,137 @@ +# Integration + +## Overview + +This integration integrates with an external service, providing a Python client and utilities for common operations. + +## Quick Start + +### Initialize Client + +```python +from htk.lib.zillow.utils import Client + +# Create client with credentials +client = Client(api_key='your_api_key') + +# Or use from settings +client = Client() # Uses HTK_ZILLOW_API_KEY from settings +``` + +### Basic Operation + +```python +# Get resource +resource = client.get_resource(id='resource_id') + +# List resources +resources = client.list_resources(limit=10) + +# Create resource +new_resource = client.create_resource(name='My Resource') +``` + +## Operations + +### Read Operations + +```python +# Get single resource +resource = client.get(id='123') + +# List resources +resources = client.list(limit=10, offset=0) + +# Search +results = client.search(query='search term') + +# Count +count = client.count() +``` + +### Write Operations + +```python +# Create resource +new = client.create(name='test') + +# Update resource +updated = client.update(id='123', name='new name') + +# Delete resource +client.delete(id='123') +``` + +## Authentication + +Configure credentials: + +```python +# settings.py +HTK_ZILLOW_API_KEY = 'your_api_key' +HTK_ZILLOW_API_SECRET = 'your_secret' +HTK_ZILLOW_API_URL = 'https://api.service.com' +``` + +## Response Format + +API responses are returned as Python dictionaries or objects: + +```python +result = client.get(id='123') +print(result['name']) +print(result['created_at']) +``` + +## Pagination + +Handle paginated responses: + +```python +# Get paginated results +items = client.list(limit=100, offset=0) + +# Or use iterator +for item in client.list_all(): + process(item) +``` + +## Caching + +Cache responses when appropriate: + +```python +from django.core.cache import cache + +def get_resource(id): + cache_key = f'resource_{id}' + resource = cache.get(cache_key) + + if resource is None: + resource = client.get(id=id) + cache.set(cache_key, resource, 3600) + + return resource +``` + +## Configuration + +Configure in Django settings: + +```python +# settings.py +HTK_ZILLOW_ENABLED = True +HTK_ZILLOW_API_KEY = 'your_key' +HTK_ZILLOW_TIMEOUT = 30 +HTK_ZILLOW_RETRIES = 3 +``` + +## Best Practices + +1. **Handle errors** - Always handle API errors gracefully +2. **Respect rate limits** - Don't exceed API rate limits +3. **Cache responses** - Cache data when appropriate +4. **Use retries** - Implement exponential backoff +5. **Validate input** - Validate data before sending to API +6. **Log operations** - Log API calls for debugging +7. **Test with sandbox** - Test in sandbox before production diff --git a/lib/ziprecruiter/README.md b/lib/ziprecruiter/README.md new file mode 100644 index 00000000..dabfbe34 --- /dev/null +++ b/lib/ziprecruiter/README.md @@ -0,0 +1,31 @@ +# ZipRecruiter Integration + +Job posting and recruitment API. + +## Quick Start + +```python +from htk.lib.ziprecruiter.api import ZipRecruiterAPI + +api = ZipRecruiterAPI() + +# Create job +job = api.create_job({ + 'title': 'Software Engineer', + 'description': 'Job description...', + 'location': 'San Francisco, CA' +}) + +# Update job +api.update_job(job_id, {'title': 'Senior Software Engineer'}) + +# Delete job +api.delete_job(job_id) +``` + +## Configuration + +```python +# settings.py +ZIPRECRUITER_API_KEY = os.environ.get('ZIPRECRUITER_API_KEY') +``` diff --git a/lib/zuora/README.md b/lib/zuora/README.md new file mode 100644 index 00000000..0e446686 --- /dev/null +++ b/lib/zuora/README.md @@ -0,0 +1,44 @@ +# Zuora Integration + +Billing, subscriptions, and revenue management. + +## Quick Start + +```python +from htk.lib.zuora.api import get_subscription, update_subscription, cancel_subscription + +# Get subscription +subscription = get_subscription(subscription_id) + +# Update subscription +updated = update_subscription(subscription_id, {'status': 'Active'}) + +# Cancel subscription +cancel_subscription(subscription_id) +``` + +## Operations + +```python +from htk.lib.zuora.utils import ZuoraAPI + +api = ZuoraAPI() + +# Query subscriptions +subs = api.query('select Id, Status from Subscription where AccountId = ?', [account_id]) + +# Create invoice +invoice = api.create_invoice(account_id, subscription_id) + +# Process payment +payment = api.process_payment(account_id, amount) +``` + +## Configuration + +```python +# settings.py +ZUORA_API_ENDPOINT = os.environ.get('ZUORA_API_ENDPOINT') +ZUORA_CLIENT_ID = os.environ.get('ZUORA_CLIENT_ID') +ZUORA_CLIENT_SECRET = os.environ.get('ZUORA_CLIENT_SECRET') +``` diff --git a/lib/zuora/constants/README.md b/lib/zuora/constants/README.md new file mode 100644 index 00000000..2bccb0da --- /dev/null +++ b/lib/zuora/constants/README.md @@ -0,0 +1,64 @@ +# Zuora API Constants + +Configuration constants and API endpoints for Zuora billing integration. + +## Configuration Settings + +```python +from htk.lib.zuora.constants import ( + HTK_ZUORA_CLIENT_ID, + HTK_ZUORA_CLIENT_SECRET, + HTK_ZUORA_COUNTRY, + HTK_ZUORA_PROD, + HTK_ZUORA_HANDLE_UNHANDLED_EVENTS, + HTK_ZUORA_EVENT_TYPES, + HTK_ZUORA_EVENT_HANDLERS, + HTK_ZUORA_API_BASE_URLS, +) +``` + +## OAuth Configuration + +```python +# settings.py +HTK_ZUORA_CLIENT_ID = 'your-client-id' +HTK_ZUORA_CLIENT_SECRET = 'your-client-secret' +HTK_ZUORA_COUNTRY = 'US' # 'US' or 'EU' +HTK_ZUORA_PROD = True # Use production environment +``` + +## API Base URLs + +URLs for different regions and environments: + +```python +HTK_ZUORA_API_BASE_URLS = { + 'US': { + 'prod': 'https://rest.zuora.com/', + 'sandbox': 'https://rest.apisandbox.zuora.com/', + }, + 'EU': { + 'prod': 'https://rest.eu.zuora.com/', + 'sandbox': 'https://rest.sandbox.eu.zuora.com/', + }, +} +``` + +## Event Handling + +```python +# Event types +HTK_ZUORA_EVENT_TYPES = { + 'default': 'Default (unhandled) event', + 'subscription_created': 'Subscription created', +} + +# Event handler mapping +HTK_ZUORA_EVENT_HANDLERS = { + 'default': 'htk.lib.zuora.event_handlers.default', + 'subscription_created': 'htk.lib.zuora.event_handlers.subscription_created', +} + +# Handle unhandled events +HTK_ZUORA_HANDLE_UNHANDLED_EVENTS = False +``` diff --git a/middleware/README.md b/middleware/README.md new file mode 100644 index 00000000..21752464 --- /dev/null +++ b/middleware/README.md @@ -0,0 +1,230 @@ +# Middleware + +## Overview + +The `middleware` module provides utilities for: + +- Global request access +- Request timing and profiling +- User agent parsing +- Host validation +- Error handling +- Timezone detection + +## Global Request Access + +Access request anywhere in your code without passing it explicitly: + +```python +from htk.middleware.classes import GlobalRequestMiddleware +from htk.middleware.utils import get_current_request + +# Enable in settings.py +MIDDLEWARE = [ + 'htk.middleware.classes.GlobalRequestMiddleware', + # ... +] + +# Access request globally +def some_function(): + request = get_current_request() + user = request.user + host = request.get_host() +``` + +**Use Cases:** +- Access request in utility functions +- Log request context in debugging +- Track request metadata + +## User Agent Parsing + +Automatically parse and expose user agent info: + +```python +from htk.middleware.classes import UserAgentMiddleware + +# Enable in settings.py +MIDDLEWARE = [ + 'htk.middleware.classes.UserAgentMiddleware', + # ... +] + +# Access in views +def my_view(request): + user_agent = request.user_agent + # { + # 'is_mobile': True/False, + # 'is_bot': True/False, + # 'browser': 'Chrome', + # 'os': 'Windows', + # 'device': 'Desktop', + # } +``` + +## Request Timing + +Measure request processing time: + +```python +from htk.middleware.classes import RequestTimerMiddleware + +# Enable in settings.py +MIDDLEWARE = [ + 'htk.middleware.classes.RequestTimerMiddleware', + # ... +] + +# View processing time in response headers +# X-Request-Time: 0.234 seconds +``` + +**Useful for:** +- Performance monitoring +- Identifying slow views +- A/B testing performance +- APM integration + +## Host Validation + +Validate requests against allowed hosts: + +```python +from htk.middleware.classes import AllowedHostsMiddleware + +# settings.py +ALLOWED_HOST_REGEXPS = [ + r'^example\.com$', + r'^subdomain\.example\.com$', + r'^localhost$', +] + +# Enable middleware +MIDDLEWARE = [ + 'htk.middleware.classes.AllowedHostsMiddleware', + # ... +] +``` + +## Error Handling + +Gracefully handle custom HTTP errors: + +```python +from htk.middleware.classes import HttpErrorResponseMiddleware +from htk.utils.http.errors import HttpErrorResponseError + +# Enable in settings.py +MIDDLEWARE = [ + 'htk.middleware.classes.HttpErrorResponseMiddleware', + # ... +] + +# Raise errors in views +def api_endpoint(request): + if not request.user.is_authenticated: + raise HttpErrorResponseError(401, 'Unauthorized') + # ... + +# Middleware catches and formats response +# Returns: {'error': 'Unauthorized'} with 401 status +``` + +## JSON Content-Type Handling + +Fix JSON response content type for IE compatibility: + +```python +from htk.middleware.classes import RewriteJsonResponseContentTypeMiddleware + +# Enable in settings.py +MIDDLEWARE = [ + 'htk.middleware.classes.RewriteJsonResponseContentTypeMiddleware', + # ... +] + +# Ensures application/json content type (not text/html) +``` + +## Timezone Detection + +Auto-detect and set user timezone: + +```python +from htk.middleware.classes import TimezoneMiddleware + +# Enable in settings.py +MIDDLEWARE = [ + 'htk.middleware.classes.TimezoneMiddleware', + # ... +] + +# Automatically detects timezone from IP or user profile +# Sets timezone for datetime operations +``` + +## Request Data Limits + +Prevent overly large requests: + +```python +from htk.middleware.classes import RequestDataTooBigMiddleware + +# Enable in settings.py +MIDDLEWARE = [ + 'htk.middleware.classes.RequestDataTooBigMiddleware', + # ... +] + +# Returns 413 Payload Too Large if request exceeds limit +``` + +## Complete Example + +```python +# settings.py +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + + # HTK middleware + 'htk.middleware.classes.GlobalRequestMiddleware', + 'htk.middleware.classes.UserAgentMiddleware', + 'htk.middleware.classes.RequestTimerMiddleware', + 'htk.middleware.classes.TimezoneMiddleware', + 'htk.middleware.classes.AllowedHostsMiddleware', + 'htk.middleware.classes.RewriteJsonResponseContentTypeMiddleware', + 'htk.middleware.classes.HttpErrorResponseMiddleware', + 'htk.middleware.classes.RequestDataTooBigMiddleware', + + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ALLOWED_HOST_REGEXPS = [ + r'^example\.com$', + r'^www\.example\.com$', +] +``` + +## Best Practices + +1. **Order matters** - Place middleware in logical order +2. **Global request** - Use sparingly, not a replacement for passing request +3. **Timezone handling** - Configure with user locale data +4. **Error responses** - Use HttpErrorResponseError for API errors +5. **Performance** - Monitor with RequestTimerMiddleware + +## Classes + +- **`GlobalRequestMiddleware`** - Store request in thread-local storage +- **`UserAgentMiddleware`** - Parse and expose user agent data +- **`RequestTimerMiddleware`** - Measure request processing time +- **`AllowedHostsMiddleware`** - Validate host against regex patterns +- **`HttpErrorResponseMiddleware`** - Handle custom HTTP errors +- **`RewriteJsonResponseContentTypeMiddleware`** - Fix JSON content type +- **`TimezoneMiddleware`** - Auto-detect user timezone +- **`RequestDataTooBigMiddleware`** - Enforce request size limits diff --git a/models/README.md b/models/README.md new file mode 100644 index 00000000..933e4a1a --- /dev/null +++ b/models/README.md @@ -0,0 +1,222 @@ +# Models & Fields + +Abstract base models and custom field types for Django models. + +## Overview + +The `models` module provides: + +- Abstract base model classes for common patterns +- Custom Django field types (ULID, Cross-DB Foreign Keys) +- JSON serialization helpers +- Model field utilities + +## Base Models + +### HtkBaseModel + +Abstract base class extending Django's Model: + +```python +from htk.models.classes import HtkBaseModel + +class Article(HtkBaseModel): + title = CharField(max_length=200) + content = TextField() + published = BooleanField(default=False) +``` + +**Inherited Features:** +- Timestamp fields (created, updated) +- UUID or ULID primary keys +- JSON serialization +- Consistent model behavior + +## Custom Field Types + +### ULIDField + +Use ULIDs (Universally Unique Lexicographically Sortable Identifiers) instead of UUIDs: + +```python +from htk.models.fields.ulid import ULIDField + +class User(models.Model): + id = ULIDField(primary_key=True) + email = EmailField(unique=True) + +# Usage +user = User.objects.create(email='user@example.com') +print(user.id) # 01ARZ3NDEKTSV4RRFFQ69G5FAV +``` + +**Advantages over UUID:** +- Sortable (chronological ordering) +- Case-insensitive +- No hyphens (more URL-friendly) +- Timestamp-encoded (can determine creation time) + +### CrossDBForeignKey + +Create foreign keys across different databases: + +```python +from htk.models.fields.cross_db_foreign_key import CrossDBForeignKey + +class Order(models.Model): + # Reference User from different database + user_id = CrossDBForeignKey(User, on_delete=models.CASCADE) +``` + +**Use Cases:** +- Multi-database architectures +- Sharded databases +- Legacy data migrations + +## Model Utilities + +### JSON Serialization + +```python +from htk.models.classes import HtkBaseModel + +class Article(HtkBaseModel): + title = CharField(max_length=200) + author = CharField(max_length=100) + + def to_dict(self): + return { + 'id': str(self.id), + 'title': self.title, + 'author': self.author, + 'created': self.created.isoformat(), + } + +# Usage +article = Article.objects.first() +json_data = json.dumps(article.to_dict()) +``` + +### Field Value Normalization + +```python +from htk.models.utils import normalize_model_field_value + +# Normalize value for a specific field +user = User.objects.first() +normalized = normalize_model_field_value(user, 'birth_date', '2000-01-15') +``` + +## Attribute Holder Pattern + +Store arbitrary attributes on models without creating new fields: + +```python +from htk.models.classes import AbstractAttribute, AbstractAttributeHolderClassFactory + +# Create model for storing attributes +AttributeHolder = AbstractAttributeHolderClassFactory(User, 'user_id') + +# Store attributes +attr = AttributeHolder.objects.create( + user=user, + key='preferences', + value={'theme': 'dark', 'notifications': True} +) + +# Retrieve attributes +prefs = AttributeHolder.objects.get(user=user, key='preferences').value +``` + +**Benefits:** +- Flexible schema without migrations +- No need for separate columns +- JSON storage support +- Indexable keys + +## Common Patterns + +### Custom Model with Timestamps + +```python +from htk.models.classes import HtkBaseModel +from django.db import models + +class BlogPost(HtkBaseModel): + title = models.CharField(max_length=200) + content = models.TextField() + author = models.ForeignKey(User, on_delete=models.CASCADE) + published = models.BooleanField(default=False) + + class Meta: + ordering = ['-created'] # Newest first + +# Inherited fields: +# - id: ULIDField (auto) +# - created: DateTimeField (auto) +# - updated: DateTimeField (auto) +``` + +### ULID Primary Keys + +```python +from htk.models.fields.ulid import ULIDField + +class Product(models.Model): + id = ULIDField(primary_key=True) + name = CharField(max_length=100) + price = DecimalField(max_digits=10, decimal_places=2) + +# Usage +product = Product.objects.create( + name='Widget', + price=19.99 +) +# ID is automatically generated as ULID +``` + +### Model Composition + +```python +from htk.models.classes import HtkBaseModel + +class BaseContent(HtkBaseModel): + title = CharField(max_length=200) + body = TextField() + author = ForeignKey(User, on_delete=models.CASCADE) + + class Meta: + abstract = True # Allow inheritance + +class Article(BaseContent): + category = CharField(max_length=50) + +class Page(BaseContent): + slug = SlugField(unique=True) +``` + +## Best Practices + +1. **Extend HtkBaseModel** for new models to get timestamps +2. **Use ULIDField** for sortable, URL-friendly IDs +3. **Use CrossDBForeignKey** only when necessary (multi-db) +4. **Use AttributeHolder** for flexible, schema-less data +5. **Normalize field values** before storage +6. **Override to_dict()** for JSON serialization + +## Classes + +- **`HtkBaseModel`** - Abstract base model with timestamps +- **`AbstractAttribute`** - Store key-value attributes on models +- **`AbstractAttributeHolderClassFactory`** - Factory for attribute holders +- **`ULID`** - ULID wrapper class with utilities +- **`ULIDField`** - Django field type for ULIDs +- **`CrossDBForeignKey`** - Foreign key across databases + +## Functions + +- **`json_encode`** - Serialize model instance to JSON-compatible dict +- **`json_decode`** - Deserialize JSON to model fields +- **`attribute_fields`** - Get list of attribute keys +- **`boolean_attributes_lookup`** - Find boolean-valued attributes +- **`normalize_model_field_value`** - Normalize value for field type diff --git a/models/fields/README.md b/models/fields/README.md new file mode 100644 index 00000000..2ed49818 --- /dev/null +++ b/models/fields/README.md @@ -0,0 +1,265 @@ +# Custom Model Fields + +Django model field types for common patterns and advanced use cases. + +## Quick Start + +```python +from htk.models.fields import ULIDField, CrossDBForeignKey, StarRatingField, IntegerRangeField + +class Product(models.Model): + id = ULIDField(primary_key=True) + name = models.CharField(max_length=200) + rating = StarRatingField(default=0) + price_range = IntegerRangeField(default=(0, 1000)) + +class Review(models.Model): + product = models.ForeignKey(Product, on_delete=models.CASCADE) + score = StarRatingField(min_value=1, max_value=5) +``` + +## ULIDField + +Use ULIDs (Universally Unique Lexicographically Sortable Identifiers) as primary keys or unique identifiers. + +**Advantages over UUID:** +- Sortable by timestamp +- URL-friendly (no hyphens) +- Case-insensitive +- Timestamp-encoded (creation time extractable) +- Shorter representation + +```python +from htk.models.fields import ULIDField + +class Article(models.Model): + id = ULIDField(primary_key=True) + title = models.CharField(max_length=200) + created = models.DateTimeField(auto_now_add=True) + +# Usage +article = Article.objects.create(title='Test') +print(article.id) # 01ARZ3NDEKTSV4RRFFQ69G5FAV + +# ULIDs are sortable - newer IDs are greater +articles = Article.objects.all() # Automatically ordered by ULID (creation time) +``` + +### ULID Structure + +``` +01ARZ3NDEKTSV4RRFFQ69G5FAV +││││││││││││││││││││││││││ +│││││││││└────────────────┘ Randomness (80 bits) +└────────┘────────────────┘ Timestamp (48 bits = milliseconds since epoch) +``` + +### Use Cases + +```python +from htk.models.fields import ULIDField +from datetime import datetime + +# Sortable primary keys +class Event(models.Model): + id = ULIDField(primary_key=True) + name = models.CharField(max_length=100) + +# Events are automatically ordered chronologically +recent_events = Event.objects.order_by('-id') # Newest first + +# Extract creation time from ULID +from ulid import ULID +event = Event.objects.first() +creation_time = ULID(str(event.id)).timestamp() +``` + +## CrossDBForeignKey + +Create foreign key relationships across different databases (multi-database architectures). + +**Use Cases:** +- Multi-database sharding +- Legacy data migrations +- Microservices integration +- Database split scenarios + +```python +from htk.models.fields import CrossDBForeignKey + +class Order(models.Model): + # Reference User from different database + user_id = CrossDBForeignKey( + User, + on_delete=models.CASCADE, + db_constraint=False # No database constraint across databases + ) + order_number = models.CharField(max_length=20) + total = models.DecimalField(max_digits=10, decimal_places=2) + + def get_user(self): + # Manual lookup across databases + return User.objects.using('users_db').get(id=self.user_id) +``` + +### Multi-Database Setup + +```python +# settings.py +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'orders_db', + }, + 'users_db': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'users_db', + } +} + +# Usage +class Order(models.Model): + user_id = CrossDBForeignKey(User, on_delete=models.CASCADE, db_constraint=False) + +# Queries must specify database +user = User.objects.using('users_db').get(id=order.user_id) +``` + +## StarRatingField + +Store and validate star ratings (typically 1-5 stars or custom range). + +```python +from htk.models.fields import StarRatingField + +class Review(models.Model): + product = models.ForeignKey(Product, on_delete=models.CASCADE) + rating = StarRatingField( + min_value=1, + max_value=5, + default=0 + ) + comment = models.TextField() + +# Usage +review = Review.objects.create( + product=product, + rating=4, + comment='Great product!' +) + +# Validates range (1-5) +review.rating = 6 # Will fail validation +review.full_clean() # Raises ValidationError +``` + +### Common Patterns + +```python +from django.db.models import Avg, Q +from htk.models.fields import StarRatingField + +# Get average rating +average = Review.objects.filter(product=product).aggregate( + avg_rating=Avg('rating') +)['avg_rating'] + +# Filter by rating range +highly_rated = Review.objects.filter(rating__gte=4) +poor_reviews = Review.objects.filter(rating__lt=3) + +# Rating distribution +distribution = Review.objects.values('rating').annotate( + count=Count('id') +).order_by('rating') +``` + +## IntegerRangeField + +Store a range of integers (min/max pair) efficiently. + +```python +from htk.models.fields import IntegerRangeField + +class PriceListItem(models.Model): + product = models.ForeignKey(Product, on_delete=models.CASCADE) + # Store price range as a single field + price_range = IntegerRangeField() + +# Usage +item = PriceListItem.objects.create( + product=product, + price_range=(100, 500) # (min, max) +) + +print(item.price_range) # (100, 500) +print(item.price_range[0]) # 100 (min) +print(item.price_range[1]) # 500 (max) +``` + +### Query Ranges + +```python +from htk.models.fields import IntegerRangeField + +# Find items where price is in range +class Inventory(models.Model): + quantity_range = IntegerRangeField() + +# Get inventory with stock between 10-100 +low_stock = Inventory.objects.filter( + quantity_range__gte=(10, 100) +) +``` + +## Best Practices + +1. **ULIDField** - Use for sortable primary keys +2. **CrossDBForeignKey** - Only use in multi-database architectures +3. **StarRatingField** - Validate range in model clean() method +4. **IntegerRangeField** - Use for efficient min/max storage + +## Common Patterns + +### Validating Custom Fields + +```python +from django.core.exceptions import ValidationError + +class Article(models.Model): + id = ULIDField(primary_key=True) + rating = StarRatingField(min_value=1, max_value=5) + word_count_range = IntegerRangeField() + + def clean(self): + super().clean() + # Validate rating + if not (1 <= self.rating <= 5): + raise ValidationError({'rating': 'Rating must be 1-5'}) + + # Validate range + min_words, max_words = self.word_count_range + if min_words >= max_words: + raise ValidationError({'word_count_range': 'Min must be less than max'}) +``` + +### Database Query Examples + +```python +from django.db.models import Avg, Count, Q +from htk.models.fields import ULIDField, StarRatingField + +# Get trending items (high rating + recent ULID) +trending = Product.objects.annotate( + avg_rating=Avg('review__rating') +).filter( + avg_rating__gte=4, + id__gte=threshold_ulid # Recent ULIDs +).order_by('-id') + +# Rating distribution +distribution = Review.objects.values('rating').annotate( + count=Count('id'), + percentage=Count('id') * 100.0 / Count('*') +).order_by('-rating') +``` diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..9e62b783 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,95 @@ +# Scripts + +## Quick Start + +```python +from htk.scripts.utils import job_runner + +# Define a job function +def update_user_stats(): + print("Updating user statistics...") + for user in User.objects.all(): + user.update_stats() + +# Run the job +job_runner(update_user_stats) +``` + +## Job Runner + +Execute any callable function as a job: + +```python +from htk.scripts.utils import job_runner + +# Run simple function +def sync_external_data(): + # Long-running operation + pass + +job_runner(sync_external_data) + +# Run with arguments +def process_user(user_id): + user = User.objects.get(id=user_id) + # Process user + pass + +job_runner(lambda: process_user(123)) +``` + +## Management Commands + +Use with Django management commands: + +```python +from django.core.management.base import BaseCommand +from htk.scripts.utils import job_runner + +class Command(BaseCommand): + def handle(self, *args, **options): + job_runner(self.sync_data) + + def sync_data(self): + # Sync logic + pass +``` + +## Testing Scripts + +```python +# Scripts are tested in tests.py +from django.test import TestCase +from htk.scripts.utils import job_runner + +class ScriptTestCase(TestCase): + def test_job_runner(self): + # Job should complete successfully + job_runner(lambda: print("test")) +``` + +## Common Patterns + +### Batch Processing + +```python +from htk.scripts.utils import job_runner + +def batch_process_orders(): + orders = Order.objects.filter(status='pending') + for order in orders: + order.process() + order.save() + +job_runner(batch_process_orders) +``` + +### Scheduled Tasks + +```python +# With Celery or APScheduler +from htk.scripts.utils import job_runner + +def scheduled_sync(): + job_runner(sync_external_api) +``` diff --git a/south_migrations/README.md b/south_migrations/README.md new file mode 100644 index 00000000..ba83bf07 --- /dev/null +++ b/south_migrations/README.md @@ -0,0 +1,86 @@ +# Migrations + +## Overview + +This directory contains database migration files that track schema changes over time. Migrations are executed in order to evolve the database structure while preserving data. + +## Migration Files + +Each migration file represents a specific change to the database schema: + +``` +migrations/ +├── 0001_initial.py # Initial schema creation +├── 0002_add_field.py # Add new field +├── 0003_remove_field.py # Remove deprecated field +└── ... +``` + +## Running Migrations + +### Apply All Migrations + +```bash +python manage.py migrate +``` + +### Apply Specific App + +```bash +python manage.py migrate [app_name] +``` + +### Create New Migration + +```bash +python manage.py makemigrations [app_name] +``` + +### Show Migration Status + +```bash +python manage.py showmigrations +``` + +### Rollback Migrations + +```bash +# Rollback to specific migration +python manage.py migrate [app_name] [migration_number] + +# Rollback all migrations for an app +python manage.py migrate [app_name] zero +``` + +## Creating Migrations + +### From Model Changes + +```bash +# After modifying models.py, create a migration +python manage.py makemigrations + +# Review the generated migration before applying +python manage.py showmigrations + +# Apply the migration +python manage.py migrate +``` + +### Manual Migrations + +For complex operations, create data migrations: + +```bash +python manage.py makemigrations --empty [app_name] --name [description] +``` + +## Best Practices + +1. **Create descriptive names** - Use meaningful migration names +2. **Review before applying** - Always check migrations before running +3. **Keep migrations small** - Easier to debug and reverse if needed +4. **Test in development** - Always test migrations locally first +5. **Back up production** - Before applying migrations to production +6. **Use data migrations** - For complex data transformations +7. **Document changes** - Add comments explaining why changes were made diff --git a/static/htk/images/logo/logo.png b/static/htk/images/logo/logo.png new file mode 100644 index 00000000..dd234aaf Binary files /dev/null and b/static/htk/images/logo/logo.png differ diff --git a/templatetags/README.md b/templatetags/README.md new file mode 100644 index 00000000..08c3bedd --- /dev/null +++ b/templatetags/README.md @@ -0,0 +1,118 @@ +# Template Tags + +Django template filters and tags for common operations. + +## Quick Start + +```django +{% load htk_tags %} + + +{{ text|markdownify }} + + +{{ data|btoa }} +{{ encoded|atob }} + + +{{ phone_number|phonenumber:"US" }} + + +{% qrcode_image_url text as qr_url %} +QR Code +``` + +## Available Tags + +### Text Processing + +```django + +{{ markdown_text|markdownify }} + + +{{ secret_text|obfuscate }} + + +{{ email|obfuscate_mailto }} + + +{{ "2025551234"|phonenumber:"US" }} +``` + +### Encoding/Decoding + +```django + +{{ text|btoa }} + + +{{ encoded|atob }} +``` + +### Django Settings + +```django + +{% get_django_setting "DEBUG" as debug_mode %} +{% if debug_mode %} +
Debug mode enabled
+{% endif %} +``` + +### Asset Versions + +```django + +{% lesscss "main.css" %} + + +{% loadjs "app.js" %} +``` + +### Template Rendering + +```django + +{% render_string template_string with context_var=value %} +``` + +### Redirect Links + +```django + +{% redir "example.com" %} + + +{% redir_trunc "https://example.com/very/long/url" max_length=30 %} +``` + +## Common Patterns + +### Dynamic Content with Markdown + +```django +
+ {{ post.content|markdownify }} +
+``` + +### Email Obfuscation + +```django + +

Contact us: {{ "support@example.com"|obfuscate_mailto }}

+``` + +### QR Code Generation + +```django +{% qrcode_image_url request.build_absolute_uri as qr_url %} +Share this page +``` + +### HTTP Header Formatting + +```django +{% http_header "CONTENT_TYPE" %} +``` diff --git a/test_scaffold/README.md b/test_scaffold/README.md new file mode 100644 index 00000000..d085069e --- /dev/null +++ b/test_scaffold/README.md @@ -0,0 +1,137 @@ +# Test Scaffold + +Base classes and utilities for testing. + +## Quick Start + +```python +from htk.test_scaffold.tests import BaseTestCase, BaseWebTestCase +from htk.test_scaffold.utils import create_test_user, create_test_username + +class MyTestCase(BaseTestCase): + def setUp(self): + super().setUp() + self.user = create_test_user() + + def test_something(self): + self.assertIsNotNone(self.user) +``` + +## Base Test Cases + +### BaseTestCase + +Base class for all tests with common setup: + +```python +from htk.test_scaffold.tests import BaseTestCase + +class ArticleTestCase(BaseTestCase): + def test_article_creation(self): + # Basic test methods + self.assertIsNotNone(article) +``` + +### BaseWebTestCase + +Extended base for web/integration tests: + +```python +from htk.test_scaffold.tests import BaseWebTestCase + +class ArticleWebTestCase(BaseWebTestCase): + def test_article_view(self): + response = self.client.get('/articles/') + self.assertEqual(response.status_code, 200) +``` + +## Utilities + +### User Creation + +```python +from htk.test_scaffold.utils import create_test_user, create_test_username + +# Create test user with random username +user = create_test_user() +username = user.username # auto-generated + +# Generate random username +username = create_test_username() +``` + +## Fake Time + +Mock system time for testing time-dependent features: + +```python +from htk.test_scaffold.models import set_fake_timestamp +from datetime import datetime + +# Set fake timestamp +fake_time = datetime(2024, 1, 1, 12, 0, 0) +set_fake_timestamp(fake_time) + +# Your test runs with fake time +# Reset in tearDown +``` + +## Fake Prelaunch + +Mock prelaunch status: + +```python +from htk.test_scaffold.models import FakePrelaunch + +# Set prelaunch mode +prelaunch = FakePrelaunch.objects.create(is_enabled=True) +``` + +## Common Patterns + +### Testing with Test Users + +```python +from htk.test_scaffold.tests import BaseTestCase +from htk.test_scaffold.utils import create_test_user + +class UserAuthTestCase(BaseTestCase): + def setUp(self): + super().setUp() + self.user = create_test_user() + self.admin = create_test_user() + + def test_user_login(self): + self.client.login( + username=self.user.username, + password='password' + ) + response = self.client.get('/profile/') + self.assertEqual(response.status_code, 200) +``` + +### Testing Time-Dependent Logic + +```python +from htk.test_scaffold.tests import BaseTestCase +from htk.test_scaffold.models import set_fake_timestamp +from datetime import datetime + +class TimeBasedTestCase(BaseTestCase): + def test_expiration(self): + # Set time to before expiration + set_fake_timestamp(datetime(2024, 1, 1)) + self.assertTrue(offer.is_valid()) + + # Move time forward + set_fake_timestamp(datetime(2024, 2, 1)) + self.assertFalse(offer.is_valid()) +``` + +## Best Practices + +1. **Extend BaseTestCase** for all tests +2. **Use create_test_user()** for test users +3. **Set up in setUp()** method +4. **Tear down in tearDown()** method +5. **Use fake time** for time-sensitive tests diff --git a/test_scaffold/constants/README.md b/test_scaffold/constants/README.md new file mode 100644 index 00000000..63f18122 --- /dev/null +++ b/test_scaffold/constants/README.md @@ -0,0 +1,49 @@ +# Test Scaffold Constants + +## Overview + +This module provides configuration constants for test environment setup and test data generation. + +## Constants + +```python +from htk.test_scaffold.constants import TESTSERVER, HTK_TEST_EMAIL_DOMAIN + +# Django test server hostname +TESTSERVER = 'testserver' + +# Domain to use for test email addresses +HTK_TEST_EMAIL_DOMAIN = 'hacktoolkit.com' +``` + +## Usage Examples + +### Generate Test Email + +```python +from htk.test_scaffold.constants import HTK_TEST_EMAIL_DOMAIN + +def create_test_user(username): + """Create a test user with a test domain email.""" + email = f"{username}@{HTK_TEST_EMAIL_DOMAIN}" + # Create user with email + return email +``` + +### Test Server Validation + +```python +from htk.test_scaffold.constants import TESTSERVER + +def is_test_environment(request): + """Check if running on test server.""" + return request.get_host().startswith(TESTSERVER) +``` + +## Customization + +Override these settings in test settings.py: + +```python +HTK_TEST_EMAIL_DOMAIN = 'test.local' +``` diff --git a/utils/README.md b/utils/README.md new file mode 100644 index 00000000..f4c3be91 --- /dev/null +++ b/utils/README.md @@ -0,0 +1,338 @@ +# Utilities + +## Overview + +The `utils` module provides practical utilities organized by purpose: + +- **Text Processing** - Formatting, sanitization, translation, transformations +- **Caching** - Memoization and cached attributes +- **Data Structures** - Dictionaries, enums, collections +- **DateTime** - Timezone handling, conversions, scheduling +- **HTTP/Requests** - Response handling, CORS, caching headers +- **JSON** - Path traversal, value extraction +- **Database** - Raw SQL, migrations, connection management +- **Email** - Parsing, enrichment, permutations +- **Handles** - Unique identifiers and slugs +- **Images** - Format detection, processing +- **Payments** - Luhn validation, card handling +- **PDF/CSV** - File generation and export +- **Queries** - Bulk operations, object retrieval +- **Security** - Encryption, HTTPS detection + +## Text Processing + +### Formatting & Display + +```python +from htk.utils.text.pretty import phonenumber +from htk.utils.text.english import pluralize_noun, oxford_comma + +phone = phonenumber('5551234567') +message = oxford_comma(['Alice', 'Bob', 'Charlie']) # Alice, Bob, and Charlie +items_text = pluralize_noun('item', count) # 'items' or 'item' +``` + +### Conversion + +```python +from htk.utils.text.converters import html2markdown, markdown2slack + +markdown = html2markdown('Bold text') +slack_msg = markdown2slack('**Bold** text') +``` + +### Transformations + +```python +from htk.utils.text.transformers import ellipsize, seo_tokenize +from htk.utils.text.transformers import snake_case_to_camel_case + +text = ellipsize('Very long text', max_len=20) # 'Very long ...' +tokens = seo_tokenize('my-product-name') +camel = snake_case_to_camel_case('user_id') # 'userId' +``` + +### Sanitization & Unicode + +```python +from htk.utils.text.unicode import demojize, unicode_to_ascii + +clean = demojize('Hello 👋') # 'Hello' +ascii_text = unicode_to_ascii('Café') # 'Cafe' +``` + +## Caching & Optimization + +### Decorators + +```python +from htk.utils.cache_descriptors import memoized, CachedAttribute + +@memoized +def expensive_function(x): + return x ** 2 + +class MyClass: + @CachedAttribute + def computed_value(self): + return sum(range(1000)) # Computed once, cached forever +``` + +### Memoization + +```python +from htk.utils.cache_descriptors import CachedClassAttribute + +class Config: + @CachedClassAttribute + def settings(cls): + return load_settings() +``` + +## Data Structures + +### Dictionaries + +```python +from htk.utils.data_structures.general import filter_dict + +filtered = filter_dict({'a': 1, 'b': 2, 'c': 3}, ['a', 'c']) # {'a': 1, 'c': 3} +``` + +### Enums + +```python +from htk.utils.enums import enum_to_str, choices + +class Status(Enum): + ACTIVE = 1 + INACTIVE = 2 + +status_str = enum_to_str(Status.ACTIVE) +status_choices = choices(Status) # [(1, 'ACTIVE'), (2, 'INACTIVE')] +``` + +### Collections + +```python +from htk.utils.iter_utils import chunks, lookahead + +for group in chunks(items, 10): + process_batch(group) # Process 10 items at a time +``` + +## DateTime & Timezone + +### Timezone Handling + +```python +from htk.utils.datetime_utils import localized_datetime +from htk.utils.datetime_utils import is_within_hour_bounds_for_timezone + +dt = localized_datetime(naive_dt, 'America/New_York') +is_business_hours = is_within_hour_bounds_for_timezone('America/New_York', 9, 17) +``` + +### Conversions + +```python +from htk.utils.datetime_utils import datetime_to_unix_time, iso_to_gregorian + +timestamp = datetime_to_unix_time(datetime.now()) +gregorian_date = iso_to_gregorian(2024, 1, 1) # ISO week to Gregorian +``` + +## HTTP & Requests + +### Response Headers + +```python +from htk.utils.http.response import set_cache_headers, set_cors_headers_for_image + +response = set_cache_headers(response, max_age=3600) # Cache for 1 hour +response = set_cors_headers_for_image(response) +``` + +### Request Parsing + +```python +from htk.utils.request import extract_request_param, parse_authorization_header + +user_id = extract_request_param(request, 'user_id', int) +auth_type, token = parse_authorization_header(request) +``` + +## JSON & Data + +### JSON Path Traversal + +```python +from htk.utils.json_utils import find_json_value, find_all_json_paths + +value = find_json_value(data, 'user.profile.name') # Dot notation +paths = find_all_json_paths(data) # All valid paths +``` + +### Compression + +```python +from htk.utils.json_utils import deepcopy_with_compact + +clean_data = deepcopy_with_compact(data) # Removes None values +``` + +## Email & Contact + +### Email Utilities + +```python +from htk.utils.emails import email_permutator, find_company_emails_for_name + +# Generate email variations for John Smith at acme.com +emails = email_permutator('acme.com', 'John', 'Smith') +# ['john.smith@acme.com', 'jsmith@acme.com', 'john@acme.com', ...] +``` + +## Handles & Identifiers + +### Unique Handles + +```python +from htk.utils.handles import generate_unique_handle, is_unique_handle + +handle = generate_unique_handle('John Smith') # 'john-smith', 'john-smith-2', etc. +``` + +## Images + +### Format Detection + +```python +from htk.utils.image import detect_image_format + +format = detect_image_format(image_file) # 'PNG', 'JPEG', etc. +``` + +## Validation + +### Payment Cards + +```python +from htk.utils.luhn import is_luhn_valid, calculate_luhn_check_digit + +valid = is_luhn_valid('4532015112830366') +check_digit = calculate_luhn_check_digit('453201511283036') +``` + +## Database + +### Raw SQL + +```python +from htk.utils.db import raw_sql, namedtuplefetchall + +results = raw_sql('SELECT * FROM users WHERE active = %s', [True]) +rows = namedtuplefetchall(cursor) # Returns named tuples +``` + +### Connection Management + +```python +from htk.utils.db import ensure_mysql_connection_usable + +ensure_mysql_connection_usable() # Reconnect if needed +``` + +## PDF & CSV + +### CSV Export + +```python +from htk.utils.csv_utils import get_csv_response + +response = get_csv_response(filename, [['Name', 'Email'], ['John', 'john@example.com']]) +``` + +### PDF Generation + +```python +from htk.utils.pdf_utils import render_to_pdf_response + +response = render_to_pdf_response('template.html', context_data) +``` + +## Queries & Lookups + +### Bulk Operations + +```python +from htk.utils.query import get_objects_by_id + +users = get_objects_by_id(User, [1, 2, 3]) # In single query +``` + +### Safe Retrieval + +```python +from htk.utils.django_shortcuts import get_object_or_none + +user = get_object_or_none(User, id=999) # Returns None instead of exception +``` + +## Security + +### Encryption + +```python +from htk.utils.crypto import AESCipher + +cipher = AESCipher() +encrypted = cipher.encrypt('sensitive data') +decrypted = cipher.decrypt(encrypted) +``` + +### HTTPS Detection + +```python +from htk.utils.security import should_use_https + +if should_use_https(): + url = f'https://{host}/path' +``` + +## Terminal Output + +### ANSI Colors + +```python +from htk.utils.xterm import colorize, c + +colored_text = colorize('Error!', fg='red', style='bold') +msg = c('Success!', 'green') # Shorthand +``` + +## Common Patterns + +**Processing CSV with Django models:** +```python +from htk.utils.csv_utils import UnicodeReader + +with open('data.csv') as f: + for row in UnicodeReader(f): + User.objects.create(email=row[0], name=row[1]) +``` + +**Paginating large datasets:** +```python +from htk.utils.iter_utils import chunks + +for batch in chunks(User.objects.all(), 100): + process_users(batch) +``` + +**Building URLs with parameters:** +```python +from htk.utils.urls import build_url_with_query_params + +url = build_url_with_query_params('/search/', {'q': 'django', 'page': 2}) +``` diff --git a/utils/concurrency/README.md b/utils/concurrency/README.md new file mode 100644 index 00000000..f0656b57 --- /dev/null +++ b/utils/concurrency/README.md @@ -0,0 +1,31 @@ +# Concurrency Utilities + +Race condition resolution and retry patterns. + +## Quick Start + +```python +from htk.utils.concurrency.race_resolvers import retry_until_not_none, retry_until + +# Retry until function returns non-None value +result = retry_until_not_none(lambda: get_data(), timeout=10) + +# Retry until predicate is true +result = retry_until( + lambda: fetch_status(), + until_predicate=lambda x: x == 'completed', + timeout=30 +) +``` + +## Common Patterns + +```python +# Waiting for async operations +def wait_for_resource(): + return retry_until_not_none( + lambda: get_resource(), + timeout=60, + retry_delay=0.1 + ) +``` diff --git a/utils/data_structures/README.md b/utils/data_structures/README.md new file mode 100644 index 00000000..4287aa80 --- /dev/null +++ b/utils/data_structures/README.md @@ -0,0 +1,66 @@ +# Data Structures Utils + +## Overview + +This module provides utility functions for working with dictionaries and other data structures. + +## Functions + +### Dictionary Filtering + +```python +from htk.utils.data_structures import filter_dict + +def filter_dict(d, keys): + """Filter a dictionary to only include specified keys. + + Args: + d: Dictionary to filter + keys: Iterable of keys to keep + + Returns: + New dictionary with only the specified keys + """ + +# Filter a dictionary +original = {'a': 1, 'b': 2, 'c': 3, 'd': 4} +filtered = filter_dict(original, ['a', 'c']) +# Result: {'a': 1, 'c': 3} +``` + +## Usage Examples + +### Keep Only Specific Fields + +```python +from htk.utils.data_structures import filter_dict + +user_data = { + 'id': 123, + 'name': 'John', + 'email': 'john@example.com', + 'password_hash': 'secret...', + 'api_key': 'key...' +} + +# Extract only safe fields for API response +safe_fields = filter_dict(user_data, ['id', 'name', 'email']) +# {'id': 123, 'name': 'John', 'email': 'john@example.com'} +``` + +### Build Dynamic Queries + +```python +from htk.utils.data_structures import filter_dict + +params = { + 'search': 'query', + 'sort': 'name', + 'limit': 10, + 'internal_flag': False, + 'debug': True +} + +# Keep only API-safe parameters +api_params = filter_dict(params, ['search', 'sort', 'limit']) +``` diff --git a/utils/http/README.md b/utils/http/README.md new file mode 100644 index 00000000..2992ca98 --- /dev/null +++ b/utils/http/README.md @@ -0,0 +1,34 @@ +# HTTP Utilities + +HTTP response headers and error handling. + +## Quick Start + +```python +from htk.utils.http.response import set_cache_headers, set_cors_headers_for_image +from htk.utils.http.errors import HttpErrorResponseError + +# Set cache headers on response +response = HttpResponse('content') +set_cache_headers(response, max_age=3600) + +# Set CORS headers for images +set_cors_headers_for_image(response) +``` + +## Common Patterns + +```python +# Create cacheable response +def get_cached_image(request): + response = serve_image() + set_cache_headers(response, max_age=86400) # 1 day + set_cors_headers_for_image(response) + return response + +# Handle HTTP errors +try: + result = api_call() +except HttpErrorResponseError as e: + return error_response(e.status_code) +``` diff --git a/utils/i18n/README.md b/utils/i18n/README.md new file mode 100644 index 00000000..99593d01 --- /dev/null +++ b/utils/i18n/README.md @@ -0,0 +1,78 @@ +# Internationalization Utils + +## Overview + +This module provides utility functions for working with international data including countries, languages, currencies, and timezones. + +## Functions + +```python +from htk.utils.i18n import ( + get_country_choices, + get_language_name, + get_currency_symbol, + get_random_greeting +) + +# Get country choices for form select fields +choices = get_country_choices(ordering=('US', 'CA')) + +# Get language name from code +lang_name = get_language_name('en') # 'English' + +# Get currency symbol +symbol = get_currency_symbol('USD') # '$' + +# Get random greeting in random language +greeting = get_random_greeting() # e.g. 'Hola' +``` + +## Usage Examples + +### Form Select Choices + +```python +from htk.utils.i18n import get_country_choices + +class LocationForm(forms.Form): + country = forms.ChoiceField( + choices=get_country_choices(ordering=('US', 'CA', 'MX')), + help_text="Select your country" + ) +``` + +### Display Language Names + +```python +from htk.utils.i18n import get_language_name + +supported_languages = ['en', 'es', 'fr', 'de'] +for lang_code in supported_languages: + print(f"{lang_code}: {get_language_name(lang_code)}") + # en: English + # es: Spanish + # fr: French + # de: German +``` + +### Format Prices with Currency Symbols + +```python +from htk.utils.i18n import get_currency_symbol + +def format_price(amount, currency_code): + symbol = get_currency_symbol(currency_code) + return f"{symbol}{amount:.2f}" + +print(format_price(99.99, 'USD')) # $99.99 +print(format_price(99.99, 'EUR')) # €99.99 +``` + +### Randomized Greetings + +```python +from htk.utils.i18n import get_random_greeting + +# Use for welcome banners or dashboard headers +greeting = get_random_greeting() +``` diff --git a/utils/log/README.md b/utils/log/README.md new file mode 100644 index 00000000..3cfbfe48 --- /dev/null +++ b/utils/log/README.md @@ -0,0 +1,41 @@ +# Logging Utilities + +Exception handlers for Rollbar and Slack logging. + +## Quick Start + +```python +from htk.utils.log.handlers import RollbarHandler, SlackDebugHandler +import logging + +# Configure Rollbar handler +rollbar_handler = RollbarHandler() +logger = logging.getLogger() +logger.addHandler(rollbar_handler) + +# Configure Slack handler for debugging +slack_handler = SlackDebugHandler() +debug_logger = logging.getLogger('debug') +debug_logger.addHandler(slack_handler) +``` + +## Common Patterns + +```python +# Log exceptions to multiple destinations +logger.addHandler(RollbarHandler()) # Production monitoring +logger.addHandler(SlackDebugHandler()) # Team notifications + +try: + dangerous_operation() +except Exception as e: + logger.exception('Operation failed') +``` + +## Configuration + +```python +# settings.py +ROLLBAR_TOKEN = os.environ.get('ROLLBAR_TOKEN') +SLACK_WEBHOOK_URL = os.environ.get('SLACK_WEBHOOK_URL') +``` diff --git a/utils/maths/README.md b/utils/maths/README.md new file mode 100644 index 00000000..4d610e97 --- /dev/null +++ b/utils/maths/README.md @@ -0,0 +1,31 @@ +# Math Utilities + +Mathematical functions for algebra and trigonometry. + +## Quick Start + +```python +from htk.utils.maths.algebra import quadratic +from htk.utils.maths.trigonometry import deg2rad, rad2deg + +# Solve quadratic equation (ax² + bx + c = 0) +roots = quadratic(a=1, b=-3, c=2) # Returns: (2.0, 1.0) + +# Convert between degrees and radians +radians = deg2rad(180) # 3.14159... +degrees = rad2deg(3.14159) # 180.0 +``` + +## Common Patterns + +```python +# Find solutions to quadratic equations +a, b, c = coefficients() +solutions = quadratic(a, b, c) + +# Work with angles +angle_degrees = 45 +angle_radians = deg2rad(angle_degrees) +# Use in calculations... +angle_back = rad2deg(angle_radians) +``` diff --git a/utils/measurements/README.md b/utils/measurements/README.md new file mode 100644 index 00000000..e86f2824 --- /dev/null +++ b/utils/measurements/README.md @@ -0,0 +1,32 @@ +# Measurements Utilities + +Distance and weight unit conversions. + +## Quick Start + +```python +from htk.utils.measurements.distance import DistanceType +from htk.utils.measurements.weight import WeightType +from htk.utils.measurements.units import Distance, Weight + +# Create distance measurements +distance = DistanceType(value=100, unit=Distance.METERS) +distance_in_miles = distance.to(Distance.MILES) + +# Create weight measurements +weight = WeightType(value=150, unit=Weight.POUNDS) +weight_in_kg = weight.to(Weight.KILOGRAMS) +``` + +## Common Patterns + +```python +# Convert between units +def convert_measurements(value, from_unit, to_unit): + measurement = DistanceType(value, from_unit) + return measurement.to(to_unit) + +# Store canonical units in database, convert for display +distance_meters = DistanceType(1609.34, Distance.METERS) # 1 mile +distance_miles = distance_meters.to(Distance.MILES) # Display as 1 mile +``` diff --git a/utils/text/README.md b/utils/text/README.md new file mode 100644 index 00000000..868db9f6 --- /dev/null +++ b/utils/text/README.md @@ -0,0 +1,54 @@ +# Text Utilities + +String manipulation, formatting, and text processing. + +## Quick Start + +```python +from htk.utils.text.algorithms import levenshtein_distance, get_closest_dict_words +from htk.utils.text.converters import html2markdown, markdown2slack +from htk.utils.text.english import oxford_comma, pluralize_noun +from htk.utils.text.transformers import seo_tokenize, snake_case_to_camel_case + +# String distance and autocorrect +distance = levenshtein_distance('kitten', 'sitting') # 3 +suggestions = get_closest_dict_words('speling', word_dict) + +# Format conversions +markdown = html2markdown('Hello World') +slack_formatted = markdown2slack('**Bold** and *italic*') + +# English formatting +items_list = oxford_comma(['apples', 'oranges', 'bananas']) # "apples, oranges, and bananas" +message = f"You have {pluralize_noun('item', count)}" + +# Case conversion +camel = snake_case_to_camel_case('user_name') # 'userName' +seo_token = seo_tokenize('hello-world_2024') # Optimized token +``` + +## Common Patterns + +```python +# User input sanitization and formatting +from htk.utils.text.sanitizers import sanitize_cookie_value + +cookie_value = sanitize_cookie_value(user_input) + +# Formatting phone numbers +from htk.utils.text.pretty import phonenumber + +formatted = phonenumber('2025551234', country='US') # (202) 555-1234 + +# Text summarization +from htk.utils.text.transformers import ellipsize, summarize + +truncated = ellipsize(long_text, max_len=100) +summary = summarize(paragraph, num_sentences=3) + +# Unicode handling +from htk.utils.text.unicode import demojize, unicode_to_ascii + +text_no_emoji = demojize('Hello 👋 World 🌍') +ascii_text = unicode_to_ascii('café') # cafe +``` diff --git a/validators/README.md b/validators/README.md new file mode 100644 index 00000000..547e426a --- /dev/null +++ b/validators/README.md @@ -0,0 +1,163 @@ +# HTK Validators Module + +> Form and data validation utilities. + +## Purpose + +The validators module provides reusable validation functions for forms and data. These validators enforce data integrity and ensure values meet business logic requirements. + +## Quick Start + +```python +from htk.validators import is_valid_email, is_valid_url, is_valid_phone +from django import forms + +class ContactForm(forms.Form): + email = forms.EmailField(validators=[is_valid_email]) + website = forms.URLField(validators=[is_valid_url], required=False) + phone = forms.CharField(validators=[is_valid_phone], required=False) + +# Test validators +assert is_valid_email('user@example.com') == True +assert is_valid_email('invalid') == False +assert is_valid_url('https://example.com') == True +assert is_valid_phone('+1-555-0123') == True +``` + +## Key Components + +| Function | Purpose | +|----------|---------| +| **is_valid_email()** | Validate email address format (RFC-compliant) | +| **is_valid_url()** | Validate URL format with scheme | +| **is_valid_phone()** | Validate phone number format | +| **Custom validators** | Build field-specific or cross-field validators | + +## Common Patterns + +### Field-Level Validation for Models and Forms + +```python +from django.core.exceptions import ValidationError +from django.db import models + +def validate_positive(value): + """Validate value is positive""" + if value <= 0: + raise ValidationError("Value must be positive") + +def validate_age(value): + """Validate age is between 0 and 150""" + if not (0 <= value <= 150): + raise ValidationError("Age must be between 0 and 150") + +# In model +class Person(models.Model): + age = models.IntegerField(validators=[validate_age]) + quantity = models.IntegerField(validators=[validate_positive]) + +# In form +class PersonForm(forms.Form): + age = forms.IntegerField(validators=[validate_age]) + quantity = forms.IntegerField(validators=[validate_positive]) +``` + +### Cross-Field and Conditional Validation + +```python +from django import forms +from django.core.exceptions import ValidationError + +class RegistrationForm(forms.Form): + email = forms.EmailField() + email_confirm = forms.EmailField() + password = forms.CharField(widget=forms.PasswordInput) + password_confirm = forms.CharField(widget=forms.PasswordInput) + + def clean(self): + cleaned_data = super().clean() + + # Verify emails match + if cleaned_data.get('email') != cleaned_data.get('email_confirm'): + raise ValidationError("Emails do not match") + + # Verify passwords match + if cleaned_data.get('password') != cleaned_data.get('password_confirm'): + raise ValidationError("Passwords do not match") + + # Ensure email not already registered + email = cleaned_data.get('email') + if User.objects.filter(email=email).exists(): + self.add_error('email', "Email already registered") + + return cleaned_data +``` + +### Validator Classes for Complex Logic + +```python +from django.core.exceptions import ValidationError + +class PercentValidator: + """Validate percentage value (0-100)""" + def __call__(self, value): + if not (0 <= value <= 100): + raise ValidationError("Percentage must be between 0-100") + +class SKUValidator: + """Validate SKU format (3 letters + 6 digits)""" + def __call__(self, value): + import re + if not re.match(r'^[A-Z]{3}[0-9]{6}$', value): + raise ValidationError("SKU must be 3 letters followed by 6 digits") + +# Use in models +class Product(models.Model): + sku = models.CharField(max_length=20, validators=[SKUValidator()]) + discount = models.IntegerField(validators=[PercentValidator()]) +``` + +## Best Practices + +- **Use built-in validators first** - Django provides `MinValueValidator`, `MaxLengthValidator`, `RegexValidator`, etc. +- **Write clear error messages** - Be specific about what's invalid and what format is expected +- **Validate in order** - Combine field-level validators (format) then cross-field validators (relationships) +- **Test edge cases** - Valid inputs, boundary values, and invalid inputs +- **Separate concerns** - Keep validators simple; use form `clean()` for complex logic + +## Testing + +```python +from django.test import TestCase +from django.core.exceptions import ValidationError +from htk.validators import is_valid_email, is_valid_url, is_valid_phone + +class ValidatorTestCase(TestCase): + def test_email_validation(self): + """Test email validation with valid and invalid inputs""" + valid_emails = [ + 'user@example.com', + 'user.name@example.co.uk', + 'user+tag@example.com', + ] + for email in valid_emails: + self.assertTrue(is_valid_email(email)) + + invalid_emails = ['invalid', 'user@', '@example.com', 'user @example.com'] + for email in invalid_emails: + self.assertFalse(is_valid_email(email)) + + def test_url_validation(self): + """Test URL validation""" + self.assertTrue(is_valid_url('https://example.com')) + self.assertTrue(is_valid_url('http://sub.example.com/path')) + self.assertFalse(is_valid_url('not a url')) + self.assertFalse(is_valid_url('example.com')) # Missing scheme + + def test_phone_validation(self): + """Test phone validation""" + self.assertTrue(is_valid_phone('5550123')) + self.assertTrue(is_valid_phone('+1-555-0123')) + self.assertFalse(is_valid_phone('123')) # Too short + self.assertFalse(is_valid_phone('abcdefgh')) # Non-numeric +```