""" Services for moderation functionality. Following Django styleguide pattern for business logic encapsulation. """ from typing import Optional, Dict, Any, Union from django.db import transaction from django.utils import timezone from django.contrib.auth.models import User from django.db.models import QuerySet from .models import EditSubmission class ModerationService: """Service for handling content moderation workflows.""" @staticmethod def approve_submission( *, submission_id: int, moderator: User, notes: Optional[str] = None ) -> Union[object, None]: """ Approve a content submission and apply changes. Args: submission_id: ID of the submission to approve moderator: User performing the approval notes: Optional notes about the approval Returns: The created/updated object or None if approval failed Raises: EditSubmission.DoesNotExist: If submission doesn't exist ValidationError: If submission data is invalid ValueError: If submission cannot be processed """ with transaction.atomic(): submission = EditSubmission.objects.select_for_update().get( id=submission_id ) if submission.status != "PENDING": raise ValueError(f"Submission {submission_id} is not pending approval") try: # Call the model's approve method which handles the business # logic obj = submission.approve(moderator) # Add moderator notes if provided if notes: if submission.notes: submission.notes += f"\n[Moderator]: {notes}" else: submission.notes = f"[Moderator]: {notes}" submission.save() return obj except Exception as e: # Mark as rejected on any error submission.status = "REJECTED" submission.handled_by = moderator submission.handled_at = timezone.now() submission.notes = f"Approval failed: {str(e)}" submission.save() raise @staticmethod def reject_submission( *, submission_id: int, moderator: User, reason: str ) -> EditSubmission: """ Reject a content submission. Args: submission_id: ID of the submission to reject moderator: User performing the rejection reason: Reason for rejection Returns: Updated submission object Raises: EditSubmission.DoesNotExist: If submission doesn't exist ValueError: If submission cannot be rejected """ with transaction.atomic(): submission = EditSubmission.objects.select_for_update().get( id=submission_id ) if submission.status != "PENDING": raise ValueError(f"Submission {submission_id} is not pending review") submission.status = "REJECTED" submission.handled_by = moderator submission.handled_at = timezone.now() submission.notes = f"Rejected: {reason}" # Call full_clean before saving - CRITICAL STYLEGUIDE FIX submission.full_clean() submission.save() return submission @staticmethod def create_edit_submission( *, content_object: object, changes: Dict[str, Any], submitter: User, submission_type: str = "UPDATE", notes: Optional[str] = None, ) -> EditSubmission: """ Create a new edit submission for moderation. Args: content_object: The object being edited changes: Dictionary of field changes submitter: User submitting the changes submission_type: Type of submission ("CREATE" or "UPDATE") notes: Optional notes about the submission Returns: Created EditSubmission object Raises: ValidationError: If submission data is invalid """ submission = EditSubmission( content_object=content_object, changes=changes, submitted_by=submitter, submission_type=submission_type, notes=notes or "", ) # Call full_clean before saving - CRITICAL STYLEGUIDE FIX submission.full_clean() submission.save() return submission @staticmethod def update_submission_changes( *, submission_id: int, moderator_changes: Dict[str, Any], moderator: User, ) -> EditSubmission: """ Update submission with moderator changes before approval. Args: submission_id: ID of the submission to update moderator_changes: Dictionary of moderator modifications moderator: User making the changes Returns: Updated submission object Raises: EditSubmission.DoesNotExist: If submission doesn't exist ValueError: If submission cannot be modified """ with transaction.atomic(): submission = EditSubmission.objects.select_for_update().get( id=submission_id ) if submission.status != "PENDING": raise ValueError(f"Submission {submission_id} is not pending review") submission.moderator_changes = moderator_changes # Add note about moderator changes note = f"[Moderator changes by {moderator.username}]" if submission.notes: submission.notes += f"\n{note}" else: submission.notes = note # Call full_clean before saving - CRITICAL STYLEGUIDE FIX submission.full_clean() submission.save() return submission @staticmethod def get_pending_submissions_for_moderator( *, moderator: User, content_type: Optional[str] = None, limit: Optional[int] = None, ) -> QuerySet: """ Get pending submissions for a moderator to review. Args: moderator: The moderator user content_type: Optional filter by content type limit: Maximum number of submissions to return Returns: QuerySet of pending submissions """ from .selectors import pending_submissions_for_review return pending_submissions_for_review(content_type=content_type, limit=limit) @staticmethod def get_submission_statistics( *, days: int = 30, moderator: Optional[User] = None ) -> Dict[str, Any]: """ Get moderation statistics for a time period. Args: days: Number of days to analyze moderator: Optional filter by specific moderator Returns: Dictionary containing moderation statistics """ from .selectors import moderation_statistics_summary return moderation_statistics_summary(days=days, moderator=moderator)