Processing: Jobs, Usecases, Cron, Signal tasks
This page documents the four modules that form the async processing pipeline.
View / Scheduler
│
▼
execute_export() ← usecases.py (entry point)
│ saves Export + ExportItem rows
│ calls Export.send_mail()
│
▼
mail_export_by_id() ← jobs.py (RQ task on "exports" queue)
│
▼
export_items() ← usecases.py (generates file, updates status)
│
├─ success → mail_successful_export() ← usecases.py
└─ failure → notify_about_failed_export() ← usecases.py
Scheduled exports follow the same pipeline but enter via schedule_export() in cron.py instead of a view.
Jobs (outputs/jobs.py)
Contains the RQ task that drives async export processing.
mail_export_by_id(export_id, export_class_name, language, filename=None)
An RQ task enqueued on the exports queue. Called by Export.send_mail() after the Export record has been persisted.
Steps:
- Resolves the export class from
export_class_nameusingimport_string, then fetches theExportbyexport_id. - Sets
export.status = PROCESSINGand saves. - Activates the requested
languagefor i18n. - Delegates to
export_items()inusecases.pyto generate the file and send email.
Any exception is logged with full traceback and re-raised so RQ marks the job as failed.
The task decorator is obtained via pragmatic.utils.get_task_decorator("exports"), which wraps the function as an RQ job bound to the exports queue.
Usecases (outputs/usecases.py)
Plain functions containing the core business logic. They are called by jobs, views, and the cron runner; nothing in this module is queue-specific.
execute_export(exporter, language)
Entry point for triggering a new export from a view or the cron runner.
- Calls
exporter.save_export()to persist theExportandExportItemrecords. - Calls
export.send_mail(language, filename)to enqueuemail_export_by_idon theexportsqueue.
Raises on any error (the caller is responsible for handling or re-raising).
export_items(export, language, filename=None)
Core processing function called inside the RQ worker.
- Sets
export.status = PROCESSING. - Activates
languagefor i18n. - Instantiates the exporter from
export.exporterand setsexporter.items = export.object_listso the exporter operates on the exact same rows that were snapshotted at export creation time. - Runs
exporter.export()inside atransaction.atomic()block:- On success: sets
export.status = FINISHED, bulk-updates allExportItemrows toRESULT_SUCCESS, then callsmail_successful_export(). - On failure: sets
export.status = FAILED, bulk-updates allExportItemrows toRESULT_FAILURE(storing the exception message asdetail), then callsnotify_about_failed_export()and re-raises.
- On success: sets
The status update and ExportItem bulk-update share the same transaction.atomic() block, so a crash after the file is generated but before the DB commit leaves the export in a consistent FAILED state.
notify_about_failed_export(export, error_detail)
Called when export_items() catches an exception.
- Logs the failure at
ERRORlevel with full traceback. - If
django-whistleis installed, sends anEXPORT_FAILEDin-app notification to the creator, all recipients, and all active superusers.
mail_successful_export(export, filename=None, output_file=None)
Sends the completed export file to recipients.
- If
OUTPUTS_SAVE_AS_FILE = True: saves the file toexports/<filename>via Django'sdefault_storage, then passes the resulting URL toget_message()instead of attaching the file directly. - If
export.send_separately = True: sends one email per recipient address. - Otherwise: sends a single email to all recipients at once.
- The file is not attached when
total == 0(empty export) or when afile_urlis available (storage mode).
get_message(exporter, count, recipient_list, subject, output_file=None, filename=None, file_url=None)
Constructs an EmailMultiAlternatives message.
- Body is produced by
exporter.get_message_body(count, file_url). - Subject is overridden by
exporter.get_message_subject()if it returns a non-Nonevalue. - The export file is attached (using
exporter.content_typeas MIME type) only whencount > 0and nofile_urlis present.
Cron (outputs/cron.py)
schedule_export(scheduler_id, scheduler_class_name)
The function registered with rq-scheduler as a recurring cron job. It is stored in Redis by Scheduler.schedule() and invoked automatically on the configured schedule.
Steps:
- Resolves
scheduler_class_nameviaimport_stringand fetches theSchedulerbyscheduler_id. Passing the class name (rather than a hard-coded import path) allows theSchedulermodel to be subclassed in the host application. - Calls
execute_export(scheduler.exporter, language=scheduler.language)to save a newExportrecord and enqueue the mail job. - Appends the current UTC datetime to
scheduler.executionsand saves only that field.
Signal tasks (outputs/signal_tasks.py)
Small functions used as deferred tasks by signal handlers. They run outside the request/save transaction, either via django-rq or invoked directly after a signal fires.
notify_about_executed_export(export)
Called 1 minute after a new Export is created (scheduled via rq-scheduler in the export_executed_post_save signal handler). Requires django-whistle.
Sends an EXPORT_EXECUTED in-app notification to all active superusers who are neither the creator nor a recipient of the export.
schedule_scheduler(scheduler)
Called after a Scheduler is saved with changed scheduling fields (triggered by the reschedule_scheduler signal). Calls scheduler.schedule(), which cancels any existing cron job in Redis and registers a new one with the current settings.