awx/docs/docsite/rst/contributor/API_REQUIREMENTS.rst
TVo 0d9483b54c
Added Django and API requirements to AWX Contributor Docs for POC (#16093)
* Requirements POC docs from Claude Code eval

* Removed unnecessary reference.

* Excluded custom DRF configurations per @AlanCoding

* Implement review changes from @chrismeyersfsu

---------

Co-authored-by: Peter Braun <pbraun@redhat.com>
2025-10-16 10:38:37 -06:00

1305 lines
43 KiB
ReStructuredText

=====================================================
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<pk>[0-9]+)/$', ResourceDetail.as_view(), name='resource_detail'),
# Nested resource relationships
re_path(r'^(?P<pk>[0-9]+)/children/$', ResourceChildrenList.as_view(),
name='resource_children_list'),
# Action endpoints
re_path(r'^(?P<pk>[0-9]+)/launch/$', ResourceLaunch.as_view(),
name='resource_launch'),
re_path(r'^(?P<pk>[0-9]+)/copy/$', ResourceCopy.as_view(),
name='resource_copy'),
re_path(r'^(?P<pk>[0-9]+)/callback/$', ResourceCallback.as_view(),
name='resource_callback'),
]
**URL Requirements**:
- **Numeric Primary Keys**: Use ``(?P<pk>[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 '<none>'
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