Scheduled reports¶
Scheduled reports run an independent copy of a validated row or summary report and email the complete bounded result to an explicitly confirmed list of recipients. The feature is available from the Scheduled reports link in the QBE builder or through Django Admin’s Django QBE 2 navigation.
A schedule belongs to the staff user who created it. Only that owner can view, edit, pause, resume or delete it; superusers do not bypass this ownership rule. The definition is copied when the schedule is created. Changing or deleting the source saved query does not change the schedule. Use Edit definition and explicitly update the snapshot to change what future runs execute.
Warning
Email recipients receive report data as an attachment. They do not gain application access, and BCC hides recipient addresses from one another, but recipients can keep or forward the file. Confirm that every address is authorized to receive the report. Use the optional domain allowlist for an additional deployment-level restriction.
Create a schedule¶
Build a row report or grouped summary and choose Preview report.
Choose Schedule this report from the results page.
Set a name, daily or weekly frequency, local time, IANA timezone, recipients and CSV or Excel format. Excel requires
django-qbe2[excel].Confirm that the listed recipients may receive the report, then save.
Addresses are entered one per line, normalized and deduplicated. The default maximum is 10. The schedule screen shows its next run, last status and recent sanitized run history. Recipients are not displayed in one another’s messages.
Migration and storage¶
After upgrading the package and adding django_qbe2 to INSTALLED_APPS,
apply its migrations:
python manage.py migrate
The package adds ScheduledReport and ScheduledReportRun tables to the
default database. User foreign keys use AUTH_USER_MODEL. Schedules store a
bounded definition, recurrence, recipients and output format. Run history stores
only occurrence timestamps, status, attempts, a short lease and a sanitized
message. Report rows, SQL results and attachment contents are never persisted.
Run the scheduler¶
The package does not start background threads or require Celery. Invoke this bounded management command periodically through cron, a platform scheduler or your task system:
python manage.py qbe2_run_scheduled_reports --limit 25
For example, a cron entry can poll once per minute:
* * * * * cd /srv/myproject && /srv/myproject/.venv/bin/python manage.py qbe2_run_scheduled_reports --limit 25
The database lease and unique schedule/occurrence record prevent two workers from claiming the same occurrence. SQLite and PostgreSQL are supported. A worker that exits without recording a result can be reclaimed after its 15-minute lease expires. Failed runs retry after 5 and then 10 minutes, with at most three attempts. A permission loss disables the schedule and requires owner review.
If the command is down for several periods, it sends only the most recent due occurrence and moves to the next future occurrence; it does not send a burst of missed reports. Pause stops new claims; an already claimed run can finish. Resume chooses the next future local occurrence.
Delivery uses an at-least-once boundary: email delivery and saving the success record cannot be one atomic transaction. If the process stops after the mail server accepts a message but before the database records success, a retry may send that same occurrence again. Keep the command supervised and monitor run history for failed deliveries.
Email configuration¶
Django QBE 2 sends a plain-text EmailMessage through the project’s default
Django mailer. Configure that mailer for your deployment. For Django 6.0, use
the version’s email settings, for example:
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.example.com"
EMAIL_PORT = 587
EMAIL_HOST_USER = os.environ["SMTP_USERNAME"]
EMAIL_HOST_PASSWORD = os.environ["SMTP_PASSWORD"]
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = "reports@example.com"
Django 6.1 introduces MAILERS; configure the default mailer there instead
of relying on the deprecated EMAIL_* settings:
MAILERS = {
"default": {
"BACKEND": "django.core.mail.backends.smtp.EmailBackend",
"OPTIONS": {
"host": "smtp.example.com",
"port": 587,
"username": os.environ["SMTP_USERNAME"],
"password": os.environ["SMTP_PASSWORD"],
"use_tls": True,
"timeout": 10,
},
},
}
DEFAULT_FROM_EMAIL = "reports@example.com"
For a safe local run, configure Django’s console email backend; it prints the message without contacting an external service. Django’s test runner uses the in-memory backend automatically. QBE does not require an SMTP or email-provider package of its own. See Django’s email documentation and MAILERS settings.
Recurrence and timezone behavior¶
Schedules run daily or weekly at minute precision in the saved IANA timezone. The database stores the next occurrence in UTC. During a daylight-saving gap, the occurrence moves to the first valid local minute after the gap. During a repeated hour, it uses the first occurrence. These rules keep the local schedule predictable while avoiding skipped or duplicate runs at a clock transition.
Troubleshooting¶
No email was sent: inspect the schedule’s last-run status and the host project’s configured mailer; run the management command manually to see its batch summary.
The schedule was disabled: restore the owner’s active staff status, report-level view permissions and any related-report permissions, then review the definition and resume it.
The report exceeded a limit: narrow its conditions, reduce selected related values or groups, or adjust the relevant positive integer QBE limit. The package will not send a partial attachment.
A recipient domain is rejected: check
QBE2_SCHEDULED_EMAIL_DOMAIN_ALLOWLISTin the settings module used by the command process.No run is due: check the stored timezone and next-run time; the command only processes due schedules and should be invoked periodically.