feat: Implement initial schema and add various API, service, and management command enhancements across the application.

This commit is contained in:
pacnpal
2026-01-01 15:13:01 -05:00
parent c95f99ca10
commit b243b17af7
413 changed files with 11164 additions and 17433 deletions

View File

@@ -39,7 +39,7 @@ class ContractCompliantAPIView(APIView):
response = super().dispatch(request, *args, **kwargs)
# Validate contract in DEBUG mode
if settings.DEBUG and hasattr(response, 'data'):
if settings.DEBUG and hasattr(response, "data"):
self._validate_response_contract(response.data)
return response
@@ -49,19 +49,18 @@ class ContractCompliantAPIView(APIView):
logger.error(
f"API error in {self.__class__.__name__}: {str(e)}",
extra={
'view_class': self.__class__.__name__,
'request_path': request.path,
'request_method': request.method,
'user': getattr(request, 'user', None),
'error': str(e)
"view_class": self.__class__.__name__,
"request_path": request.path,
"request_method": request.method,
"user": getattr(request, "user", None),
"detail": str(e),
},
exc_info=True
exc_info=True,
)
# Return standardized error response
return self.error_response(
message="An internal error occurred",
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
message="An internal error occurred", status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
)
def success_response(
@@ -69,7 +68,7 @@ class ContractCompliantAPIView(APIView):
data: Any = None,
message: str = None,
status_code: int = status.HTTP_200_OK,
headers: dict[str, str] = None
headers: dict[str, str] = None,
) -> Response:
"""
Create a standardized success response.
@@ -83,21 +82,15 @@ class ContractCompliantAPIView(APIView):
Returns:
Response with standardized format
"""
response_data = {
'success': True
}
response_data = {"success": True}
if data is not None:
response_data['data'] = data
response_data["data"] = data
if message:
response_data['message'] = message
response_data["message"] = message
return Response(
response_data,
status=status_code,
headers=headers
)
return Response(response_data, status=status_code, headers=headers)
def error_response(
self,
@@ -105,7 +98,7 @@ class ContractCompliantAPIView(APIView):
status_code: int = status.HTTP_400_BAD_REQUEST,
error_code: str = None,
details: Any = None,
headers: dict[str, str] = None
headers: dict[str, str] = None,
) -> Response:
"""
Create a standardized error response.
@@ -120,37 +113,22 @@ class ContractCompliantAPIView(APIView):
Returns:
Response with standardized error format
"""
error_data = {
'code': error_code or 'API_ERROR',
'message': message
}
error_data = {"code": error_code or "API_ERROR", "message": message}
if details:
error_data['details'] = details
error_data["details"] = details
# Add user context if available
if hasattr(self, 'request') and hasattr(self.request, 'user'):
if hasattr(self, "request") and hasattr(self.request, "user"):
user = self.request.user
if user and user.is_authenticated:
error_data['request_user'] = user.username
error_data["request_user"] = user.username
response_data = {
'status': 'error',
'error': error_data,
'data': None
}
response_data = {"status": "error", "error": error_data, "data": None}
return Response(
response_data,
status=status_code,
headers=headers
)
return Response(response_data, status=status_code, headers=headers)
def validation_error_response(
self,
errors: dict[str, Any],
message: str = "Validation failed"
) -> Response:
def validation_error_response(self, errors: dict[str, Any], message: str = "Validation failed") -> Response:
"""
Create a standardized validation error response.
@@ -161,14 +139,7 @@ class ContractCompliantAPIView(APIView):
Returns:
Response with validation errors
"""
return Response(
{
'success': False,
'message': message,
'errors': errors
},
status=status.HTTP_400_BAD_REQUEST
)
return Response({"success": False, "message": message, "errors": errors}, status=status.HTTP_400_BAD_REQUEST)
def _validate_response_contract(self, data: Any) -> None:
"""
@@ -179,7 +150,7 @@ class ContractCompliantAPIView(APIView):
"""
try:
# Check if this looks like filter metadata
if isinstance(data, dict) and 'categorical' in data and 'ranges' in data:
if isinstance(data, dict) and "categorical" in data and "ranges" in data:
validate_filter_metadata_contract(data)
# Add more contract validations as needed
@@ -188,10 +159,10 @@ class ContractCompliantAPIView(APIView):
logger.warning(
f"Contract validation failed in {self.__class__.__name__}: {str(e)}",
extra={
'view_class': self.__class__.__name__,
'validation_error': str(e),
'response_data_type': type(data).__name__
}
"view_class": self.__class__.__name__,
"validation_error": str(e),
"response_data_type": type(data).__name__,
},
)
@@ -225,17 +196,11 @@ class FilterMetadataAPIView(ContractCompliantAPIView):
except Exception as e:
logger.error(
f"Error getting filter metadata in {self.__class__.__name__}: {str(e)}",
extra={
'view_class': self.__class__.__name__,
'error': str(e)
},
exc_info=True
extra={"view_class": self.__class__.__name__, "detail": str(e)},
exc_info=True,
)
return self.error_response(
message="Failed to retrieve filter metadata",
error_code="FILTER_METADATA_ERROR"
)
return self.error_response(message="Failed to retrieve filter metadata", error_code="FILTER_METADATA_ERROR")
class HybridFilteringAPIView(ContractCompliantAPIView):
@@ -276,17 +241,14 @@ class HybridFilteringAPIView(ContractCompliantAPIView):
logger.error(
f"Error in hybrid filtering for {self.__class__.__name__}: {str(e)}",
extra={
'view_class': self.__class__.__name__,
'filters': getattr(self, '_extracted_filters', {}),
'error': str(e)
"view_class": self.__class__.__name__,
"filters": getattr(self, "_extracted_filters", {}),
"detail": str(e),
},
exc_info=True
exc_info=True,
)
return self.error_response(
message="Failed to retrieve filtered data",
error_code="HYBRID_FILTERING_ERROR"
)
return self.error_response(message="Failed to retrieve filtered data", error_code="HYBRID_FILTERING_ERROR")
def extract_filters(self, request) -> dict[str, Any]:
"""
@@ -313,19 +275,19 @@ class HybridFilteringAPIView(ContractCompliantAPIView):
def _validate_hybrid_response(self, data: dict[str, Any]) -> None:
"""Validate hybrid response structure."""
required_fields = ['strategy', 'total_count']
required_fields = ["strategy", "total_count"]
for field in required_fields:
if field not in data:
raise ValueError(f"Hybrid response missing required field: {field}")
# Validate strategy value
if data['strategy'] not in ['client_side', 'server_side']:
if data["strategy"] not in ["client_side", "server_side"]:
raise ValueError(f"Invalid strategy value: {data['strategy']}")
# Validate filter metadata if present
if 'filter_metadata' in data:
validate_filter_metadata_contract(data['filter_metadata'])
if "filter_metadata" in data:
validate_filter_metadata_contract(data["filter_metadata"])
class PaginatedAPIView(ContractCompliantAPIView):
@@ -340,11 +302,7 @@ class PaginatedAPIView(ContractCompliantAPIView):
max_page_size = 100
def get_paginated_response(
self,
queryset,
serializer_class: type[Serializer],
request,
page_size: int = None
self, queryset, serializer_class: type[Serializer], request, page_size: int = None
) -> Response:
"""
Create a paginated response.
@@ -362,13 +320,10 @@ class PaginatedAPIView(ContractCompliantAPIView):
# Determine page size
if page_size is None:
page_size = min(
int(request.query_params.get('page_size', self.default_page_size)),
self.max_page_size
)
page_size = min(int(request.query_params.get("page_size", self.default_page_size)), self.max_page_size)
# Get page number
page_number = request.query_params.get('page', 1)
page_number = request.query_params.get("page", 1)
try:
page_number = int(page_number)
@@ -389,28 +344,28 @@ class PaginatedAPIView(ContractCompliantAPIView):
serializer = serializer_class(page.object_list, many=True)
# Build pagination URLs
request_url = request.build_absolute_uri().split('?')[0]
request_url = request.build_absolute_uri().split("?")[0]
query_params = request.query_params.copy()
next_url = None
if page.has_next():
query_params['page'] = page.next_page_number()
query_params["page"] = page.next_page_number()
next_url = f"{request_url}?{query_params.urlencode()}"
previous_url = None
if page.has_previous():
query_params['page'] = page.previous_page_number()
query_params["page"] = page.previous_page_number()
previous_url = f"{request_url}?{query_params.urlencode()}"
# Create response data
response_data = {
'count': paginator.count,
'next': next_url,
'previous': previous_url,
'results': serializer.data,
'page_size': page_size,
'current_page': page.number,
'total_pages': paginator.num_pages
"count": paginator.count,
"next": next_url,
"previous": previous_url,
"results": serializer.data,
"page_size": page_size,
"current_page": page.number,
"total_pages": paginator.num_pages,
}
return self.success_response(response_data)
@@ -430,29 +385,23 @@ def contract_compliant_view(view_class):
response = original_dispatch(self, request, *args, **kwargs)
# Add contract validation in DEBUG mode
if settings.DEBUG and hasattr(response, 'data'):
if settings.DEBUG and hasattr(response, "data"):
# Basic validation - can be extended
pass
return response
except Exception as e:
logger.error(
f"Error in decorated view {view_class.__name__}: {str(e)}",
exc_info=True
)
logger.error(f"Error in decorated view {view_class.__name__}: {str(e)}", exc_info=True)
# Return basic error response
return Response(
{
'status': 'error',
'error': {
'code': 'API_ERROR',
'message': 'An internal error occurred'
},
'data': None
"status": "error",
"error": {"code": "API_ERROR", "detail": "An internal error occurred"},
"data": None,
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
view_class.dispatch = new_dispatch