mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-23 13:11:08 -05:00
feat: Refactor rides app with unique constraints, mixins, and enhanced documentation
- Added migration to convert unique_together constraints to UniqueConstraint for RideModel. - Introduced RideFormMixin for handling entity suggestions in ride forms. - Created comprehensive code standards documentation outlining formatting, docstring requirements, complexity guidelines, and testing requirements. - Established error handling guidelines with a structured exception hierarchy and best practices for API and view error handling. - Documented view pattern guidelines, emphasizing the use of CBVs, FBVs, and ViewSets with examples. - Implemented a benchmarking script for query performance analysis and optimization. - Developed security documentation detailing measures, configurations, and a security checklist. - Compiled a database optimization guide covering indexing strategies, query optimization patterns, and computed fields.
This commit is contained in:
@@ -306,35 +306,39 @@ class ModerationReportViewSet(viewsets.ModelViewSet):
|
||||
|
||||
return Response(stats_data)
|
||||
|
||||
@action(detail=True, methods=['get'], permission_classes=[CanViewModerationData])
|
||||
@action(detail=True, methods=["get"], permission_classes=[CanViewModerationData])
|
||||
def history(self, request, pk=None):
|
||||
"""Get transition history for this report."""
|
||||
from django_fsm_log.models import StateLog
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
|
||||
report = self.get_object()
|
||||
content_type = ContentType.objects.get_for_model(report)
|
||||
|
||||
logs = StateLog.objects.filter(
|
||||
content_type=content_type,
|
||||
object_id=report.id
|
||||
).select_related('by').order_by('-timestamp')
|
||||
|
||||
history_data = [{
|
||||
'id': log.id,
|
||||
'timestamp': log.timestamp,
|
||||
'state': log.state,
|
||||
'from_state': log.source_state,
|
||||
'to_state': log.state,
|
||||
'transition': log.transition,
|
||||
'user': log.by.username if log.by else None,
|
||||
'description': log.description,
|
||||
'reason': log.description,
|
||||
} for log in logs]
|
||||
|
||||
logs = (
|
||||
StateLog.objects.filter(content_type=content_type, object_id=report.id)
|
||||
.select_related("by")
|
||||
.order_by("-timestamp")
|
||||
)
|
||||
|
||||
history_data = [
|
||||
{
|
||||
"id": log.id,
|
||||
"timestamp": log.timestamp,
|
||||
"state": log.state,
|
||||
"from_state": log.source_state,
|
||||
"to_state": log.state,
|
||||
"transition": log.transition,
|
||||
"user": log.by.username if log.by else None,
|
||||
"description": log.description,
|
||||
"reason": log.description,
|
||||
}
|
||||
for log in logs
|
||||
]
|
||||
|
||||
return Response(history_data)
|
||||
|
||||
@action(detail=False, methods=['get'], permission_classes=[CanViewModerationData])
|
||||
@action(detail=False, methods=["get"], permission_classes=[CanViewModerationData])
|
||||
def all_history(self, request):
|
||||
"""Get all transition history with filtering.
|
||||
|
||||
@@ -343,61 +347,77 @@ class ModerationReportViewSet(viewsets.ModelViewSet):
|
||||
from django_fsm_log.models import StateLog
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
queryset = StateLog.objects.select_related('by', 'content_type').all()
|
||||
queryset = StateLog.objects.select_related("by", "content_type").all()
|
||||
|
||||
# Filter by id (for detail view)
|
||||
log_id = request.query_params.get('id')
|
||||
log_id = request.query_params.get("id")
|
||||
if log_id:
|
||||
try:
|
||||
log = queryset.get(id=log_id)
|
||||
# Check if HTMX request for detail view
|
||||
if request.headers.get('HX-Request'):
|
||||
return render(request, 'moderation/partials/history_detail_content.html', {
|
||||
'log': log,
|
||||
})
|
||||
if request.headers.get("HX-Request"):
|
||||
return render(
|
||||
request,
|
||||
"moderation/partials/history_detail_content.html",
|
||||
{
|
||||
"log": log,
|
||||
},
|
||||
)
|
||||
# Return JSON for API request
|
||||
return Response({
|
||||
'id': log.id,
|
||||
'timestamp': log.timestamp,
|
||||
'model': log.content_type.model,
|
||||
'object_id': log.object_id,
|
||||
'state': log.state,
|
||||
'from_state': log.source_state,
|
||||
'to_state': log.state,
|
||||
'transition': log.transition,
|
||||
'user': log.by.username if log.by else None,
|
||||
'description': log.description,
|
||||
'reason': log.description,
|
||||
})
|
||||
return Response(
|
||||
{
|
||||
"id": log.id,
|
||||
"timestamp": log.timestamp,
|
||||
"model": log.content_type.model,
|
||||
"object_id": log.object_id,
|
||||
"state": log.state,
|
||||
"from_state": log.source_state,
|
||||
"to_state": log.state,
|
||||
"transition": log.transition,
|
||||
"user": log.by.username if log.by else None,
|
||||
"description": log.description,
|
||||
"reason": log.description,
|
||||
}
|
||||
)
|
||||
except StateLog.DoesNotExist:
|
||||
if request.headers.get('HX-Request'):
|
||||
return render(request, 'moderation/partials/history_detail_content.html', {
|
||||
'log': None,
|
||||
})
|
||||
return Response({'error': 'Log not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||
if request.headers.get("HX-Request"):
|
||||
return render(
|
||||
request,
|
||||
"moderation/partials/history_detail_content.html",
|
||||
{
|
||||
"log": None,
|
||||
},
|
||||
)
|
||||
return Response(
|
||||
{"error": "Log not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
# Filter by model type with app_label support for correct ContentType resolution
|
||||
model_type = request.query_params.get('model_type')
|
||||
app_label = request.query_params.get('app_label')
|
||||
model_type = request.query_params.get("model_type")
|
||||
app_label = request.query_params.get("app_label")
|
||||
if model_type:
|
||||
try:
|
||||
if app_label:
|
||||
# Use both app_label and model for precise matching
|
||||
content_type = ContentType.objects.get_by_natural_key(app_label, model_type)
|
||||
content_type = ContentType.objects.get_by_natural_key(
|
||||
app_label, model_type
|
||||
)
|
||||
else:
|
||||
# Map common model names to their app_labels for correct resolution
|
||||
model_app_mapping = {
|
||||
'park': 'parks',
|
||||
'ride': 'rides',
|
||||
'editsubmission': 'submissions',
|
||||
'photosubmission': 'submissions',
|
||||
'moderationreport': 'moderation',
|
||||
'moderationqueue': 'moderation',
|
||||
'bulkoperation': 'moderation',
|
||||
"park": "parks",
|
||||
"ride": "rides",
|
||||
"editsubmission": "submissions",
|
||||
"photosubmission": "submissions",
|
||||
"moderationreport": "moderation",
|
||||
"moderationqueue": "moderation",
|
||||
"bulkoperation": "moderation",
|
||||
}
|
||||
mapped_app_label = model_app_mapping.get(model_type.lower())
|
||||
if mapped_app_label:
|
||||
content_type = ContentType.objects.get_by_natural_key(mapped_app_label, model_type.lower())
|
||||
content_type = ContentType.objects.get_by_natural_key(
|
||||
mapped_app_label, model_type.lower()
|
||||
)
|
||||
else:
|
||||
# Fallback to model-only lookup
|
||||
content_type = ContentType.objects.get(model=model_type)
|
||||
@@ -406,88 +426,98 @@ class ModerationReportViewSet(viewsets.ModelViewSet):
|
||||
pass
|
||||
|
||||
# Filter by object_id (for object-level history)
|
||||
object_id = request.query_params.get('object_id')
|
||||
object_id = request.query_params.get("object_id")
|
||||
if object_id:
|
||||
queryset = queryset.filter(object_id=object_id)
|
||||
|
||||
# Filter by user
|
||||
user_id = request.query_params.get('user_id')
|
||||
user_id = request.query_params.get("user_id")
|
||||
if user_id:
|
||||
queryset = queryset.filter(by_id=user_id)
|
||||
|
||||
# Filter by date range
|
||||
start_date = request.query_params.get('start_date')
|
||||
end_date = request.query_params.get('end_date')
|
||||
start_date = request.query_params.get("start_date")
|
||||
end_date = request.query_params.get("end_date")
|
||||
if start_date:
|
||||
queryset = queryset.filter(timestamp__gte=start_date)
|
||||
if end_date:
|
||||
queryset = queryset.filter(timestamp__lte=end_date)
|
||||
|
||||
# Filter by state
|
||||
state = request.query_params.get('state')
|
||||
state = request.query_params.get("state")
|
||||
if state:
|
||||
queryset = queryset.filter(state=state)
|
||||
|
||||
# Search filter (case-insensitive across relevant fields)
|
||||
search_query = request.query_params.get('q')
|
||||
search_query = request.query_params.get("q")
|
||||
if search_query:
|
||||
queryset = queryset.filter(
|
||||
Q(transition__icontains=search_query) |
|
||||
Q(description__icontains=search_query) |
|
||||
Q(state__icontains=search_query) |
|
||||
Q(source_state__icontains=search_query) |
|
||||
Q(object_id__icontains=search_query) |
|
||||
Q(by__username__icontains=search_query)
|
||||
Q(transition__icontains=search_query)
|
||||
| Q(description__icontains=search_query)
|
||||
| Q(state__icontains=search_query)
|
||||
| Q(source_state__icontains=search_query)
|
||||
| Q(object_id__icontains=search_query)
|
||||
| Q(by__username__icontains=search_query)
|
||||
)
|
||||
|
||||
# Order queryset
|
||||
queryset = queryset.order_by('-timestamp')
|
||||
queryset = queryset.order_by("-timestamp")
|
||||
|
||||
# Check if HTMX request
|
||||
if request.headers.get('HX-Request'):
|
||||
if request.headers.get("HX-Request"):
|
||||
# Use Django's Paginator for HTMX responses
|
||||
paginator = Paginator(queryset, 20)
|
||||
page_number = request.query_params.get('page', 1)
|
||||
page_number = request.query_params.get("page", 1)
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
return render(request, 'moderation/partials/history_table.html', {
|
||||
'history_logs': page_obj,
|
||||
'page_obj': page_obj,
|
||||
'request': request,
|
||||
})
|
||||
return render(
|
||||
request,
|
||||
"moderation/partials/history_table.html",
|
||||
{
|
||||
"history_logs": page_obj,
|
||||
"page_obj": page_obj,
|
||||
"request": request,
|
||||
},
|
||||
)
|
||||
|
||||
# Paginate for API response
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
history_data = [{
|
||||
'id': log.id,
|
||||
'timestamp': log.timestamp,
|
||||
'model': log.content_type.model,
|
||||
'object_id': log.object_id,
|
||||
'state': log.state,
|
||||
'from_state': log.source_state,
|
||||
'to_state': log.state,
|
||||
'transition': log.transition,
|
||||
'user': log.by.username if log.by else None,
|
||||
'description': log.description,
|
||||
'reason': log.description,
|
||||
} for log in page]
|
||||
history_data = [
|
||||
{
|
||||
"id": log.id,
|
||||
"timestamp": log.timestamp,
|
||||
"model": log.content_type.model,
|
||||
"object_id": log.object_id,
|
||||
"state": log.state,
|
||||
"from_state": log.source_state,
|
||||
"to_state": log.state,
|
||||
"transition": log.transition,
|
||||
"user": log.by.username if log.by else None,
|
||||
"description": log.description,
|
||||
"reason": log.description,
|
||||
}
|
||||
for log in page
|
||||
]
|
||||
return self.get_paginated_response(history_data)
|
||||
|
||||
# Return all history data when pagination is not triggered
|
||||
history_data = [{
|
||||
'id': log.id,
|
||||
'timestamp': log.timestamp,
|
||||
'model': log.content_type.model,
|
||||
'object_id': log.object_id,
|
||||
'state': log.state,
|
||||
'from_state': log.source_state,
|
||||
'to_state': log.state,
|
||||
'transition': log.transition,
|
||||
'user': log.by.username if log.by else None,
|
||||
'description': log.description,
|
||||
'reason': log.description,
|
||||
} for log in queryset]
|
||||
history_data = [
|
||||
{
|
||||
"id": log.id,
|
||||
"timestamp": log.timestamp,
|
||||
"model": log.content_type.model,
|
||||
"object_id": log.object_id,
|
||||
"state": log.state,
|
||||
"from_state": log.source_state,
|
||||
"to_state": log.state,
|
||||
"transition": log.transition,
|
||||
"user": log.by.username if log.by else None,
|
||||
"description": log.description,
|
||||
"reason": log.description,
|
||||
}
|
||||
for log in queryset
|
||||
]
|
||||
return Response(history_data)
|
||||
|
||||
|
||||
@@ -704,7 +734,7 @@ class ModerationQueueViewSet(viewsets.ModelViewSet):
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['get'], permission_classes=[CanViewModerationData])
|
||||
@action(detail=True, methods=["get"], permission_classes=[CanViewModerationData])
|
||||
def history(self, request, pk=None):
|
||||
"""Get transition history for this queue item."""
|
||||
from django_fsm_log.models import StateLog
|
||||
@@ -713,22 +743,26 @@ class ModerationQueueViewSet(viewsets.ModelViewSet):
|
||||
queue_item = self.get_object()
|
||||
content_type = ContentType.objects.get_for_model(queue_item)
|
||||
|
||||
logs = StateLog.objects.filter(
|
||||
content_type=content_type,
|
||||
object_id=queue_item.id
|
||||
).select_related('by').order_by('-timestamp')
|
||||
logs = (
|
||||
StateLog.objects.filter(content_type=content_type, object_id=queue_item.id)
|
||||
.select_related("by")
|
||||
.order_by("-timestamp")
|
||||
)
|
||||
|
||||
history_data = [{
|
||||
'id': log.id,
|
||||
'timestamp': log.timestamp,
|
||||
'state': log.state,
|
||||
'from_state': log.source_state,
|
||||
'to_state': log.state,
|
||||
'transition': log.transition,
|
||||
'user': log.by.username if log.by else None,
|
||||
'description': log.description,
|
||||
'reason': log.description,
|
||||
} for log in logs]
|
||||
history_data = [
|
||||
{
|
||||
"id": log.id,
|
||||
"timestamp": log.timestamp,
|
||||
"state": log.state,
|
||||
"from_state": log.source_state,
|
||||
"to_state": log.state,
|
||||
"transition": log.transition,
|
||||
"user": log.by.username if log.by else None,
|
||||
"description": log.description,
|
||||
"reason": log.description,
|
||||
}
|
||||
for log in logs
|
||||
]
|
||||
|
||||
return Response(history_data)
|
||||
|
||||
@@ -996,7 +1030,7 @@ class BulkOperationViewSet(viewsets.ModelViewSet):
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
@action(detail=True, methods=["get"])
|
||||
def history(self, request, pk=None):
|
||||
"""Get transition history for this bulk operation."""
|
||||
from django_fsm_log.models import StateLog
|
||||
@@ -1005,22 +1039,26 @@ class BulkOperationViewSet(viewsets.ModelViewSet):
|
||||
operation = self.get_object()
|
||||
content_type = ContentType.objects.get_for_model(operation)
|
||||
|
||||
logs = StateLog.objects.filter(
|
||||
content_type=content_type,
|
||||
object_id=operation.id
|
||||
).select_related('by').order_by('-timestamp')
|
||||
logs = (
|
||||
StateLog.objects.filter(content_type=content_type, object_id=operation.id)
|
||||
.select_related("by")
|
||||
.order_by("-timestamp")
|
||||
)
|
||||
|
||||
history_data = [{
|
||||
'id': log.id,
|
||||
'timestamp': log.timestamp,
|
||||
'state': log.state,
|
||||
'from_state': log.source_state,
|
||||
'to_state': log.state,
|
||||
'transition': log.transition,
|
||||
'user': log.by.username if log.by else None,
|
||||
'description': log.description,
|
||||
'reason': log.description,
|
||||
} for log in logs]
|
||||
history_data = [
|
||||
{
|
||||
"id": log.id,
|
||||
"timestamp": log.timestamp,
|
||||
"state": log.state,
|
||||
"from_state": log.source_state,
|
||||
"to_state": log.state,
|
||||
"transition": log.transition,
|
||||
"user": log.by.username if log.by else None,
|
||||
"description": log.description,
|
||||
"reason": log.description,
|
||||
}
|
||||
for log in logs
|
||||
]
|
||||
|
||||
return Response(history_data)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user