===================================================== 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