Add proper email notifications
This commit is contained in:
80
apps/submissions/management/commands/process_submissions.py
Normal file
80
apps/submissions/management/commands/process_submissions.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""`python manage.py process_submissions` -- implements plan.md §7.5.
|
||||
|
||||
Drains one batch of submissions stuck in `processing`: validates each row's
|
||||
STL (uploads) or URL (printables / makerworld / thingiverse) and transitions
|
||||
to `verifying` on success or `rejected` on failure. Runs on a 30-second
|
||||
loop from the `web` container's entrypoint (plan.md §10) -- one invocation
|
||||
of this command per tick.
|
||||
|
||||
Concurrency: `select_for_update(skip_locked=True)` keeps replicas / stray
|
||||
cron ticks / a re-entrant restart from grabbing the same row. On SQLite the
|
||||
locks are no-ops (dev only); Postgres in prod gets the non-blocking lock
|
||||
semantics the design assumes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.submissions.emails import send_status_update_email, send_verifying_email
|
||||
from apps.submissions.models import Submission
|
||||
from apps.submissions.validation import (
|
||||
ValidationError,
|
||||
validate_external_url,
|
||||
validate_stl_file,
|
||||
)
|
||||
|
||||
BATCH = 50
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = (
|
||||
"Drain submissions stuck in `processing` -- one batch per invocation. "
|
||||
"Designed to be called on a loop from the web container entrypoint; "
|
||||
"safe to also call ad-hoc for debugging."
|
||||
)
|
||||
|
||||
def add_arguments(self, parser) -> None:
|
||||
parser.add_argument(
|
||||
"--batch",
|
||||
type=int,
|
||||
default=BATCH,
|
||||
help=f"Max rows to process per invocation (default: {BATCH}).",
|
||||
)
|
||||
|
||||
def handle(self, *args, batch: int, **opts) -> None:
|
||||
with transaction.atomic():
|
||||
# `list(...)` materialises the slice inside the transaction so
|
||||
# subsequent `.save()` calls don't invalidate the queryset.
|
||||
queue = list(
|
||||
Submission.objects
|
||||
.select_for_update(skip_locked=True)
|
||||
.filter(status=Submission.Status.PROCESSING)
|
||||
.order_by("updated_at")[:batch]
|
||||
)
|
||||
|
||||
if not queue:
|
||||
return
|
||||
|
||||
for sub in queue:
|
||||
try:
|
||||
if sub.source_type == Submission.SourceType.UPLOAD:
|
||||
validate_stl_file(sub.stl_file.path)
|
||||
else:
|
||||
validate_external_url(sub.source_url, sub.source_type)
|
||||
except ValidationError as exc:
|
||||
sub.status = Submission.Status.REJECTED
|
||||
sub.operator_notes = f"Automatic rejection: {exc}"
|
||||
sub.closed_at = timezone.now()
|
||||
# closed_by stays NULL -- the validator did the rejecting,
|
||||
# not an operator (plan.md §5 / §7.3).
|
||||
sub.save()
|
||||
send_status_update_email(sub, previous_status="processing")
|
||||
self.stdout.write(f"rejected {sub.slug}: {exc}")
|
||||
else:
|
||||
sub.status = Submission.Status.VERIFYING
|
||||
sub.save()
|
||||
send_verifying_email(sub)
|
||||
self.stdout.write(f"verifying {sub.slug}")
|
||||
Reference in New Issue
Block a user