Files
thrillwiki_django_no_react/backend/apps/moderation/views.py
pacnpal 08a4a2d034 feat: Add PrimeProgress, PrimeSelect, and PrimeSkeleton components with customizable styles and props
- Implemented PrimeProgress component with support for labels, helper text, and various styles (size, variant, color).
- Created PrimeSelect component with dropdown functionality, custom templates, and validation states.
- Developed PrimeSkeleton component for loading placeholders with different shapes and animations.
- Updated index.ts to export new components for easy import.
- Enhanced PrimeVueTest.vue to include tests for new components and their functionalities.
- Introduced a custom ThrillWiki theme for PrimeVue with tailored color schemes and component styles.
- Added ambient type declarations for various components to improve TypeScript support.
2025-08-27 21:00:02 -04:00

430 lines
15 KiB
Python

from django.views.generic import ListView
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponse, HttpRequest
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.auth.decorators import login_required
from django.db.models import QuerySet
from django.core.exceptions import PermissionDenied
from typing import Optional, Any, Dict, List, cast
from django.core.serializers.json import DjangoJSONEncoder
import json
from apps.accounts.models import User
from .models import EditSubmission, PhotoSubmission
from apps.parks.models import Park, ParkArea
from apps.rides.models import RideModel
MODERATOR_ROLES = ["MODERATOR", "ADMIN", "SUPERUSER"]
class ModeratorRequiredMixin(UserPassesTestMixin):
request: HttpRequest
def test_func(self) -> bool:
"""Check if user has moderator permissions."""
user = cast(User, self.request.user)
return user.is_authenticated and (
user.role in MODERATOR_ROLES or user.is_superuser
)
def handle_no_permission(self) -> HttpResponse:
if not self.request.user.is_authenticated:
return super().handle_no_permission()
raise PermissionDenied("You do not have moderator permissions.")
def get_filtered_queryset(
request: HttpRequest, status: str, submission_type: str
) -> QuerySet:
"""Get filtered queryset based on request parameters."""
if submission_type == "photo":
return PhotoSubmission.objects.filter(status=status).order_by("-created_at")
queryset = EditSubmission.objects.filter(status=status).order_by("-created_at")
if type_filter := request.GET.get("type"):
queryset = queryset.filter(submission_type=type_filter)
if content_type := request.GET.get("content_type"):
queryset = queryset.filter(content_type__model=content_type)
return queryset
def get_context_data(request: HttpRequest, queryset: QuerySet) -> Dict[str, Any]:
"""Get common context data for views."""
park_areas_by_park: Dict[int, List[tuple[int, str]]] = {}
if isinstance(queryset.first(), EditSubmission):
for submission in queryset:
if (
submission.content_type.model == "park"
and isinstance(submission.changes, dict)
and "park" in submission.changes
):
park_id = submission.changes["park"]
if park_id not in park_areas_by_park:
areas = ParkArea.objects.filter(park_id=park_id)
park_areas_by_park[park_id] = [
(area.pk, str(area)) for area in areas
]
return {
"submissions": queryset,
"user": request.user,
"parks": [(park.pk, str(park)) for park in Park.objects.all()],
"ride_models": [(model.pk, str(model)) for model in RideModel.objects.all()],
"owners": [
(user.pk, str(user))
for user in User.objects.filter(role__in=["OWNER", "ADMIN", "SUPERUSER"])
],
"park_areas_by_park": park_areas_by_park,
}
@login_required
def search_parks(request: HttpRequest) -> HttpResponse:
"""HTMX endpoint for searching parks in moderation dashboard"""
user = cast(User, request.user)
if not (user.role in MODERATOR_ROLES or user.is_superuser):
return HttpResponse(status=403)
query = request.GET.get("q", "").strip()
submission_id = request.GET.get("submission_id")
parks = Park.objects.all().order_by("name")
if query:
parks = parks.filter(name__icontains=query)
parks = parks[:10]
return render(
request,
"moderation/partials/park_search_results.html",
{"parks": parks, "search_term": query, "submission_id": submission_id},
)
@login_required
def search_ride_models(request: HttpRequest) -> HttpResponse:
"""HTMX endpoint for searching ride models in moderation dashboard"""
user = cast(User, request.user)
if not (user.role in MODERATOR_ROLES or user.is_superuser):
return HttpResponse(status=403)
query = request.GET.get("q", "").strip()
submission_id = request.GET.get("submission_id")
manufacturer_id = request.GET.get("manufacturer")
queryset = RideModel.objects.all()
if manufacturer_id:
queryset = queryset.filter(manufacturer_id=manufacturer_id)
if query:
queryset = queryset.filter(name__icontains=query)
queryset = queryset.order_by("name")[:10]
return render(
request,
"moderation/partials/ride_model_search_results.html",
{
"ride_models": queryset,
"search_term": query,
"submission_id": submission_id,
},
)
class DashboardView(LoginRequiredMixin, ModeratorRequiredMixin, ListView):
template_name = "moderation/dashboard.html"
context_object_name = "submissions"
paginate_by = 10
def get_template_names(self) -> List[str]:
if self.request.headers.get("HX-Request"):
return ["moderation/partials/dashboard_content.html"]
return [self.template_name]
def get_queryset(self) -> QuerySet:
status = self.request.GET.get("status", "PENDING")
submission_type = self.request.GET.get("submission_type", "")
return get_filtered_queryset(self.request, status, submission_type)
@login_required
def submission_list(request: HttpRequest) -> HttpResponse:
"""View for submission list with filters"""
user = cast(User, request.user)
if not (user.role in MODERATOR_ROLES or user.is_superuser):
return HttpResponse(status=403)
status = request.GET.get("status", "PENDING")
submission_type = request.GET.get("submission_type", "")
queryset = get_filtered_queryset(request, status, submission_type)
# Process location data for park submissions
for submission in queryset:
if submission.content_type.model == "park" and isinstance(
submission.changes, dict
):
# Extract location fields into a location object
location_fields = [
"latitude",
"longitude",
"street_address",
"city",
"state",
"postal_code",
"country",
]
location_data = {
field: submission.changes.get(field) for field in location_fields
}
# Add location data back as a single object
submission.changes["location"] = location_data
context = get_context_data(request, queryset)
template_name = (
"moderation/partials/dashboard_content.html"
if request.headers.get("HX-Request")
else "moderation/dashboard.html"
)
return render(request, template_name, context)
@login_required
def edit_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
"""HTMX endpoint for editing a submission"""
user = cast(User, request.user)
if not (user.role in MODERATOR_ROLES or user.is_superuser):
return HttpResponse(status=403)
submission = get_object_or_404(EditSubmission, id=submission_id)
if request.method != "POST":
return HttpResponse("Invalid request method", status=405)
notes = request.POST.get("notes")
if not notes:
return HttpResponse("Notes are required when editing a submission", status=400)
try:
edited_changes = dict(submission.changes) if submission.changes else {}
# Update stats if present
if "stats" in edited_changes:
edited_stats = {}
for key in edited_changes["stats"]:
if new_value := request.POST.get(f"stats.{key}"):
edited_stats[key] = new_value
edited_changes["stats"] = edited_stats
# Update location fields if present
if submission.content_type.model == "park":
location_fields = [
"latitude",
"longitude",
"street_address",
"city",
"state",
"postal_code",
"country",
]
location_data = {}
for field in location_fields:
if new_value := request.POST.get(field):
if field in ["latitude", "longitude"]:
try:
location_data[field] = float(new_value)
except ValueError:
return HttpResponse(
f"Invalid value for {field}", status=400
)
else:
location_data[field] = new_value
if location_data:
edited_changes.update(location_data)
# Update other fields
for field in edited_changes:
if field == "stats" or field in [
"latitude",
"longitude",
"street_address",
"city",
"state",
"postal_code",
"country",
]:
continue
if new_value := request.POST.get(field):
if field in ["size_acres"]:
try:
edited_changes[field] = float(new_value)
except ValueError:
return HttpResponse(f"Invalid value for {field}", status=400)
else:
edited_changes[field] = new_value
# Convert to JSON-serializable format
json_changes = json.loads(json.dumps(edited_changes, cls=DjangoJSONEncoder))
submission.moderator_changes = json_changes
submission.notes = notes
submission.save()
# Process location data for display
if submission.content_type.model == "park":
location_fields = [
"latitude",
"longitude",
"street_address",
"city",
"state",
"postal_code",
"country",
]
location_data = {
field: json_changes.get(field) for field in location_fields
}
# Add location data back as a single object
json_changes["location"] = location_data
submission.changes = json_changes
context = get_context_data(
request, EditSubmission.objects.filter(id=submission_id)
)
return render(request, "moderation/partials/submission_list.html", context)
except Exception as e:
return HttpResponse(str(e), status=400)
@login_required
def approve_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
"""HTMX endpoint for approving a submission"""
user = cast(User, request.user)
submission = get_object_or_404(EditSubmission, id=submission_id)
if not (
(submission.status != "ESCALATED" and user.role in MODERATOR_ROLES)
or user.role in ["ADMIN", "SUPERUSER"]
or user.is_superuser
):
return HttpResponse("Insufficient permissions", status=403)
try:
submission.approve(user)
_update_submission_notes(submission, request.POST.get("notes"))
status = request.GET.get("status", "PENDING")
submission_type = request.GET.get("submission_type", "")
queryset = get_filtered_queryset(request, status, submission_type)
return render(
request,
"moderation/partials/dashboard_content.html",
{
"submissions": queryset,
"user": request.user,
},
)
except ValueError as e:
return HttpResponse(str(e), status=400)
@login_required
def reject_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
"""HTMX endpoint for rejecting a submission"""
user = cast(User, request.user)
submission = get_object_or_404(EditSubmission, id=submission_id)
if not (
(submission.status != "ESCALATED" and user.role in MODERATOR_ROLES)
or user.role in ["ADMIN", "SUPERUSER"]
or user.is_superuser
):
return HttpResponse("Insufficient permissions", status=403)
submission.reject(user)
_update_submission_notes(submission, request.POST.get("notes"))
status = request.GET.get("status", "PENDING")
submission_type = request.GET.get("submission_type", "")
queryset = get_filtered_queryset(request, status, submission_type)
context = get_context_data(request, queryset)
return render(request, "moderation/partials/submission_list.html", context)
@login_required
def escalate_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
"""HTMX endpoint for escalating a submission"""
user = cast(User, request.user)
if not (user.role in MODERATOR_ROLES or user.is_superuser):
return HttpResponse(status=403)
submission = get_object_or_404(EditSubmission, id=submission_id)
if submission.status == "ESCALATED":
return HttpResponse("Submission is already escalated", status=400)
submission.escalate(user)
_update_submission_notes(submission, request.POST.get("notes"))
status = request.GET.get("status", "PENDING")
submission_type = request.GET.get("submission_type", "")
queryset = get_filtered_queryset(request, status, submission_type)
return render(
request,
"moderation/partials/dashboard_content.html",
{
"submissions": queryset,
"user": request.user,
},
)
@login_required
def approve_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
"""HTMX endpoint for approving a photo submission"""
user = cast(User, request.user)
if not (user.role in MODERATOR_ROLES or user.is_superuser):
return HttpResponse(status=403)
submission = get_object_or_404(PhotoSubmission, id=submission_id)
try:
submission.approve(user, request.POST.get("notes", ""))
return render(
request,
"moderation/partials/photo_submission.html",
{"submission": submission},
)
except Exception as e:
return HttpResponse(str(e), status=400)
@login_required
def reject_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
"""HTMX endpoint for rejecting a photo submission"""
user = cast(User, request.user)
if not (user.role in MODERATOR_ROLES or user.is_superuser):
return HttpResponse(status=403)
submission = get_object_or_404(PhotoSubmission, id=submission_id)
submission.reject(user, request.POST.get("notes", ""))
return render(
request,
"moderation/partials/photo_submission.html",
{"submission": submission},
)
def _update_submission_notes(submission: EditSubmission, notes: Optional[str]) -> None:
"""Update submission notes if provided."""
if notes:
submission.notes = notes
submission.save()