mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:11:08 -05:00
- 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.
261 lines
7.9 KiB
Python
261 lines
7.9 KiB
Python
"""
|
|
Common mixins for API views following Django styleguide patterns.
|
|
"""
|
|
|
|
from typing import Dict, Any, Optional
|
|
from rest_framework.request import Request
|
|
from rest_framework.response import Response
|
|
from rest_framework import status
|
|
|
|
|
|
class ApiMixin:
|
|
"""
|
|
Base mixin for API views providing standardized response formatting.
|
|
"""
|
|
|
|
def create_response(
|
|
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,
|
|
) -> 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,
|
|
}
|
|
|
|
if message:
|
|
response_data["message"] = message
|
|
|
|
if pagination:
|
|
response_data["pagination"] = pagination
|
|
|
|
if 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,
|
|
) -> 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,
|
|
}
|
|
|
|
if details:
|
|
error_data["details"] = details
|
|
|
|
response_data = {
|
|
"status": "error",
|
|
"error": error_data,
|
|
"data": None,
|
|
}
|
|
|
|
return Response(response_data, status=status_code)
|
|
|
|
|
|
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",
|
|
)
|
|
|
|
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."""
|
|
return self.OutputSerializer(*args, **kwargs)
|
|
|
|
|
|
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.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",
|
|
)
|
|
|
|
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)
|
|
|
|
|
|
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"
|
|
)
|
|
|
|
def get_output_serializer(self, *args, **kwargs):
|
|
"""Get the output serializer for response."""
|
|
return self.OutputSerializer(*args, **kwargs)
|
|
|
|
|
|
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"
|
|
)
|
|
|
|
def get_output_serializer(self, *args, **kwargs):
|
|
"""Get the output serializer for response."""
|
|
return self.OutputSerializer(*args, **kwargs)
|
|
|
|
|
|
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",
|
|
)
|
|
|
|
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"
|
|
)
|