mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 15:51:08 -05:00
- Implemented extensive test cases for the Parks API, covering endpoints for listing, retrieving, creating, updating, and deleting parks. - Added tests for filtering, searching, and ordering parks in the API. - Created tests for error handling in the API, including malformed JSON and unsupported methods. - Developed model tests for Park, ParkArea, Company, and ParkReview models, ensuring validation and constraints are enforced. - Introduced utility mixins for API and model testing to streamline assertions and enhance test readability. - Included integration tests to validate complete workflows involving park creation, retrieval, updating, and deletion.
245 lines
7.7 KiB
Python
245 lines
7.7 KiB
Python
"""
|
|
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.core.exceptions import ValidationError
|
|
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
|
|
)
|