from django.views.generic import ListView, TemplateView from django.shortcuts import get_object_or_404, render from django.http import HttpResponse, JsonResponse, HttpRequest from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.auth.decorators import login_required from django.template.loader import render_to_string from django.db.models import Q from django.core.exceptions import PermissionDenied from typing import Optional, Any, cast from accounts.models import User from .models import EditSubmission, PhotoSubmission from parks.models import Park, ParkArea from designers.models import Designer from companies.models import Manufacturer from 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 (getattr(user, 'role', None) 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.") @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') # If no query, show first 10 parks if not query: parks = Park.objects.all().order_by('name')[:10] else: parks = Park.objects.filter(name__icontains=query).order_by('name')[:10] context = { 'parks': parks, 'search_term': query, 'submission_id': submission_id } return render(request, 'moderation/partials/park_search_results.html', context) @login_required def search_manufacturers(request: HttpRequest) -> HttpResponse: """HTMX endpoint for searching manufacturers 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') # If no query, show first 10 manufacturers if not query: manufacturers = Manufacturer.objects.all().order_by('name')[:10] else: manufacturers = Manufacturer.objects.filter(name__icontains=query).order_by('name')[:10] context = { 'manufacturers': manufacturers, 'search_term': query, 'submission_id': submission_id } return render(request, 'moderation/partials/manufacturer_search_results.html', context) @login_required def search_designers(request: HttpRequest) -> HttpResponse: """HTMX endpoint for searching designers 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') # If no query, show first 10 designers if not query: designers = Designer.objects.all().order_by('name')[:10] else: designers = Designer.objects.filter(name__icontains=query).order_by('name')[:10] context = { 'designers': designers, 'search_term': query, 'submission_id': submission_id } return render(request, 'moderation/partials/designer_search_results.html', context) @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 no query, show first 10 models if not query: ride_models = queryset.order_by('name')[:10] else: ride_models = queryset.filter(name__icontains=query).order_by('name')[:10] context = { 'ride_models': ride_models, 'search_term': query, 'submission_id': submission_id } return render(request, 'moderation/partials/ride_model_search_results.html', context) class DashboardView(LoginRequiredMixin, ModeratorRequiredMixin, ListView): template_name = 'moderation/dashboard.html' context_object_name = 'submissions' paginate_by = 10 def get_template_names(self): if self.request.headers.get('HX-Request'): return ['moderation/partials/dashboard_content.html'] return [self.template_name] def get_queryset(self): status = self.request.GET.get('status', 'PENDING') submission_type = self.request.GET.get('submission_type', '') if submission_type == 'photo': queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at') else: queryset = EditSubmission.objects.filter(status=status).order_by('-created_at') if type_filter := self.request.GET.get('type'): queryset = queryset.filter(submission_type=type_filter) if content_type := self.request.GET.get('content_type'): queryset = queryset.filter(content_type__model=content_type) return queryset @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', '') if submission_type == 'photo': queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at') else: 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) context = { 'submissions': queryset, 'user': request.user, 'parks': [(p.id, str(p)) for p in Park.objects.all()], 'designers': [(d.id, str(d)) for d in Designer.objects.all()], 'manufacturers': [(m.id, str(m)) for m in Manufacturer.objects.all()], 'ride_models': [(m.id, str(m)) for m in RideModel.objects.all()], 'owners': [(u.id, str(u)) for u in User.objects.filter(role__in=['OWNER', 'ADMIN', 'SUPERUSER'])] } # If it's an HTMX request, return just the content if request.headers.get('HX-Request'): return render(request, 'moderation/partials/dashboard_content.html', context) # For direct URL access, return the full dashboard template return render(request, 'moderation/dashboard.html', 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': # Handle the edit submission notes = request.POST.get('notes') if not notes: return HttpResponse("Notes are required when editing a submission", status=400) try: # Update the moderator_changes with the edited values edited_changes = submission.changes.copy() for field in submission.changes.keys(): if field == 'stats': edited_stats = {} for key in submission.changes['stats'].keys(): if new_value := request.POST.get(f'stats.{key}'): edited_stats[key] = new_value edited_changes['stats'] = edited_stats else: if new_value := request.POST.get(field): # Handle special field types if field in ['latitude', 'longitude', '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 submission.moderator_changes = edited_changes submission.notes = notes submission.save() # Return the updated submission context = { 'submission': submission, 'user': request.user, 'parks': [(p.id, str(p)) for p in Park.objects.all()], 'designers': [(d.id, str(d)) for d in Designer.objects.all()], 'manufacturers': [(m.id, str(m)) for m in Manufacturer.objects.all()], 'ride_models': [(m.id, str(m)) for m in RideModel.objects.all()], 'owners': [(u.id, str(u)) for u in User.objects.filter(role__in=['OWNER', 'ADMIN', 'SUPERUSER'])], 'park_areas': [(a.id, str(a)) for a in ParkArea.objects.filter(park_id=edited_changes.get('park'))] if edited_changes.get('park') else [] } return render(request, 'moderation/partials/submission_list.html', context) except Exception as e: return HttpResponse(str(e), status=400) return HttpResponse("Invalid request method", status=405) @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 submission.status == 'ESCALATED' and not (user.role in ['ADMIN', 'SUPERUSER'] or user.is_superuser): return HttpResponse("Only admins can handle escalated submissions", status=403) if not (user.role in MODERATOR_ROLES or user.is_superuser): return HttpResponse(status=403) try: submission.approve(user) _update_submission_notes(submission, request.POST.get('notes')) # Get updated queryset with filters status = request.GET.get('status', 'PENDING') submission_type = request.GET.get('submission_type', '') if submission_type == 'photo': queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at') else: 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) context = { 'submissions': queryset, 'user': request.user, } return render(request, 'moderation/partials/dashboard_content.html', context) 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 submission.status == 'ESCALATED' and not (user.role in ['ADMIN', 'SUPERUSER'] or user.is_superuser): return HttpResponse("Only admins can handle escalated submissions", status=403) if not (user.role in MODERATOR_ROLES or user.is_superuser): return HttpResponse(status=403) submission.reject(user) _update_submission_notes(submission, request.POST.get('notes')) # Get updated queryset with filters status = request.GET.get('status', 'PENDING') submission_type = request.GET.get('submission_type', '') if submission_type == 'photo': queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at') else: 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) context = { 'submissions': queryset, 'user': request.user, } return render(request, 'moderation/partials/dashboard_content.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')) # Get updated queryset with filters status = request.GET.get('status', 'PENDING') submission_type = request.GET.get('submission_type', '') if submission_type == 'photo': queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at') else: 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) context = { 'submissions': queryset, 'user': request.user, } return render(request, 'moderation/partials/dashboard_content.html', context) @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()