from typing import Any, Dict, Optional, Type, cast from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.contenttypes.models import ContentType from django.http import ( JsonResponse, HttpResponseForbidden, HttpRequest, HttpResponse, ) from django.views.generic import DetailView from django.db import models from django.contrib.auth import get_user_model import json from .models import EditSubmission, PhotoSubmission, UserType User = get_user_model() class EditSubmissionMixin(DetailView): """ Mixin for handling edit submissions with proper moderation. """ model: Optional[Type[models.Model]] = None def handle_edit_submission( self, request: HttpRequest, changes: Dict[str, Any], reason: str = "", source: str = "", submission_type: str = "EDIT", ) -> JsonResponse: """ Handle an edit submission based on user's role. Args: request: The HTTP request changes: Dict of field changes {field_name: new_value} reason: Why this edit is needed source: Source of information (optional) submission_type: 'EDIT' or 'CREATE' Returns: JsonResponse with status and message """ if not request.user.is_authenticated: return JsonResponse( { "status": "error", "message": "You must be logged in to make edits.", }, status=403, ) if not self.model: raise ValueError("model attribute must be set") content_type = ContentType.objects.get_for_model(self.model) # Create the submission submission = EditSubmission( user=request.user, content_type=content_type, submission_type=submission_type, changes=changes, reason=reason, source=source, ) # For edits, set the object_id if submission_type == "EDIT": obj = self.get_object() submission.object_id = getattr(obj, "id", None) # Auto-approve for moderators and above user_role = getattr(request.user, "role", None) if user_role in ["MODERATOR", "ADMIN", "SUPERUSER"]: obj = submission.approve(cast(UserType, request.user)) return JsonResponse( { "status": "success", "message": "Changes saved successfully.", "auto_approved": True, "redirect_url": getattr(obj, "get_absolute_url", lambda: None)(), } ) # Submit for approval for regular users submission.save() return JsonResponse( { "status": "success", "message": "Your changes have been submitted for approval.", "auto_approved": False, } ) def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> JsonResponse: """Handle POST requests for editing""" if not request.user.is_authenticated: return JsonResponse( { "status": "error", "message": "You must be logged in to make edits.", }, status=403, ) try: data = json.loads(request.body) changes = data.get("changes", {}) reason = data.get("reason", "") source = data.get("source", "") submission_type = data.get("submission_type", "EDIT") if not changes: return JsonResponse( {"status": "error", "message": "No changes provided."}, status=400, ) user_role = getattr(request.user, "role", None) if not reason and user_role == "USER": return JsonResponse( { "status": "error", "message": "Please provide a reason for your changes.", }, status=400, ) return self.handle_edit_submission( request, changes, reason, source, submission_type ) except json.JSONDecodeError: return JsonResponse( {"status": "error", "message": "Invalid JSON data."}, status=400, ) except Exception as e: return JsonResponse({"status": "error", "message": str(e)}, status=500) class PhotoSubmissionMixin(DetailView): """ Mixin for handling photo submissions with proper moderation. """ model: Optional[Type[models.Model]] = None def handle_photo_submission(self, request: HttpRequest) -> JsonResponse: """Handle a photo submission based on user's role""" if not request.user.is_authenticated: return JsonResponse( { "status": "error", "message": "You must be logged in to upload photos.", }, status=403, ) if not self.model: raise ValueError("model attribute must be set") try: obj = self.get_object() except (AttributeError, self.model.DoesNotExist): return JsonResponse( {"status": "error", "message": "Invalid object."}, status=400 ) if not request.FILES.get("photo"): return JsonResponse( {"status": "error", "message": "No photo provided."}, status=400, ) content_type = ContentType.objects.get_for_model(obj) submission = PhotoSubmission( user=request.user, content_type=content_type, object_id=getattr(obj, "id", None), photo=request.FILES["photo"], caption=request.POST.get("caption", ""), date_taken=request.POST.get("date_taken"), ) # Auto-approve for moderators and above user_role = getattr(request.user, "role", None) if user_role in ["MODERATOR", "ADMIN", "SUPERUSER"]: submission.auto_approve() return JsonResponse( { "status": "success", "message": "Photo uploaded successfully.", "auto_approved": True, } ) # Submit for approval for regular users submission.save() return JsonResponse( { "status": "success", "message": "Your photo has been submitted for approval.", "auto_approved": False, } ) class ModeratorRequiredMixin(UserPassesTestMixin): """Require moderator or higher role for access""" request: Optional[HttpRequest] = None def test_func(self) -> bool: if not self.request: return False user_role = getattr(self.request.user, "role", None) return self.request.user.is_authenticated and user_role in [ "MODERATOR", "ADMIN", "SUPERUSER", ] def handle_no_permission(self) -> HttpResponse: if not self.request or not self.request.user.is_authenticated: return super().handle_no_permission() return HttpResponseForbidden("You must be a moderator to access this page.") class AdminRequiredMixin(UserPassesTestMixin): """Require admin or superuser role for access""" request: Optional[HttpRequest] = None def test_func(self) -> bool: if not self.request: return False user_role = getattr(self.request.user, "role", None) return self.request.user.is_authenticated and user_role in [ "ADMIN", "SUPERUSER", ] def handle_no_permission(self) -> HttpResponse: if not self.request or not self.request.user.is_authenticated: return super().handle_no_permission() return HttpResponseForbidden("You must be an admin to access this page.") class InlineEditMixin: """Add inline editing context to views""" request: Optional[HttpRequest] = None def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: context = super().get_context_data(**kwargs) # type: ignore if self.request and self.request.user.is_authenticated: context["can_edit"] = True user_role = getattr(self.request.user, "role", None) context["can_auto_approve"] = user_role in [ "MODERATOR", "ADMIN", "SUPERUSER", ] if isinstance(self, DetailView): obj = self.get_object() # type: ignore context["pending_edits"] = ( EditSubmission.objects.filter( content_type=ContentType.objects.get_for_model(obj.__class__), object_id=getattr(obj, "id", None), status="NEW", ) .select_related("user") .order_by("-created_at") ) return context class HistoryMixin: """Add edit history context to views""" def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: context = super().get_context_data(**kwargs) # type: ignore # Only add history context for DetailViews if isinstance(self, DetailView): obj = self.get_object() # type: ignore # Get historical records ordered by date if available try: # Use pghistory's get_history method context["history"] = obj.get_history() except (AttributeError, TypeError): context["history"] = [] # Get related edit submissions content_type = ContentType.objects.get_for_model(obj.__class__) context["edit_submissions"] = ( EditSubmission.objects.filter( content_type=content_type, object_id=getattr(obj, "id", None), ) .exclude(status="NEW") .select_related("user", "handled_by") .order_by("-created_at") ) return context