diff --git a/docs/docsite/rst/contributor/API_REQUIREMENTS.rst b/docs/docsite/rst/contributor/API_REQUIREMENTS.rst new file mode 100644 index 0000000000..96b5efef90 --- /dev/null +++ b/docs/docsite/rst/contributor/API_REQUIREMENTS.rst @@ -0,0 +1,1305 @@ +===================================================== +API Development Requirements +===================================================== + +**AWX REST API Best Practices** + +:Version: 1.0 +:Date: September 2025 +:Based on: AWX Enterprise Django REST Framework Analysis +:Generated by: Claude Code AI + +---- + +.. contents:: Table of Contents + :depth: 3 + :local: + +---- + +.. important:: + This document defines mandatory API development standards for enterprise-grade Django REST Framework applications based on AWX production patterns. + +---- + +1. API Architecture and Configuration +===================================== + +1.1 Django REST Framework Configuration +--------------------------------------- + +**REQUIRED**: Comprehensive DRF setup with enterprise-grade defaults: + +.. code-block:: python + + # settings/defaults.py + REST_FRAMEWORK = { + 'DEFAULT_PAGINATION_CLASS': 'awx.api.pagination.Pagination', + 'PAGE_SIZE': 25, + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'awx.api.authentication.JWTAuthentication', + 'awx.api.authentication.SessionAuthentication', + 'awx.api.authentication.LoggedBasicAuthentication', + ), + 'DEFAULT_PERMISSION_CLASSES': ( + 'awx.api.permissions.ModelAccessPermission', + ), + 'DEFAULT_PARSER_CLASSES': ( + 'awx.api.parsers.JSONParser', + ), + 'DEFAULT_RENDERER_CLASSES': ( + 'awx.api.renderers.DefaultJSONRenderer', + 'awx.api.renderers.BrowsableAPIRenderer', + ), + 'DEFAULT_METADATA_CLASS': 'awx.api.metadata.Metadata', + 'EXCEPTION_HANDLER': 'awx.api.views.api_exception_handler', + 'VIEW_DESCRIPTION_FUNCTION': 'awx.api.generics.get_view_description', + 'NON_FIELD_ERRORS_KEY': '__all__', + 'DEFAULT_VERSION': 'v2', + 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema', + } + +**Configuration Requirements**: + +- **Centralized Settings**: API configuration must be centralized and environment-aware +- **Version Control**: Default API version must be explicitly configured +- **Error Handling**: Custom exception handler for consistent error responses + +1.2 Modular API Structure +------------------------- + +**REQUIRED**: Organize API with clear separation of concerns: + +.. code-block:: text + + api/ + ├── __init__.py + ├── authentication.py # Custom authentication classes + ├── permissions.py # Permission and access control + ├── serializers.py # Base serializer patterns + ├── generics.py # Custom view classes + ├── pagination.py # Pagination strategies + ├── renderers.py # Response formatters + ├── fields.py # Custom field types + ├── exceptions.py # Custom exception classes + ├── metadata.py # Field metadata for clients + ├── urls/ # Resource-specific URL modules + │ ├── __init__.py + │ ├── organization.py + │ ├── user.py + │ ├── project.py + │ └── job_template.py + └── views/ # Resource-specific view modules + ├── __init__.py + ├── organization.py + └── user.py + +**Structural Requirements**: + +- **Resource-Based Organization**: Each API resource must have its own module +- **URL Separation**: URL patterns organized by resource type +- **View Inheritance**: Consistent view hierarchy with shared base classes +- **Component Isolation**: Authentication, permissions, and serialization in separate modules + +---- + +2. REST API Design Standards +============================ + +2.1 Core REST Principles +------------------------- + +**MANDATORY**: All APIs must adhere to these fundamental principles: + +.. list-table:: REST Design Requirements + :header-rows: 1 + :widths: 30 70 + + * - Principle + - Implementation Requirement + * - **Paginate Everything** + - Any endpoint returning collections MUST be paginated + * - **Performance Target** + - Expected response time ≤ 250ms (1/4 second) + * - **RBAC Filtering** + - All collections MUST be filtered by user access permissions + * - **API Discoverability** + - All endpoints MUST be traversable from API root '/' + * - **RESTful Verbs** + - HTTP methods MUST follow REST conventions + * - **Constant Time Queries** + - Database queries MUST NOT vary with result set size + +2.2 URL Pattern Standards +-------------------------- + +**REQUIRED**: Consistent RESTful URL patterns: + +.. code-block:: python + + # Standard resource patterns + urlpatterns = [ + # Collection endpoints + re_path(r'^$', ResourceList.as_view(), name='resource_list'), + + # Instance endpoints + re_path(r'^(?P[0-9]+)/$', ResourceDetail.as_view(), name='resource_detail'), + + # Nested resource relationships + re_path(r'^(?P[0-9]+)/children/$', ResourceChildrenList.as_view(), + name='resource_children_list'), + + # Action endpoints + re_path(r'^(?P[0-9]+)/launch/$', ResourceLaunch.as_view(), + name='resource_launch'), + re_path(r'^(?P[0-9]+)/copy/$', ResourceCopy.as_view(), + name='resource_copy'), + re_path(r'^(?P[0-9]+)/callback/$', ResourceCallback.as_view(), + name='resource_callback'), + ] + +**URL Requirements**: + +- **Numeric Primary Keys**: Use ``(?P[0-9]+)`` pattern for resource IDs +- **Hierarchical Relationships**: Support nested resource access patterns +- **Action Endpoints**: Additional endpoints for resource-specific actions +- **Consistent Naming**: Follow ``resource_action`` naming convention + +2.3 HTTP Method Standards +------------------------- + +**REQUIRED**: Proper HTTP method usage: + +.. list-table:: HTTP Method Requirements + :header-rows: 1 + :widths: 15 25 60 + + * - Method + - Usage + - Requirements + * - **GET** + - Retrieve resources + - Idempotent, no side effects, cacheable + * - **POST** + - Create resources + - Returns 201 Created with Location header + * - **PUT** + - Update/Replace resources + - Idempotent, full resource replacement + * - **PATCH** + - Partial updates + - Non-idempotent, partial resource modification + * - **DELETE** + - Remove resources + - Idempotent, returns 204 No Content + * - **OPTIONS** + - Metadata discovery + - Returns available methods and field metadata + +---- + +3. Authentication and Authorization +=================================== + +3.1 Multi-Layer Authentication Stack +------------------------------------ + +**REQUIRED**: Implement comprehensive authentication system: + +.. code-block:: python + + # api/authentication.py + class LoggedBasicAuthentication(authentication.BasicAuthentication): + """Basic authentication with comprehensive logging""" + + def authenticate(self, request): + if not settings.AUTH_BASIC_ENABLED: + return + + ret = super().authenticate(request) + if ret: + username = ret[0].username if ret[0] else '' + logger.info( + f"User {username} performed {request.method} " + f"to {request.path} through the API using Basic Auth" + ) + return ret + + class SessionAuthentication(authentication.SessionAuthentication): + """Enhanced session authentication with CSRF handling""" + + def authenticate(self, request): + # Custom session validation logic + return super().authenticate(request) + +**Authentication Requirements**: + +1. **JWT Authentication**: Primary token-based authentication +2. **Session Authentication**: Browser-based session support +3. **Basic Authentication**: With comprehensive request logging +4. **Authentication Logging**: All authentication events must be logged +5. **Fallback Support**: Multiple authentication methods for different clients + +3.2 Role-Based Access Control (RBAC) +------------------------------------ + +**REQUIRED**: Implement comprehensive permission system: + +.. code-block:: python + + # api/permissions.py + class ModelAccessPermission(permissions.BasePermission): + """Advanced model-based access control""" + + def has_permission(self, request, view): + # Check view-level permissions + if not hasattr(view, 'model'): + return False + + # Parent resource permission checking + if hasattr(view, 'parent_model'): + parent_obj = view.get_parent_object() + if not check_user_access( + request.user, + view.parent_model, + 'read', + parent_obj + ): + return False + + return True + + def has_object_permission(self, request, view, obj): + # Object-level permission checking + permission_map = { + 'GET': 'read', + 'POST': 'add', + 'PUT': 'change', + 'PATCH': 'change', + 'DELETE': 'delete', + } + + required_permission = permission_map.get(request.method, 'read') + return check_user_access(request.user, view.model, required_permission, obj) + +**RBAC Requirements**: + +- **Method-Specific Permissions**: Different permissions for GET, POST, PUT, DELETE +- **Object-Level Control**: Fine-grained access control on individual resources +- **Hierarchical Permissions**: Parent-child relationship permission inheritance +- **Data-Aware Permissions**: Access control based on request data content +- **Audit Trail**: All permission checks must be logged + +3.3 API Token Management +------------------------ + +**REQUIRED**: Secure token handling patterns: + +.. code-block:: python + + # Token-based authentication requirements + JWT_SETTINGS = { + 'ACCESS_TOKEN_LIFETIME': timedelta(hours=1), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), + 'ROTATE_REFRESH_TOKENS': True, + 'BLACKLIST_AFTER_ROTATION': True, + 'ALGORITHM': 'RS256', + 'SIGNING_KEY': settings.JWT_PRIVATE_KEY, + 'VERIFYING_KEY': settings.JWT_PUBLIC_KEY, + } + +**Token Requirements**: + +- **Short-Lived Access Tokens**: Maximum 1-hour expiration +- **Refresh Token Rotation**: Automatic rotation on refresh +- **Token Blacklisting**: Revoked tokens must be blacklisted +- **Asymmetric Encryption**: Use RS256 with key pairs +- **Secure Storage**: Private keys must be externally managed + +---- + +4. Serialization and Data Validation +===================================== + +4.1 Base Serializer Architecture +-------------------------------- + +**REQUIRED**: Implement consistent serializer patterns: + +.. code-block:: python + + # api/serializers.py + class BaseSerializer(serializers.ModelSerializer, metaclass=BaseSerializerMetaclass): + """Base serializer with common enterprise patterns""" + + class Meta: + fields = ( + 'id', 'type', 'url', 'related', 'summary_fields', + 'created', 'modified', 'name', 'description' + ) + summary_fields = () + summarizable_fields = () + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._setup_summary_fields() + self._setup_related_fields() + + def _setup_summary_fields(self): + """Configure optimized nested data representation""" + pass + + def _setup_related_fields(self): + """Configure related resource URL fields""" + pass + +**Serializer Requirements**: + +- **Base Class Inheritance**: All serializers must inherit from base serializer +- **Common Fields**: Standardized fields across all resources +- **Summary Fields**: Optimized nested data representation +- **Related URLs**: Structured related resource references +- **Metaclass Usage**: Custom metaclass for field inheritance + +4.2 Custom Field Types +---------------------- + +**REQUIRED**: Domain-specific field validation: + +.. code-block:: python + + # api/fields.py + class BooleanNullField(NullFieldMixin, serializers.BooleanField): + """Boolean field that allows null and empty string as False""" + + def to_internal_value(self, data): + if data is None or data == '': + return False + return super().to_internal_value(data) + + class CharNullField(NullFieldMixin, serializers.CharField): + """Char field that allows null input and coerces to empty string""" + + def to_internal_value(self, data): + if data is None: + return '' + return super().to_internal_value(data) + + class JSONField(serializers.Field): + """Custom JSON field with validation""" + + def to_representation(self, value): + return json.loads(value) if isinstance(value, str) else value + + def to_internal_value(self, data): + try: + return json.dumps(data) + except (TypeError, ValueError): + raise serializers.ValidationError("Invalid JSON data") + +**Field Requirements**: + +- **Null Handling**: Consistent null value processing across field types +- **Type Coercion**: Automatic data type conversion where appropriate +- **Validation Mixins**: Reusable validation logic through mixins +- **Custom Serialization**: Domain-specific serialization logic + +4.3 Data Validation Patterns +---------------------------- + +**REQUIRED**: Comprehensive validation strategies: + +.. code-block:: python + + class ResourceSerializer(BaseSerializer): + """Example resource serializer with validation""" + + class Meta(BaseSerializer.Meta): + model = Resource + fields = BaseSerializer.Meta.fields + ('status', 'type', 'configuration') + + def validate_configuration(self, value): + """Field-level validation""" + if not isinstance(value, dict): + raise serializers.ValidationError("Configuration must be a JSON object") + return value + + def validate(self, data): + """Object-level validation""" + if data.get('status') == 'active' and not data.get('configuration'): + raise serializers.ValidationError( + "Active resources must have configuration" + ) + return data + + def create(self, validated_data): + """Custom creation logic with audit trail""" + instance = super().create(validated_data) + self._log_creation(instance) + return instance + +**Validation Requirements**: + +- **Field-Level Validation**: Validate individual fields with custom logic +- **Object-Level Validation**: Cross-field validation rules +- **Business Logic Validation**: Domain-specific validation rules +- **Audit Logging**: All validation failures must be logged +- **Error Formatting**: Consistent error message structure + +---- + +5. Performance and Optimization +=============================== + +5.1 Query Optimization Standards +-------------------------------- + +**MANDATORY**: Database query performance requirements: + +.. warning:: + **Critical Performance Rule**: The number of database queries MUST be constant time and MUST NOT vary with the size of the result set. + +.. code-block:: python + + # api/generics.py + def optimize_queryset(queryset, view, remove_django_content_type=False): + """Query optimization for API views""" + + # Select related for foreign keys + if hasattr(view, 'select_related_fields'): + queryset = queryset.select_related(*view.select_related_fields) + + # Prefetch related for many-to-many and reverse foreign keys + if hasattr(view, 'prefetch_related_fields'): + queryset = queryset.prefetch_related(*view.prefetch_related_fields) + + # Only select necessary fields + if hasattr(view, 'only_fields'): + queryset = queryset.only(*view.only_fields) + + return queryset + +**Query Optimization Requirements**: + +- **Constant Time Queries**: Query count independent of result set size +- **Select Related**: Use ``select_related()`` for foreign key relationships +- **Prefetch Related**: Use ``prefetch_related()`` for many-to-many relationships +- **Field Selection**: Use ``only()`` to limit selected fields +- **No Serializer Queries**: Database queries prohibited in serializers + +5.2 Pagination Strategy +----------------------- + +**REQUIRED**: Advanced pagination with performance optimization: + +.. code-block:: python + + # api/pagination.py + class Pagination(pagination.PageNumberPagination): + """Enterprise pagination with performance features""" + + page_size_query_param = 'page_size' + max_page_size = 200 + count_disabled = False + + def paginate_queryset(self, queryset, request, **kwargs): + # Optional count disabling for large datasets + self.count_disabled = 'count_disabled' in request.query_params + + if self.count_disabled: + # Skip expensive COUNT query for large datasets + return self._paginate_without_count(queryset, request) + + return super().paginate_queryset(queryset, request, **kwargs) + + def get_paginated_response(self, data): + response_data = { + 'count': self.page.paginator.count if not self.count_disabled else None, + 'next': self.get_next_link(), + 'previous': self.get_previous_link(), + 'results': data + } + return Response(response_data) + +**Pagination Requirements**: + +- **Performance Optimization**: Optional count disabling for large datasets +- **Configurable Page Sizes**: Maximum page size limits with user control +- **Consistent Format**: Standardized pagination response format +- **Memory Efficiency**: Efficient memory usage for large result sets + +5.3 Caching Strategies +---------------------- + +**REQUIRED**: Multi-level caching implementation: + +.. code-block:: python + + # api/middleware.py + class SettingsCacheMiddleware(MiddlewareMixin): + """Clear settings cache on each request""" + + def process_request(self, request): + from django.conf import settings + if hasattr(settings, '_awx_conf_memoizedcache'): + settings._awx_conf_memoizedcache.clear() + + class ResponseCacheMiddleware(MiddlewareMixin): + """Cache API responses based on content type""" + + def process_response(self, request, response): + # Cache responses for GET requests only + if request.method == 'GET' and response.status_code == 200: + cache_key = self._generate_cache_key(request) + cache.set(cache_key, response.content, timeout=300) + return response + +**Caching Requirements**: + +- **Settings Caching**: Intelligent configuration caching with invalidation +- **Response Caching**: Cache GET responses with appropriate TTL +- **Cache Invalidation**: Automatic cache clearing on data changes +- **Selective Caching**: Cache only appropriate endpoints and data + +---- + +6. Error Handling and Status Codes +=================================== + +6.1 Custom Exception Hierarchy +------------------------------ + +**REQUIRED**: Comprehensive exception handling system: + +.. code-block:: python + + # api/exceptions.py + class APIException(Exception): + """Base API exception with structured error format""" + status_code = 400 + detail = "An error occurred" + + def __init__(self, detail=None, code=None): + if detail is not None: + self.detail = detail + if code is not None: + self.status_code = code + + class ActiveJobConflict(ValidationError): + """Resource conflict with active jobs""" + status_code = 409 + + def __init__(self, active_jobs): + super().__init__() + self.detail = { + "error": "Resource is being used by running jobs", + "active_jobs": active_jobs, + "conflict_type": "active_job_dependency" + } + + class PermissionDenied(APIException): + """Enhanced permission denied with context""" + status_code = 403 + + def __init__(self, detail=None, required_permission=None, resource=None): + super().__init__(detail) + self.detail = { + "error": detail or "Permission denied", + "required_permission": required_permission, + "resource": resource + } + +**Exception Requirements**: + +- **Structured Error Format**: Consistent error response structure +- **HTTP Status Codes**: Proper status code mapping for different error types +- **Context Information**: Rich error context for debugging and user feedback +- **Exception Hierarchy**: Logical exception inheritance structure + +6.2 Global Exception Handler +----------------------------- + +**REQUIRED**: Centralized exception processing: + +.. code-block:: python + + # api/views/__init__.py + def api_exception_handler(exc, context): + """Enhanced exception handler with logging and standardization""" + + # Handle database integrity errors + if isinstance(exc, IntegrityError): + exc = ParseError("Database constraint violation: " + str(exc)) + + # Handle Django field errors + if isinstance(exc, FieldError): + exc = ParseError("Invalid field specification: " + str(exc)) + + # Handle permission errors with context + if isinstance(exc, PermissionDenied): + logger.warning( + f"Permission denied for user {context['request'].user} " + f"accessing {context['request'].path}" + ) + + # Use default DRF exception handler + response = exception_handler(exc, context) + + # Add custom error metadata + if response is not None: + response.data['timestamp'] = timezone.now().isoformat() + response.data['path'] = context['request'].path + response.data['method'] = context['request'].method + + return response + +**Exception Handler Requirements**: + +- **Comprehensive Logging**: All exceptions must be logged with context +- **Error Transformation**: Convert database/system errors to API errors +- **Metadata Addition**: Include request context in error responses +- **Security Considerations**: Sanitize error messages for production + +6.3 Status Code Standards +------------------------- + +**REQUIRED**: Proper HTTP status code usage: + +.. list-table:: Status Code Requirements + :header-rows: 1 + :widths: 15 25 60 + + * - Code + - Usage + - Implementation Requirements + * - **200 OK** + - Successful GET, PUT, PATCH + - Include response timing headers + * - **201 Created** + - Successful POST + - Include Location header with new resource URL + * - **204 No Content** + - Successful DELETE + - No response body required + * - **400 Bad Request** + - Validation errors + - Include detailed field-level error information + * - **401 Unauthorized** + - Authentication required + - Include WWW-Authenticate header + * - **403 Forbidden** + - Permission denied + - Include required permission information + * - **404 Not Found** + - Resource not found + - Include suggested alternatives if available + * - **409 Conflict** + - Resource conflicts + - Include conflict resolution information + * - **422 Unprocessable Entity** + - Business logic errors + - Include business rule violation details + * - **500 Internal Server Error** + - System errors + - Log full error details, return sanitized message + +---- + +7. API Documentation and Discovery +================================== + +7.1 OpenAPI/Swagger Integration +------------------------------- + +**REQUIRED**: Comprehensive API documentation system: + +.. code-block:: python + + # api/swagger.py + class CustomSwaggerAutoSchema(SwaggerAutoSchema): + """Enhanced Swagger schema generation""" + + def get_tags(self, operation_keys=None): + """Generate logical API tags""" + tags = [] + + if hasattr(self.view, 'swagger_topic'): + tags.append(str(self.view.swagger_topic).title()) + elif hasattr(self.view, 'serializer_class'): + serializer = self.view.serializer_class + if hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'model'): + model_name = serializer.Meta.model._meta.verbose_name_plural + tags.append(str(model_name).title()) + + return tags + + def get_operation_id(self, operation_keys): + """Generate unique operation IDs""" + return '_'.join(operation_keys) + + def get_description(self): + """Enhanced operation descriptions""" + description = super().get_description() + + # Add permission requirements + if hasattr(self.view, 'required_permissions'): + description += f"\n\nRequired permissions: {self.view.required_permissions}" + + # Add rate limiting information + if hasattr(self.view, 'throttle_classes'): + description += f"\n\nRate limiting: Applied" + + return description + +**Documentation Requirements**: + +- **Auto-Generated Schemas**: Complete OpenAPI 3.0 schema generation +- **Logical Grouping**: API endpoints grouped by resource/functionality +- **Operation Metadata**: Detailed operation descriptions with examples +- **Permission Documentation**: Clear permission requirements for each endpoint +- **Response Examples**: Example responses for all status codes + +7.2 Rich Field Metadata +----------------------- + +**REQUIRED**: Comprehensive field information for clients: + +.. code-block:: python + + # api/metadata.py + class Metadata(metadata.SimpleMetadata): + """Enhanced metadata with comprehensive field information""" + + def get_field_info(self, field): + """Generate detailed field metadata""" + field_info = super().get_field_info(field) + + # Add validation information + field_info['validators'] = self._get_validator_info(field) + + # Add filtering capabilities + field_info['filterable'] = getattr(field, 'filterable', False) + field_info['searchable'] = getattr(field, 'searchable', False) + + # Add help text and examples + field_info['help_text'] = getattr(field, 'help_text', None) + field_info['example'] = getattr(field, 'example', None) + + # Add relationship information + if hasattr(field, 'queryset'): + field_info['related_model'] = field.queryset.model.__name__ + field_info['related_url'] = self._get_related_url(field) + + return field_info + + def _get_validator_info(self, field): + """Extract validator information""" + validators = [] + for validator in field.validators: + validators.append({ + 'type': validator.__class__.__name__, + 'message': getattr(validator, 'message', None) + }) + return validators + +**Metadata Requirements**: + +- **Validation Rules**: Complete field validation information +- **Filtering Support**: Indicate which fields support filtering/searching +- **Relationship Data**: Information about related resources +- **Help Text**: User-friendly field descriptions and examples +- **Client Tooling**: Metadata suitable for auto-generating client SDKs + +7.3 API Discoverability +----------------------- + +**REQUIRED**: Complete API traversal from root endpoint: + +.. code-block:: python + + # api/views/root.py + class ApiRootView(APIView): + """API root with complete endpoint discovery""" + + def get(self, request, format=None): + """Return all available API endpoints""" + endpoints = { + 'ping': reverse('api:ping', request=request), + 'config': reverse('api:config', request=request), + 'me': reverse('api:me', request=request), + 'dashboard': reverse('api:dashboard', request=request), + 'organizations': reverse('api:organization_list', request=request), + 'users': reverse('api:user_list', request=request), + 'teams': reverse('api:team_list', request=request), + 'projects': reverse('api:project_list', request=request), + 'inventories': reverse('api:inventory_list', request=request), + 'job_templates': reverse('api:job_template_list', request=request), + 'jobs': reverse('api:job_list', request=request), + } + + # Add versioning information + endpoints['current_version'] = request.version + endpoints['available_versions'] = ['v1', 'v2'] + + # Add authentication information + endpoints['auth_methods'] = [ + 'jwt', 'session', 'basic' + ] + + return Response(endpoints) + +**Discoverability Requirements**: + +- **Complete Traversal**: All endpoints accessible from API root +- **Version Information**: Available API versions clearly indicated +- **Authentication Methods**: Supported authentication mechanisms listed +- **Relationship Links**: Related resource URLs in all responses + +---- + +8. API Versioning Strategy +=========================== + +8.1 URL Path Versioning +----------------------- + +**REQUIRED**: Clean URL-based versioning system: + +.. code-block:: python + + # api/versioning.py + class URLPathVersioning(BaseVersioning): + """URL path-based API versioning""" + + version_param = 'version' + default_version = 'v2' + allowed_versions = ['v1', 'v2'] + + def determine_version(self, request, *args, **kwargs): + version = kwargs.get(self.version_param, self.default_version) + if version not in self.allowed_versions: + raise exceptions.NotFound(f"API version '{version}' not found") + return version + + def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): + if request.version is not None: + kwargs = {} if kwargs is None else kwargs + kwargs[self.version_param] = request.version + return super().reverse(viewname, args, kwargs, request, format, **extra) + +**Versioning Requirements**: + +- **URL Path Format**: Use ``/api/v{version}/`` format +- **Backward Compatibility**: Support multiple API versions simultaneously +- **Default Version**: Explicit default version configuration +- **Version Discovery**: Available versions discoverable from API root +- **Graceful Deprecation**: Clear deprecation timeline for old versions + +8.2 Version-Specific Behavior +----------------------------- + +**REQUIRED**: Handle version differences in views and serializers: + +.. code-block:: python + + # api/views/versioned.py + class VersionedViewMixin: + """Mixin for version-aware view behavior""" + + def get_serializer_class(self): + """Return version-specific serializer""" + base_serializer = super().get_serializer_class() + + if self.request.version == 'v1': + # Use legacy serializer for v1 + return getattr(self, 'v1_serializer_class', base_serializer) + elif self.request.version == 'v2': + # Use current serializer for v2 + return base_serializer + + return base_serializer + + def get_queryset(self): + """Return version-specific queryset""" + queryset = super().get_queryset() + + # Apply version-specific filtering + if self.request.version == 'v1': + # Legacy filtering logic + queryset = self._apply_v1_filters(queryset) + + return queryset + +**Version Management Requirements**: + +- **Serializer Versioning**: Version-specific serializer classes +- **Behavior Versioning**: Different view behavior for different versions +- **Data Migration**: Automatic data format conversion between versions +- **Feature Flags**: Version-specific feature availability + +---- + +9. Testing Requirements +======================= + +9.1 Comprehensive Test Coverage +------------------------------- + +**MANDATORY**: Complete API test coverage requirements: + +.. important:: + **Critical Testing Rule**: Every API URL/endpoint MUST have unit test coverage with both positive and negative test cases. + +.. code-block:: python + + # tests/api/test_resource.py + class TestResourceAPI: + """Comprehensive API testing for Resource endpoints""" + + def test_list_resources_success(self, api_client, user): + """Test successful resource listing""" + # Setup test data + resources = ResourceFactory.create_batch(3) + + # Make request + response = api_client.get('/api/v2/resources/') + + # Assertions + assert response.status_code == 200 + assert response.data['count'] == 3 + assert len(response.data['results']) == 3 + + def test_list_resources_permission_denied(self, api_client, user_without_permissions): + """Test resource listing without permissions""" + ResourceFactory.create_batch(3) + + api_client.force_authenticate(user=user_without_permissions) + response = api_client.get('/api/v2/resources/') + + assert response.status_code == 403 + assert 'permission' in response.data['error'].lower() + + def test_create_resource_success(self, api_client, user): + """Test successful resource creation""" + data = { + 'name': 'Test Resource', + 'description': 'Test Description', + 'configuration': {'key': 'value'} + } + + response = api_client.post('/api/v2/resources/', data) + + assert response.status_code == 201 + assert response.data['name'] == data['name'] + assert 'id' in response.data + + @pytest.mark.parametrize('invalid_data,expected_error', [ + ({'name': ''}, 'name'), + ({'name': 'test'}, 'description'), + ({'name': 'test', 'description': 'test', 'configuration': 'invalid'}, 'configuration'), + ]) + def test_create_resource_validation_errors(self, api_client, user, invalid_data, expected_error): + """Test resource creation validation""" + response = api_client.post('/api/v2/resources/', invalid_data) + + assert response.status_code == 400 + assert expected_error in response.data + +**Testing Requirements**: + +- **Positive Test Cases**: Test all successful operation scenarios +- **Negative Test Cases**: Test all error conditions and edge cases +- **Permission Testing**: Test access control for all user types +- **Validation Testing**: Test all field validation rules +- **Parameterized Testing**: Use data-driven test approaches +- **Factory Pattern**: Use factories for test data generation + +9.2 Performance Testing +----------------------- + +**REQUIRED**: API performance validation: + +.. code-block:: python + + # tests/api/test_performance.py + class TestAPIPerformance: + """Performance testing for API endpoints""" + + def test_query_count_constant(self, api_client, django_assert_num_queries): + """Ensure query count doesn't vary with result set size""" + + # Test with small dataset + ResourceFactory.create_batch(10) + with django_assert_num_queries(5): # Expected query count + response = api_client.get('/api/v2/resources/') + assert response.status_code == 200 + + # Test with larger dataset - query count should remain same + ResourceFactory.create_batch(90) # Total 100 resources + with django_assert_num_queries(5): # Same query count + response = api_client.get('/api/v2/resources/') + assert response.status_code == 200 + + def test_response_time_target(self, api_client): + """Ensure response time meets performance targets""" + import time + + start_time = time.time() + response = api_client.get('/api/v2/resources/') + end_time = time.time() + + response_time = end_time - start_time + assert response_time < 0.25 # 250ms target + assert response.status_code == 200 + +**Performance Test Requirements**: + +- **Query Count Testing**: Verify constant-time database queries +- **Response Time Testing**: Validate performance targets (≤250ms) +- **Load Testing**: Test with realistic data volumes +- **Memory Usage**: Monitor memory consumption patterns + +---- + +10. Security Requirements +========================= + +10.1 Authentication Security +---------------------------- + +**REQUIRED**: Secure authentication implementation: + +.. code-block:: python + + # Security configuration + AUTHENTICATION_SECURITY = { + 'PASSWORD_RESET_TIMEOUT': 3600, # 1 hour + 'LOGIN_ATTEMPT_LIMIT': 5, + 'LOGIN_LOCKOUT_DURATION': 300, # 5 minutes + 'JWT_ACCESS_TOKEN_LIFETIME': 3600, # 1 hour + 'JWT_REFRESH_TOKEN_LIFETIME': 604800, # 7 days + 'SESSION_COOKIE_AGE': 1800, # 30 minutes + 'CSRF_COOKIE_HTTPONLY': True, + 'SESSION_COOKIE_SECURE': True, + 'SESSION_COOKIE_SAMESITE': 'Lax', + } + +**Authentication Security Requirements**: + +- **Rate Limiting**: Implement login attempt limiting +- **Token Expiration**: Short-lived access tokens with refresh mechanism +- **Secure Cookies**: HTTPOnly, Secure, and SameSite cookie attributes +- **Brute Force Protection**: Account lockout after failed attempts +- **Session Management**: Configurable session timeouts + +10.2 Authorization Security +--------------------------- + +**REQUIRED**: Comprehensive access control: + +.. code-block:: python + + # api/permissions.py + class SecureModelAccessPermission(permissions.BasePermission): + """Security-enhanced permission checking""" + + def has_permission(self, request, view): + # Log all permission checks for security audit + logger.info( + f"Permission check: {request.user} accessing " + f"{view.__class__.__name__} via {request.method}" + ) + + # Check user account status + if not request.user.is_active: + logger.warning(f"Inactive user {request.user} attempted access") + return False + + # Check IP whitelist if configured + if hasattr(settings, 'API_IP_WHITELIST'): + client_ip = self._get_client_ip(request) + if client_ip not in settings.API_IP_WHITELIST: + logger.warning(f"Access denied from IP {client_ip}") + return False + + return super().has_permission(request, view) + +**Authorization Security Requirements**: + +- **Audit Logging**: Log all access attempts and permission checks +- **Account Status Checking**: Verify user account is active +- **IP Whitelisting**: Optional IP-based access control +- **Principle of Least Privilege**: Minimal required permissions only +- **Session Validation**: Verify session integrity on each request + +10.3 Data Protection +-------------------- + +**REQUIRED**: Secure data handling: + +.. code-block:: python + + # api/serializers.py + class SecureSerializer(BaseSerializer): + """Security-enhanced serializer""" + + sensitive_fields = ['password', 'secret_key', 'private_key', 'token'] + + def to_representation(self, instance): + """Remove sensitive data from responses""" + data = super().to_representation(instance) + + # Mask sensitive fields + for field in self.sensitive_fields: + if field in data: + data[field] = '***REDACTED***' + + return data + + def validate(self, attrs): + """Enhanced validation with security checks""" + attrs = super().validate(attrs) + + # Validate against known security patterns + for field, value in attrs.items(): + if self._contains_injection_attempt(value): + logger.warning(f"Potential injection attempt in field {field}") + raise serializers.ValidationError(f"Invalid value for {field}") + + return attrs + +**Data Protection Requirements**: + +- **Sensitive Data Masking**: Automatic masking of sensitive fields in responses +- **Input Sanitization**: Validate and sanitize all input data +- **Injection Prevention**: Protect against SQL injection and XSS attacks +- **Data Encryption**: Encrypt sensitive data at rest and in transit +- **Audit Trail**: Log all data access and modification events + +---- + +Compliance Checklist +===================== + +API Design Standards +-------------------- + +.. list-table:: + :header-rows: 1 + :widths: 50 10 + + * - Requirement + - Status + * - RESTful URL patterns implemented + - ☐ + * - Proper HTTP method usage + - ☐ + * - Consistent error responses + - ☐ + * - Comprehensive pagination + - ☐ + * - API versioning strategy + - ☐ + * - OpenAPI documentation + - ☐ + * - API discoverability from root + - ☐ + * - Performance targets met (≤250ms) + - ☐ + +Security Requirements +--------------------- + +.. list-table:: + :header-rows: 1 + :widths: 50 10 + + * - Requirement + - Status + * - Multi-factor authentication + - ☐ + * - RBAC implementation + - ☐ + * - Secure token management + - ☐ + * - Input validation and sanitization + - ☐ + * - Audit logging + - ☐ + * - Rate limiting + - ☐ + * - Data encryption + - ☐ + * - Security headers configured + - ☐ + +Performance Standards +--------------------- + +.. list-table:: + :header-rows: 1 + :widths: 50 10 + + * - Requirement + - Status + * - Constant-time database queries + - ☐ + * - Query optimization implemented + - ☐ + * - Effective caching strategy + - ☐ + * - Pagination performance optimized + - ☐ + * - Response time monitoring + - ☐ + * - Memory usage optimized + - ☐ + * - Database indexing proper + - ☐ + * - No N+1 query problems + - ☐ + +Testing Coverage +---------------- + +.. list-table:: + :header-rows: 1 + :widths: 50 10 + + * - Requirement + - Status + * - 100% endpoint test coverage + - ☐ + * - Positive and negative test cases + - ☐ + * - Permission testing complete + - ☐ + * - Performance testing implemented + - ☐ + * - Security testing coverage + - ☐ + * - Integration testing + - ☐ + * - Load testing completed + - ☐ + * - Automated test execution + - ☐ + +---- + +References +========== + +- **Django REST Framework Documentation**: https://www.django-rest-framework.org/ +- **OpenAPI Specification**: https://swagger.io/specification/ +- **HTTP Status Code Definitions**: https://tools.ietf.org/html/rfc7231#section-6 +- **OAuth 2.0 Authorization Framework**: https://tools.ietf.org/html/rfc6749 +- **JWT Best Practices**: https://tools.ietf.org/html/rfc8725 +- **AWX Source Code**: https://github.com/ansible/awx + +---- + +| **Document Maintainer**: API Development Team +| **Last Updated**: September 2025 +| **Review Schedule**: Quarterly +| **Compliance Audit**: Monthly \ No newline at end of file diff --git a/docs/docsite/rst/contributor/DJANGO_REQUIREMENTS.rst b/docs/docsite/rst/contributor/DJANGO_REQUIREMENTS.rst new file mode 100644 index 0000000000..fe5aebb1aa --- /dev/null +++ b/docs/docsite/rst/contributor/DJANGO_REQUIREMENTS.rst @@ -0,0 +1,725 @@ +===================================================== +Django Development Requirements +===================================================== + +**AWX Codebase Best Practices** + +:Version: 1.0 +:Date: September 2025 +:Based on: AWX Enterprise Django Application Analysis +:Generated by: Claude Code AI + +---- + +.. contents:: Table of Contents + :depth: 3 + :local: + +---- + +1. Project Structure +==================== + +1.1 Modular Application Architecture +------------------------------------ + +**REQUIRED**: Organize Django project with clear separation of concerns:: + + awx/ + ├── __init__.py # Version management and environment detection + ├── main/ # Core business logic and models + ├── api/ # REST API layer (Django REST Framework) + ├── ui/ # Frontend integration + ├── conf/ # Configuration management + ├── settings/ # Environment-specific settings + ├── templates/ # Django templates + └── static/ # Static assets + +**Requirements**: + +- Each functional area must have its own Django app +- Use descriptive app names that reflect business domains +- Separate API logic from core business logic + +1.2 Pre-Management Command Code +-------------------------------- + +This section describes the code that runs before every management command. + +AWX persistent services (i.e. wsrelay, heartbeat, dispatcher) all have management commands as entry points. So if you want to write a new persistent service, make a management command. + +System jobs are implemented as management commands too. + + +**REQUIRED**: Implement custom Django management integration: + +.. code-block:: python + + # awx/__init__.py + def manage(): + """Custom management function with environment preparation""" + prepare_env() + from django.core.management import execute_from_command_line + + # Version validation for production + if not MODE == 'development': + validate_production_requirements() + + execute_from_command_line(sys.argv) + +**Requirements**: + +- Environment detection (development/production modes) +- Production deployment validation +- Custom version checking mechanisms +- Database version compatibility checks + +---- + +2. Settings Management +====================== + +2.1 Environment-Based Settings Architecture +------------------------------------------- + +**REQUIRED**: Use ``django-split-settings`` for modular configuration:: + + # settings/defaults.py - Base configuration + # settings/development.py - Development overrides + # settings/production.py - Production security settings + # settings/testing.py - Test-specific configuration + +**Settings Pattern**: + +.. code-block:: python + + # development.py + from .defaults import * + from split_settings.tools import optional, include + + DEBUG = True + ALLOWED_HOSTS = ['*'] + + # Include optional local settings + include(optional('local_settings.py')) + +2.2 Sourcing config from files +------------------------------- + +**REQUIRED**: Sourcing config from multiple files (in a directory) on disk: + +.. code-block:: python + + # External settings loading + EXTERNAL_SETTINGS = os.environ.get('AWX_SETTINGS_FILE') + if EXTERNAL_SETTINGS: + include(EXTERNAL_SETTINGS, scope=locals()) + + +3. URL Patterns and Routing +============================ + +3.1 Modular URL Architecture +----------------------------- + +**REQUIRED**: Implement hierarchical URL organization with namespacing: + +.. code-block:: python + + # urls.py + def get_urlpatterns(prefix=None): + """Dynamic URL pattern generation with prefix support""" + if not prefix: + prefix = '/' + else: + prefix = f'/{prefix}/' + + return [ + path(f'api{prefix}', include('awx.api.urls', namespace='api')), + path(f'ui{prefix}', include('awx.ui.urls', namespace='ui')), + ] + + urlpatterns = get_urlpatterns() + +3.2 Environment-Specific URL Inclusion +-------------------------------------- + +**REQUIRED**: Conditional URL patterns based on environment: + +This example allows the Django debug toolbar to work. + +.. code-block:: python + + # Development-only URLs + if settings.DEBUG: + try: + import debug_toolbar + urlpatterns += [path('__debug__/', include(debug_toolbar.urls))] + except ImportError: + pass + + +**OPTIONAL**: If you want to include your own debug logic and endpoints: + +.. code-block:: python + + if MODE == 'development': + # Only include these if we are in the development environment + from awx.api.swagger import schema_view + + from awx.api.urls.debug import urls as debug_urls + + urlpatterns += [re_path(r'^debug/', include(debug_urls))] + urlpatterns += [ + re_path(r'^swagger(?P\.json|\.yaml)/$', schema_view.without_ui(cache_timeout=0), name='schema-json'), + re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), + re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), + ] + +**Requirements**: + +- Use Django's ``include()`` for modular organization +- Implement URL namespacing for API versioning +- Support dynamic URL prefix configuration +- Separate URL patterns by functional area + +---- + +4. Model Design +=============== + +4.1 Abstract Base Models +------------------------ + +**REQUIRED**: Use abstract base models for common functionality: + +.. code-block:: python + + # models/base.py + class BaseModel(models.Model): + """Common fields and methods for all models""" + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True + + class AuditableModel(BaseModel): + """Models requiring audit trail""" + created_by = models.ForeignKey(User, on_delete=models.CASCADE) + + class Meta: + abstract = True + +4.2 Mixin-Based Architecture +---------------------------- + +**REQUIRED**: Implement reusable model behaviors through mixins: + +.. code-block:: python + + # models/mixins.py + class ResourceMixin(models.Model): + """Common resource management functionality""" + class Meta: + abstract = True + + class ExecutionEnvironmentMixin(models.Model): + """Execution environment configuration""" + class Meta: + abstract = True + +4.3 Model Organization +---------------------- + +**REQUIRED**: Organize models by domain functionality:: + + models/ + ├── __init__.py + ├── base.py # Abstract base models + ├── mixins.py # Reusable model behaviors + ├── inventory.py # Inventory-related models + ├── jobs.py # Job execution models + ├── credential.py # Credential management + └── organization.py # Organization models + +**Requirements**: + +- One file per logical domain until the domain gets too big, create a folder for it instead. In the past, credentials were broken out into logical domains until they were moved out of AWX, then they were collapsed back down to a single file. +- Use consistent naming conventions +- Implement comprehensive model validation +- Custom managers for complex queries + +---- + +5. REST API Development +======================= + +5.1 Custom Authentication Classes +---------------------------------- + +The recommended best practice is to log all of the terminal (return) paths of authentication, not just the successful ones. + +**REQUIRED**: Implement domain-specific authentication with logging: + +.. code-block:: python + + # api/authentication.py + class LoggedBasicAuthentication(authentication.BasicAuthentication): + """Basic authentication with request logging""" + + def authenticate(self, request): + if not settings.AUTH_BASIC_ENABLED: + return + + ret = super().authenticate(request) + if ret: + username = ret[0].username if ret[0] else '' + logger.info( + f"User {username} performed {request.method} " + f"to {request.path} through the API" + ) + return ret + +5.2 Custom Permission Classes +----------------------------- + +**REQUIRED**: Implement comprehensive permission checking: + +.. code-block:: python + + # api/permissions.py + class ModelAccessPermission(permissions.BasePermission): + """Model-based access control with hierarchy support""" + + def has_permission(self, request, view): + if hasattr(view, 'parent_model'): + parent_obj = view.get_parent_object() + return check_user_access( + request.user, + view.parent_model, + 'read', + parent_obj + ) + return True + +**Requirements**: + +- Multiple authentication methods (JWT, Session, Basic) +- Custom pagination, renderers, and metadata classes +- Comprehensive API exception handling +- Resource-based URL organization +- Logging for authentication events + +---- + +6. Security Requirements +======================== + +6.1 Production Security Settings +-------------------------------- + +**REQUIRED**: Enforce secure defaults for production: + +.. code-block:: python + + # settings/production.py + DEBUG = False + SECRET_KEY = None # Force explicit configuration + ALLOWED_HOSTS = [] # Must be explicitly set + + # Session security + SESSION_COOKIE_SECURE = True + SESSION_COOKIE_HTTPONLY = True + SESSION_COOKIE_SAMESITE = 'Lax' + SESSION_COOKIE_AGE = 1800 + + # CSRF protection + CSRF_COOKIE_SECURE = True + CSRF_COOKIE_HTTPONLY = True + CSRF_TRUSTED_ORIGINS = [] + +6.2 Django SECRET_KEY loading +------------------------------ + +**REQUIRED**: Implement Django SECRET_KEY loading: + +.. code-block:: python + + # Secret key from external file + SECRET_KEY_FILE = os.environ.get('SECRET_KEY_FILE', '/etc/awx/SECRET_KEY') + if os.path.exists(SECRET_KEY_FILE): + with open(SECRET_KEY_FILE, 'rb') as f: + SECRET_KEY = f.read().strip().decode() + else: + if not DEBUG: + raise ImproperlyConfigured("SECRET_KEY must be configured in production") + +For more detail, refer to the `Django documentation `_. + +6.3 Proxy and Network Security +------------------------------ + +**REQUIRED**: Configure reverse proxy security: + +.. code-block:: python + + # Proxy configuration + REMOTE_HOST_HEADERS = ['REMOTE_ADDR', 'REMOTE_HOST'] + PROXY_IP_ALLOWED_LIST = [] + USE_X_FORWARDED_HOST = True + USE_X_FORWARDED_PORT = True + SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + +**Requirements**: + +- External secret file management +- Secure cookie configuration +- CSRF protection with trusted origins +- Proxy header validation +- Force HTTPS in production + +---- + +7. Database Management +====================== + +7.1 Advanced Database Configuration +----------------------------------- + +**REQUIRED**: Robust database connections for production: + +.. code-block:: python + + # Database configuration with connection tuning + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ.get('DATABASE_NAME', 'awx'), + 'ATOMIC_REQUESTS': True, + 'CONN_MAX_AGE': 0, + 'OPTIONS': { + 'keepalives': 1, + 'keepalives_idle': 5, + 'keepalives_interval': 5, + 'keepalives_count': 5, + }, + } + } + +7.2 Database Version Validation +------------------------------- + +**REQUIRED**: Implement database compatibility checking: + +.. code-block:: python + + # PostgreSQL version enforcement + def validate_database_version(): + from django.db import connection + if (connection.pg_version // 10000) < 12: + raise ImproperlyConfigured( + "PostgreSQL version 12 or higher is required" + ) + +7.3 Migration Management +------------------------ + +**REQUIRED**: Structured migration organization + +:: + + migrations/ + ├── 0001_initial.py + ├── 0002_squashed_v300_release.py + ├── 0003_squashed_v300_v303_updates.py + └── _migration_utils.py + +**Requirements**: +It is best practice to not to re-write migrations. If possible, include a reverse migration, especially for data migrations to make testing easier. + +---- + +8. Testing Standards +==================== + +8.1 Pytest Configuration +------------------------- + +**REQUIRED**: Comprehensive test setup with optimization: + +.. code-block:: ini + + # pytest.ini + [pytest] + DJANGO_SETTINGS_MODULE = awx.main.tests.settings_for_test + python_files = *.py + addopts = --reuse-db --nomigrations --tb=native + markers = + ac: access control test + survey: tests related to survey feature + inventory_import: tests of code used by inventory import command + integration: integration tests requiring external services + +8.2 Test Settings Module +------------------------- + +**REQUIRED**: Dedicated test configuration: + +.. code-block:: python + + # settings/testing.py + from .defaults import * + + # Fast test database + DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3' + DATABASES['default']['NAME'] = ':memory:' + + # Disable migrations for speed + class DisableMigrations: + def __contains__(self, item): + return True + def __getitem__(self, item): + return None + + MIGRATION_MODULES = DisableMigrations() + +8.3 Coverage Requirements +------------------------- + +**REQUIRED**: Enforce comprehensive test coverage: + +.. code-block:: python + + # Coverage targets + COVERAGE_TARGETS = { + 'project_overall': 75, + 'library_code': 75, + 'test_code': 95, + 'new_patches': 100, + 'type_checking': 100, + } + +**Requirements**: + +- Database reuse for faster execution +- Skip migrations in tests +- Custom test markers for categorization +- Dedicated test settings module +- Comprehensive warning filters + +---- + +9. Application Configuration +============================= + +9.1 Advanced AppConfig Implementation +-------------------------------------- + +**REQUIRED**: Custom application configuration with initialization: + +.. code-block:: python + + # apps.py + class MainConfig(AppConfig): + name = 'awx.main' + verbose_name = _('Main') + default_auto_field = 'django.db.models.AutoField' + + def ready(self): + super().ready() + + # Feature loading with environment checks + if not os.environ.get('AWX_SKIP_FEATURES', None): + self.load_credential_types() + self.load_inventory_plugins() + self.load_named_urls() + + # Signal registration + self.register_signals() + + def load_credential_types(self): + """Load credential type definitions""" + pass + + def register_signals(self): + """Register Django signals""" + pass + +**Requirements**: + +- Custom AppConfig for complex initialization +- Feature loading in ``ready()`` method +- Environment-based feature toggling +- Plugin system integration +- Signal registration + +---- + +10. Middleware Implementation +============================= + +10.1 Custom Middleware for Enterprise Features +---------------------------------------------- + +**REQUIRED**: Implement domain-specific middleware: + +.. code-block:: python + + # middleware.py + class SettingsCacheMiddleware(MiddlewareMixin): + """Clear settings cache on each request""" + + def process_request(self, request): + from django.conf import settings + if hasattr(settings, '_awx_conf_memoizedcache'): + settings._awx_conf_memoizedcache.clear() + + class TimingMiddleware(threading.local, MiddlewareMixin): + """Request timing and performance monitoring""" + + def process_request(self, request): + self.start_time = time.time() + + def process_response(self, request, response): + if hasattr(self, 'start_time'): + duration = time.time() - self.start_time + response['X-Response-Time'] = f"{duration:.3f}s" + return response + +**Requirements**: + +- Settings cache management middleware +- Performance monitoring middleware +- Thread-local storage for request data +- Conditional middleware activation + +---- + +11. Deployment Patterns +======================== + +11.1 Production-Ready ASGI/WSGI Configuration +--------------------------------------------- + +**REQUIRED**: Proper application server setup: + +.. code-block:: python + + # asgi.py + import os + import django + from channels.routing import get_default_application + from awx import prepare_env + + prepare_env() + django.setup() + + application = get_default_application() + + # wsgi.py + import os + from django.core.wsgi import get_wsgi_application + from awx import prepare_env + + prepare_env() + application = get_wsgi_application() + +---- + +Compliance Checklist +===================== + +Development Standards +--------------------- + +.. list-table:: + :header-rows: 1 + :widths: 50 10 + + * - Requirement + - Status + * - Modular app architecture implemented + - ☐ + * - Environment-based settings configured + - ☐ + * - Custom authentication and permissions + - ☐ + * - Comprehensive test coverage (>75%) + - ☐ + * - Security settings enforced + - ☐ + * - Database optimization configured + - ☐ + * - Static files properly organized + - ☐ + * - Custom middleware implemented + - ☐ + +Production Readiness +-------------------- + +.. list-table:: + :header-rows: 1 + :widths: 50 10 + + * - Requirement + - Status + * - External secret management + - ☐ + * - Database version validation + - ☐ + * - Version deployment verification + - ☐ + * - Performance monitoring + - ☐ + * - Security headers configured + - ☐ + * - HTTPS enforcement + - ☐ + * - Proper logging setup + - ☐ + * - Error handling and monitoring + - ☐ + +Code Quality +------------ + +.. list-table:: + :header-rows: 1 + :widths: 50 10 + + * - Requirement + - Status + * - Abstract base models used + - ☐ + * - Mixin-based architecture + - ☐ + * - Custom management commands + - ☐ + * - Plugin system support + - ☐ + * - Signal registration + - ☐ + * - Migration organization + - ☐ + * - API documentation + - ☐ + * - Type hints and validation + - ☐ + +---- + +References +========== + +- **Django Documentation**: https://docs.djangoproject.com/ +- **Django REST Framework**: https://www.django-rest-framework.org/ +- **Django Split Settings**: https://github.com/sobolevn/django-split-settings +- **AWX Source Code**: https://github.com/ansible/awx + +---- + +| **Document Maintainer**: Development Team +| **Last Updated**: September 2025 +| **Review Schedule**: Quarterly \ No newline at end of file diff --git a/docs/docsite/rst/index.rst b/docs/docsite/rst/index.rst index 5df7acf102..a26d2fd6f5 100644 --- a/docs/docsite/rst/index.rst +++ b/docs/docsite/rst/index.rst @@ -15,6 +15,8 @@ Ansible AWX helps teams manage complex multi-tier deployments by adding control, :caption: Community contributor/index + contributor/DJANGO_REQUIREMENTS + contributor/API_REQUIREMENTS .. toctree:: :maxdepth: 2