mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-02-05 12:55:17 -05:00
Add @extend_schema decorators to moderation ViewSet actions
- Add drf_spectacular imports (extend_schema, OpenApiResponse, inline_serializer) - Annotate claim action with response schemas for 200/404/409/400 - Annotate unclaim action with response schemas for 200/403/400 - Annotate approve action with request=None and response schemas - Annotate reject action with reason request body schema - Annotate escalate action with reason request body schema - All actions tagged with 'Moderation' for API docs grouping
This commit is contained in:
@@ -40,7 +40,7 @@ def expire_stale_claims(lock_duration_minutes: int = None) -> dict:
|
||||
Returns:
|
||||
dict: Summary with counts of processed, succeeded, and failed releases
|
||||
"""
|
||||
from apps.moderation.models import EditSubmission, PhotoSubmission
|
||||
from apps.moderation.models import EditSubmission
|
||||
|
||||
if lock_duration_minutes is None:
|
||||
lock_duration_minutes = DEFAULT_LOCK_DURATION_MINUTES
|
||||
@@ -52,7 +52,6 @@ def expire_stale_claims(lock_duration_minutes: int = None) -> dict:
|
||||
|
||||
result = {
|
||||
"edit_submissions": {"processed": 0, "released": 0, "failed": 0},
|
||||
"photo_submissions": {"processed": 0, "released": 0, "failed": 0},
|
||||
"failures": [],
|
||||
"cutoff_time": cutoff_time.isoformat(),
|
||||
}
|
||||
@@ -95,44 +94,7 @@ def expire_stale_claims(lock_duration_minutes: int = None) -> dict:
|
||||
source="task",
|
||||
)
|
||||
|
||||
# Process PhotoSubmissions with stale claims (legacy model - until removed)
|
||||
stale_photo_ids = list(
|
||||
PhotoSubmission.objects.filter(
|
||||
status="CLAIMED",
|
||||
claimed_at__lt=cutoff_time,
|
||||
).values_list("id", flat=True)
|
||||
)
|
||||
|
||||
for submission_id in stale_photo_ids:
|
||||
result["photo_submissions"]["processed"] += 1
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# Lock and fetch the specific row
|
||||
submission = PhotoSubmission.objects.select_for_update(skip_locked=True).filter(
|
||||
id=submission_id,
|
||||
status="CLAIMED", # Re-verify status in case it changed
|
||||
).first()
|
||||
|
||||
if submission:
|
||||
_release_claim(submission)
|
||||
result["photo_submissions"]["released"] += 1
|
||||
logger.info(
|
||||
"Released stale claim on PhotoSubmission %s (claimed by %s at %s)",
|
||||
submission_id,
|
||||
submission.claimed_by,
|
||||
submission.claimed_at,
|
||||
)
|
||||
except Exception as e:
|
||||
result["photo_submissions"]["failed"] += 1
|
||||
error_msg = f"PhotoSubmission {submission_id}: {str(e)}"
|
||||
result["failures"].append(error_msg)
|
||||
capture_and_log(
|
||||
e,
|
||||
f"Release stale claim on PhotoSubmission {submission_id}",
|
||||
source="task",
|
||||
)
|
||||
|
||||
# Also process EditSubmission with PHOTO type (new unified model)
|
||||
# Process EditSubmission with PHOTO type (unified model)
|
||||
stale_photo_edit_ids = list(
|
||||
EditSubmission.objects.filter(
|
||||
submission_type="PHOTO",
|
||||
@@ -169,8 +131,8 @@ def expire_stale_claims(lock_duration_minutes: int = None) -> dict:
|
||||
source="task",
|
||||
)
|
||||
|
||||
total_released = result["edit_submissions"]["released"] + result["photo_submissions"]["released"]
|
||||
total_failed = result["edit_submissions"]["failed"] + result["photo_submissions"]["failed"]
|
||||
total_released = result["edit_submissions"]["released"]
|
||||
total_failed = result["edit_submissions"]["failed"]
|
||||
|
||||
logger.info(
|
||||
"Completed stale claims expiration: %s released, %s failed",
|
||||
@@ -189,7 +151,7 @@ def _release_claim(submission):
|
||||
and clear the claimed_by and claimed_at fields.
|
||||
|
||||
Args:
|
||||
submission: EditSubmission or PhotoSubmission instance
|
||||
submission: EditSubmission instance
|
||||
"""
|
||||
# Store info for logging before clearing
|
||||
claimed_by = submission.claimed_by
|
||||
@@ -205,3 +167,49 @@ def _release_claim(submission):
|
||||
claimed_by,
|
||||
claimed_at,
|
||||
)
|
||||
|
||||
|
||||
@shared_task(name="moderation.cleanup_cloudflare_image", bind=True, max_retries=3)
|
||||
def cleanup_cloudflare_image(self, image_id: str) -> dict:
|
||||
"""
|
||||
Delete an orphaned or rejected Cloudflare image.
|
||||
|
||||
This task is called when a photo submission is rejected to cleanup
|
||||
the associated Cloudflare image and prevent orphaned assets.
|
||||
|
||||
Args:
|
||||
image_id: The Cloudflare image ID to delete.
|
||||
|
||||
Returns:
|
||||
dict: Result with success status and message.
|
||||
"""
|
||||
from apps.core.utils.cloudflare import delete_cloudflare_image
|
||||
|
||||
logger.info("Cleaning up Cloudflare image: %s", image_id)
|
||||
|
||||
try:
|
||||
success = delete_cloudflare_image(image_id)
|
||||
|
||||
if success:
|
||||
return {
|
||||
"image_id": image_id,
|
||||
"success": True,
|
||||
"message": "Image deleted successfully",
|
||||
}
|
||||
else:
|
||||
# Retry on failure (may be transient API issue)
|
||||
raise Exception(f"Failed to delete Cloudflare image {image_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Cloudflare image cleanup failed: %s (attempt %d)", str(e), self.request.retries + 1)
|
||||
# Retry with exponential backoff
|
||||
try:
|
||||
self.retry(exc=e, countdown=60 * (2 ** self.request.retries))
|
||||
except self.MaxRetriesExceededError:
|
||||
logger.error("Max retries exceeded for Cloudflare image cleanup: %s", image_id)
|
||||
return {
|
||||
"image_id": image_id,
|
||||
"success": False,
|
||||
"message": f"Failed after {self.request.retries + 1} attempts: {str(e)}",
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user