{"id":8088,"library":"django-apscheduler","title":"Django APScheduler","description":"django-apscheduler is a Django application that provides a lightweight wrapper around APScheduler, enabling the scheduling and persistence of background jobs within a Django project using its ORM. It allows for managing scheduled tasks directly through the Django admin interface and is suitable for applications requiring basic scheduling features without external task queues like Celery. The current version is 0.7.0, and releases occur as needed to support newer Django/Python versions and address fixes.","status":"active","version":"0.7.0","language":"en","source_language":"en","source_url":"https://github.com/jcass77/django-apscheduler","tags":["django","scheduler","background tasks","jobs","apscheduler"],"install":[{"cmd":"pip install django-apscheduler","lang":"bash","label":"Install stable version"}],"dependencies":[{"reason":"Core scheduling library. django-apscheduler provides a wrapper for it.","package":"APScheduler","optional":false},{"reason":"Web framework that django-apscheduler integrates with.","package":"Django","optional":false}],"imports":[{"symbol":"DjangoJobStore","correct":"from django_apscheduler.jobstores import DjangoJobStore"},{"symbol":"DjangoJobExecution","correct":"from django_apscheduler.models import DjangoJobExecution"},{"symbol":"util","correct":"from django_apscheduler import util"},{"symbol":"BlockingScheduler","correct":"from apscheduler.schedulers.blocking import BlockingScheduler"},{"note":"Often used for web servers, but BlockingScheduler is recommended for dedicated process.","symbol":"BackgroundScheduler","correct":"from apscheduler.schedulers.background import BackgroundScheduler"},{"symbol":"CronTrigger","correct":"from apscheduler.triggers.cron import CronTrigger"},{"symbol":"IntervalTrigger","correct":"from apscheduler.triggers.interval import IntervalTrigger"}],"quickstart":{"code":"import logging\nfrom django.conf import settings\nfrom apscheduler.schedulers.blocking import BlockingScheduler\nfrom apscheduler.triggers.cron import CronTrigger\nfrom django.core.management.base import BaseCommand\nfrom django_apscheduler.jobstores import DjangoJobStore\nfrom django_apscheduler.models import DjangoJobExecution\nfrom django_apscheduler import util\n\nlogger = logging.getLogger(__name__)\n\ndef my_job():\n    # Your job processing logic here...\n    logger.info(\"My job is running!\")\n\n# The `close_old_connections` decorator ensures that database connections that have become\n# unusable or are obsolete are closed before and after your job has run. You should use it\n# to wrap any jobs that you schedule that access the Django database in any way.\n@util.close_old_connections\ndef delete_old_job_executions(max_age=604_800):\n    \"\"\"This job deletes APScheduler job execution entries older than `max_age` from the database.\"\"\"\n    logger.info(\n        \"Deleting old job executions... (anything older than %s seconds)\", max_age\n    )\n    DjangoJobExecution.objects.delete_old_job_executions(max_age)\n\nclass Command(BaseCommand):\n    help = \"Runs APScheduler.\"\n\n    def handle(self, *args, **options):\n        scheduler = BlockingScheduler(timezone=settings.TIME_ZONE)\n        scheduler.add_jobstore(DjangoJobStore(), \"default\")\n\n        scheduler.add_job(\n            my_job,\n            trigger=CronTrigger(second=\"*/10\"),  # Every 10 seconds\n            id=\"my_job\",  # The `id` assigned to each job MUST be unique\n            max_instances=1,\n            replace_existing=True,\n        )\n        logger.info(\"Added job 'my_job'.\")\n\n        scheduler.add_job(\n            delete_old_job_executions,\n            trigger=CronTrigger(day_of_week=\"mon\", hour=\"00\", minute=\"00\"),  # Midnight on Monday\n            id=\"delete_old_job_executions\",\n            max_instances=1,\n            replace_existing=True,\n        )\n        logger.info(\n            \"Added daily job: 'delete_old_job_executions'.\"\n        )\n\n        # Add a listener to log job executions and errors\n        # register_events(scheduler)\n\n        scheduler.start()\n        logger.info(\"Scheduler started. Press Ctrl+C to exit.\")\n\n# To run this, save it as `your_project_name/management/commands/runapscheduler.py`\n# Then, in your Django settings.py, add 'django_apscheduler' to INSTALLED_APPS.\n# Run migrations: `python manage.py migrate`\n# Start the scheduler: `python manage.py runapscheduler` (preferably in a dedicated process)","lang":"python","description":"To set up `django-apscheduler`, first add `django_apscheduler` to your `INSTALLED_APPS`. Then, create a custom Django management command (e.g., `your_project_name/management/commands/runapscheduler.py`) to initialize and start the scheduler with the `DjangoJobStore`. This approach is recommended to ensure a single scheduler instance runs in a dedicated process, avoiding duplicate job executions in multi-process web server environments. Finally, run `python manage.py migrate` and then execute your custom command, typically managed by a process supervisor like systemd or Supervisor."},"warnings":[{"fix":"Upgrade Python to 3.9+ and Django to 4.2+.","message":"Version 0.7.0 dropped support for Python 3.8 and Django 3.2. Ensure your environment meets the new minimum requirements.","severity":"breaking","affected_versions":"0.7.0+"},{"fix":"Run the scheduler in a single, dedicated process using a custom Django management command (e.g., `python manage.py runapscheduler`), managed by a process supervisor like systemd or Supervisor. Do not start the scheduler directly in `apps.py`'s `ready()` method or `urls.py` if running with multiple web server workers. [1, 7, 8, 13, 16]","message":"Running `django-apscheduler` within a multi-process web server (e.g., Gunicorn with multiple workers) can lead to jobs running multiple times or being missed, as APScheduler lacks inter-process synchronization. It does not support shared job stores across multiple active schedulers.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Use `scheduler.add_job()` or `@scheduled_job` instead of `@register_job` for scheduling tasks.","message":"The `@register_job` decorator was deprecated in favor of APScheduler's native `add_job()` method or `@scheduled_job` decorator.","severity":"deprecated","affected_versions":"0.5.0+"},{"fix":"Apply the `@util.close_old_connections` decorator to any scheduled job function that interacts with the Django ORM. For persistent issues, consider implementing a database connection pooler (e.g., PgBouncer for PostgreSQL) as part of your deployment strategy. [8, 13]","message":"Jobs that access the Django database (ORM) can suffer from 'lost connection' errors or timeouts. Django's database connection management is typically designed for short-lived HTTP requests.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure `django-apscheduler`'s `APScheduler` dependency is `<4.0` unless an explicit update to `django-apscheduler` for `APScheduler` 4.0+ is released. While v0.7.0 bumps dependencies, APScheduler 4.0 requires deep integration changes not just a version bump. [3, 7, 13, 14]","message":"APScheduler 4.0 introduced significant architectural changes (e.g., new job store design, event brokers, new terminology). `django-apscheduler` versions prior to one explicitly stating APScheduler 4.0 compatibility may not work correctly with `APScheduler` 4.x.","severity":"breaking","affected_versions":"< 0.7.0 (likely continues for future 3.x APScheduler versions compatible with django-apscheduler 0.7.0)"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Ensure only one scheduler instance is active. The recommended approach is to run the scheduler in a dedicated process using a custom Django management command (e.g., `python manage.py runapscheduler`), and manage this process with a supervisor (like systemd, Supervisor, or Circus). Avoid initializing the scheduler directly within `apps.py`'s `ready()` method if your web server uses multiple worker processes. [1, 7, 8, 13, 16]","cause":"Multiple scheduler instances are being started, usually due to a multi-process web server (like Gunicorn with multiple workers) starting a scheduler in each worker process.","error":"APScheduler is running twice on production server! / Jobs running multiple times."},{"fix":"Decorate all job functions that access the Django ORM with `@util.close_old_connections`. For environments where connections frequently time out, consider configuring Django's `CONN_MAX_AGE` setting and/or using a dedicated connection pooler (e.g., PgBouncer) with your database. Restart the `django-apscheduler` process after a database server restart. [1, 8, 13]","cause":"Long-running scheduled jobs are holding onto database connections that time out, or the database server was restarted while jobs were running.","error":"Lost connection to MySQL server during query / database connection failure / psycopg2.OperationalError: server closed the connection unexpectedly"},{"fix":"Ensure job functions are module-level functions or static/class methods that can be referenced by their `module:function_name` string. Avoid scheduling lambda functions, bound methods to specific instances, or functions nested within other functions. [5]","cause":"APScheduler cannot reliably serialize and deserialize lambda functions, bound methods, or nested functions when jobs are stored persistently.","error":"ValueError: This Job cannot be serialized since the reference to its callable (<bound method xxxxxxxx. on_crn_field_submission of <__main__. xxxxxxx object at xxxxxxxxxxxxx>>) could not be determined. Consider giving a textual reference (module:function name) instead."},{"fix":"This issue was mitigated by `unique_job_executions` constraint in v0.6.1 and further by a database lock in v0.7.0. Ensure you are on `django-apscheduler` 0.7.0+ and have run migrations. Also, rigorously follow the 'single scheduler' recommendation to prevent race conditions. [3]","cause":"Attempting to insert a duplicate job execution entry for the same job ID and run time, potentially due to multiple schedulers or race conditions.","error":"django.db.utils.IntegrityError: UNIQUE constraint failed: django_apscheduler_djangojobexecution.job_id, django_apscheduler_djangojobexecution.run_time"}]}