Refactor test utilities and enhance ASGI settings

- Cleaned up and standardized assertions in ApiTestMixin for API response validation.
- Updated ASGI settings to use os.environ for setting the DJANGO_SETTINGS_MODULE.
- Removed unused imports and improved formatting in settings.py.
- Refactored URL patterns in urls.py for better readability and organization.
- Enhanced view functions in views.py for consistency and clarity.
- Added .flake8 configuration for linting and style enforcement.
- Introduced type stubs for django-environ to improve type checking with Pylance.
This commit is contained in:
pacnpal
2025-08-20 19:51:59 -04:00
parent 69c07d1381
commit 66ed4347a9
230 changed files with 15094 additions and 11578 deletions

View File

@@ -12,79 +12,79 @@ class ApiMixin:
"""
Base mixin for API views providing standardized response formatting.
"""
def create_response(
self,
*,
data: Any = None,
self,
*,
data: Any = None,
message: Optional[str] = None,
status_code: int = status.HTTP_200_OK,
pagination: Optional[Dict[str, Any]] = None,
metadata: Optional[Dict[str, Any]] = None
metadata: Optional[Dict[str, Any]] = None,
) -> Response:
"""
Create standardized API response.
Args:
data: Response data
message: Optional success message
status_code: HTTP status code
pagination: Pagination information
metadata: Additional metadata
Returns:
Standardized Response object
"""
response_data = {
'status': 'success' if status_code < 400 else 'error',
'data': data,
"status": "success" if status_code < 400 else "error",
"data": data,
}
if message:
response_data['message'] = message
response_data["message"] = message
if pagination:
response_data['pagination'] = pagination
response_data["pagination"] = pagination
if metadata:
response_data['metadata'] = metadata
response_data["metadata"] = metadata
return Response(response_data, status=status_code)
def create_error_response(
self,
*,
message: str,
status_code: int = status.HTTP_400_BAD_REQUEST,
error_code: Optional[str] = None,
details: Optional[Dict[str, Any]] = None
details: Optional[Dict[str, Any]] = None,
) -> Response:
"""
Create standardized error response.
Args:
message: Error message
status_code: HTTP status code
error_code: Optional error code
details: Additional error details
Returns:
Standardized error Response object
"""
error_data = {
'code': error_code or 'GENERIC_ERROR',
'message': message,
"code": error_code or "GENERIC_ERROR",
"message": message,
}
if details:
error_data['details'] = details
error_data["details"] = details
response_data = {
'status': 'error',
'error': error_data,
'data': None,
"status": "error",
"error": error_data,
"data": None,
}
return Response(response_data, status=status_code)
@@ -92,37 +92,37 @@ class CreateApiMixin(ApiMixin):
"""
Mixin for create API endpoints with standardized input/output handling.
"""
def create(self, request: Request, *args, **kwargs) -> Response:
"""Handle POST requests for creating resources."""
serializer = self.get_input_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
# Create the object using the service layer
obj = self.perform_create(**serializer.validated_data)
# Serialize the output
output_serializer = self.get_output_serializer(obj)
return self.create_response(
data=output_serializer.data,
status_code=status.HTTP_201_CREATED,
message="Resource created successfully"
message="Resource created successfully",
)
def perform_create(self, **validated_data):
"""
Override this method to implement object creation logic.
Should use service layer methods.
"""
raise NotImplementedError("Subclasses must implement perform_create")
def get_input_serializer(self, *args, **kwargs):
"""Get the input serializer for validation."""
return self.InputSerializer(*args, **kwargs)
def get_output_serializer(self, *args, **kwargs):
"""Get the output serializer for response."""
"""Get the output serializer for response."""
return self.OutputSerializer(*args, **kwargs)
@@ -130,35 +130,37 @@ class UpdateApiMixin(ApiMixin):
"""
Mixin for update API endpoints with standardized input/output handling.
"""
def update(self, request: Request, *args, **kwargs) -> Response:
"""Handle PUT/PATCH requests for updating resources."""
instance = self.get_object()
serializer = self.get_input_serializer(data=request.data, partial=kwargs.get('partial', False))
serializer = self.get_input_serializer(
data=request.data, partial=kwargs.get("partial", False)
)
serializer.is_valid(raise_exception=True)
# Update the object using the service layer
updated_obj = self.perform_update(instance, **serializer.validated_data)
# Serialize the output
output_serializer = self.get_output_serializer(updated_obj)
return self.create_response(
data=output_serializer.data,
message="Resource updated successfully"
message="Resource updated successfully",
)
def perform_update(self, instance, **validated_data):
"""
Override this method to implement object update logic.
Should use service layer methods.
"""
raise NotImplementedError("Subclasses must implement perform_update")
def get_input_serializer(self, *args, **kwargs):
"""Get the input serializer for validation."""
return self.InputSerializer(*args, **kwargs)
def get_output_serializer(self, *args, **kwargs):
"""Get the output serializer for response."""
return self.OutputSerializer(*args, **kwargs)
@@ -168,29 +170,31 @@ class ListApiMixin(ApiMixin):
"""
Mixin for list API endpoints with pagination and filtering.
"""
def list(self, request: Request, *args, **kwargs) -> Response:
"""Handle GET requests for listing resources."""
# Use selector to get filtered queryset
queryset = self.get_queryset()
# Apply pagination
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_output_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
# No pagination
serializer = self.get_output_serializer(queryset, many=True)
return self.create_response(data=serializer.data)
def get_queryset(self):
"""
Override this method to use selector patterns.
Should call selector functions, not access model managers directly.
"""
raise NotImplementedError("Subclasses must implement get_queryset using selectors")
raise NotImplementedError(
"Subclasses must implement get_queryset using selectors"
)
def get_output_serializer(self, *args, **kwargs):
"""Get the output serializer for response."""
return self.OutputSerializer(*args, **kwargs)
@@ -200,21 +204,23 @@ class RetrieveApiMixin(ApiMixin):
"""
Mixin for retrieve API endpoints.
"""
def retrieve(self, request: Request, *args, **kwargs) -> Response:
"""Handle GET requests for retrieving a single resource."""
instance = self.get_object()
serializer = self.get_output_serializer(instance)
return self.create_response(data=serializer.data)
def get_object(self):
"""
Override this method to use selector patterns.
Should call selector functions for optimized queries.
"""
raise NotImplementedError("Subclasses must implement get_object using selectors")
raise NotImplementedError(
"Subclasses must implement get_object using selectors"
)
def get_output_serializer(self, *args, **kwargs):
"""Get the output serializer for response."""
return self.OutputSerializer(*args, **kwargs)
@@ -224,29 +230,31 @@ class DestroyApiMixin(ApiMixin):
"""
Mixin for delete API endpoints.
"""
def destroy(self, request: Request, *args, **kwargs) -> Response:
"""Handle DELETE requests for destroying resources."""
instance = self.get_object()
# Delete using service layer
self.perform_destroy(instance)
return self.create_response(
status_code=status.HTTP_204_NO_CONTENT,
message="Resource deleted successfully"
message="Resource deleted successfully",
)
def perform_destroy(self, instance):
"""
Override this method to implement object deletion logic.
Should use service layer methods.
"""
raise NotImplementedError("Subclasses must implement perform_destroy")
def get_object(self):
"""
Override this method to use selector patterns.
Should call selector functions for optimized queries.
"""
raise NotImplementedError("Subclasses must implement get_object using selectors")
raise NotImplementedError(
"Subclasses must implement get_object using selectors"
)