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 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.") 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', 'NEW') 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', 'NEW') 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, } # 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 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', 'NEW') 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', 'NEW') 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', 'NEW') 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()