mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-27 19:27:01 -05:00
feat: Add blog, media, and support apps, implement ride credits and image API, and remove toplist feature.
This commit is contained in:
@@ -4,12 +4,17 @@ Signal handlers for moderation-related FSM state transitions.
|
||||
This module provides signal handlers that execute when moderation
|
||||
models (EditSubmission, PhotoSubmission, ModerationReport, etc.)
|
||||
undergo state transitions.
|
||||
|
||||
Includes:
|
||||
- Transition handlers for approval, rejection, escalation
|
||||
- Real-time broadcasting signal for dashboard updates
|
||||
- Claim/unclaim tracking for concurrency control
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.dispatch import receiver
|
||||
from django.dispatch import receiver, Signal
|
||||
|
||||
from apps.core.state_machine.signals import (
|
||||
post_state_transition,
|
||||
@@ -20,6 +25,71 @@ from apps.core.state_machine.signals import (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Custom Signals for Real-Time Broadcasting
|
||||
# ============================================================================
|
||||
|
||||
# Signal emitted when a submission status changes - for real-time UI updates
|
||||
# Arguments:
|
||||
# - sender: The model class (EditSubmission or PhotoSubmission)
|
||||
# - submission_id: The ID of the submission
|
||||
# - submission_type: "edit" or "photo"
|
||||
# - new_status: The new status value
|
||||
# - previous_status: The previous status value
|
||||
# - locked_by: Username of the moderator who claimed it (or None)
|
||||
# - payload: Full payload dictionary for broadcasting
|
||||
submission_status_changed = Signal()
|
||||
|
||||
|
||||
def handle_submission_claimed(instance, source, target, user, context=None, **kwargs):
|
||||
"""
|
||||
Handle submission claim transitions.
|
||||
|
||||
Called when an EditSubmission or PhotoSubmission is claimed by a moderator.
|
||||
Broadcasts the status change for real-time dashboard updates.
|
||||
|
||||
Args:
|
||||
instance: The submission instance.
|
||||
source: The source state.
|
||||
target: The target state.
|
||||
user: The user who claimed.
|
||||
context: Optional TransitionContext.
|
||||
"""
|
||||
if target != 'CLAIMED':
|
||||
return
|
||||
|
||||
logger.info(
|
||||
f"Submission {instance.pk} claimed by {user.username if user else 'system'}"
|
||||
)
|
||||
|
||||
# Broadcast for real-time dashboard updates
|
||||
_broadcast_submission_status_change(instance, source, target, user)
|
||||
|
||||
|
||||
def handle_submission_unclaimed(instance, source, target, user, context=None, **kwargs):
|
||||
"""
|
||||
Handle submission unclaim transitions (CLAIMED -> PENDING).
|
||||
|
||||
Called when a moderator releases their claim on a submission.
|
||||
|
||||
Args:
|
||||
instance: The submission instance.
|
||||
source: The source state.
|
||||
target: The target state.
|
||||
user: The user who unclaimed.
|
||||
context: Optional TransitionContext.
|
||||
"""
|
||||
if source != 'CLAIMED' or target != 'PENDING':
|
||||
return
|
||||
|
||||
logger.info(
|
||||
f"Submission {instance.pk} unclaimed by {user.username if user else 'system'}"
|
||||
)
|
||||
|
||||
# Broadcast for real-time dashboard updates
|
||||
_broadcast_submission_status_change(instance, source, target, user)
|
||||
|
||||
|
||||
def handle_submission_approved(instance, source, target, user, context=None, **kwargs):
|
||||
"""
|
||||
Handle submission approval transitions.
|
||||
@@ -255,6 +325,66 @@ def _finalize_bulk_operation(instance, success):
|
||||
logger.warning(f"Failed to finalize bulk operation: {e}")
|
||||
|
||||
|
||||
def _broadcast_submission_status_change(instance, source, target, user):
|
||||
"""
|
||||
Broadcast submission status change for real-time UI updates.
|
||||
|
||||
Emits the submission_status_changed signal with a structured payload
|
||||
that can be consumed by notification systems (Novu, SSE, WebSocket, etc.).
|
||||
|
||||
Payload format:
|
||||
{
|
||||
"submission_id": 123,
|
||||
"submission_type": "edit" | "photo",
|
||||
"new_status": "CLAIMED",
|
||||
"previous_status": "PENDING",
|
||||
"locked_by": "moderator_username" | None,
|
||||
"locked_at": "2024-01-01T12:00:00Z" | None,
|
||||
"changed_by": "username" | None,
|
||||
}
|
||||
"""
|
||||
try:
|
||||
from .models import EditSubmission, PhotoSubmission
|
||||
|
||||
# Determine submission type
|
||||
submission_type = "edit" if isinstance(instance, EditSubmission) else "photo"
|
||||
|
||||
# Build the broadcast payload
|
||||
payload = {
|
||||
"submission_id": instance.pk,
|
||||
"submission_type": submission_type,
|
||||
"new_status": target,
|
||||
"previous_status": source,
|
||||
"locked_by": None,
|
||||
"locked_at": None,
|
||||
"changed_by": user.username if user else None,
|
||||
}
|
||||
|
||||
# Add claim information if available
|
||||
if hasattr(instance, 'claimed_by') and instance.claimed_by:
|
||||
payload["locked_by"] = instance.claimed_by.username
|
||||
if hasattr(instance, 'claimed_at') and instance.claimed_at:
|
||||
payload["locked_at"] = instance.claimed_at.isoformat()
|
||||
|
||||
# Emit the signal for downstream notification handlers
|
||||
submission_status_changed.send(
|
||||
sender=type(instance),
|
||||
submission_id=instance.pk,
|
||||
submission_type=submission_type,
|
||||
new_status=target,
|
||||
previous_status=source,
|
||||
locked_by=payload["locked_by"],
|
||||
payload=payload,
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f"Broadcast status change: {submission_type}#{instance.pk} "
|
||||
f"{source} -> {target}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to broadcast submission status change: {e}")
|
||||
|
||||
|
||||
# Signal handler registration
|
||||
|
||||
def register_moderation_signal_handlers():
|
||||
@@ -320,7 +450,41 @@ def register_moderation_signal_handlers():
|
||||
handle_bulk_operation_status, stage='post'
|
||||
)
|
||||
|
||||
# Claim/Unclaim handlers for EditSubmission
|
||||
register_transition_handler(
|
||||
EditSubmission, 'PENDING', 'CLAIMED',
|
||||
handle_submission_claimed, stage='post'
|
||||
)
|
||||
register_transition_handler(
|
||||
EditSubmission, 'CLAIMED', 'PENDING',
|
||||
handle_submission_unclaimed, stage='post'
|
||||
)
|
||||
|
||||
# Claim/Unclaim handlers for PhotoSubmission
|
||||
register_transition_handler(
|
||||
PhotoSubmission, 'PENDING', 'CLAIMED',
|
||||
handle_submission_claimed, stage='post'
|
||||
)
|
||||
register_transition_handler(
|
||||
PhotoSubmission, 'CLAIMED', 'PENDING',
|
||||
handle_submission_unclaimed, stage='post'
|
||||
)
|
||||
|
||||
logger.info("Registered moderation signal handlers")
|
||||
|
||||
except ImportError as e:
|
||||
logger.warning(f"Could not register moderation signal handlers: {e}")
|
||||
|
||||
|
||||
__all__ = [
|
||||
'submission_status_changed',
|
||||
'register_moderation_signal_handlers',
|
||||
'handle_submission_approved',
|
||||
'handle_submission_rejected',
|
||||
'handle_submission_escalated',
|
||||
'handle_submission_claimed',
|
||||
'handle_submission_unclaimed',
|
||||
'handle_report_resolved',
|
||||
'handle_queue_completed',
|
||||
'handle_bulk_operation_status',
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user