Django Version Upgrade Checklist for Production
Upgrade Django in production safely: plan the target version, read release notes, fix deprecations, upgrade dependencies, test, deploy, and roll back if needed.
Problem statement
Django upgrades are routine when done regularly and painful when postponed. A project three feature releases behind faces removed APIs, third-party packages that no longer support the old version, and a large change surface to test at once. Meanwhile, an unsupported Django version stops receiving security fixes, which eventually becomes a compliance and safety problem.
The upgrade itself is rarely complicated; the risk comes from doing several things at once (Django, Python, dependencies, and infrastructure) and from discovering behavior changes in production instead of in tests.
This checklist covers planning, preparation, execution, and verification for a Django upgrade, whether it is a patch release or a major version step.
Quick answer
- Know your current and target versions, and check Django's supported versions and end-of-life dates.
- Upgrade in steps: one feature release at a time, with dependencies and Python handled separately.
- Read the release notes for every version you pass through, especially backward-incompatible changes and removals.
- Run tests with deprecation warnings as errors on the current version first, and fix what appears.
- Verify third-party packages support the target version.
- Upgrade in a branch, run the full test suite,
check --deploy, andmakemigrations --check. - Deploy to staging with production-like data, smoke test, then deploy to production with a rollback path.
Checklist
1. Plan the target version
What to check:
python -c "import django; print(django.get_version())"
python --version
Decide the target based on Django's release cadence: feature releases roughly every eight months, with every third release an LTS supported for about three years. Two common strategies:
- LTS to LTS: fewer upgrades, larger steps, longest stability per version.
- Follow feature releases: smaller steps, earlier access to improvements and deprecation warnings.
Either way, upgrade through intermediate versions rather than jumping several releases at once, so deprecation warnings guide you. Check the supported Python versions for the target Django release; a Django upgrade sometimes requires a Python upgrade, which is a separate change to sequence deliberately.
2. Inventory dependencies
What to check:
pip list --outdated
pip show django-rest-framework django-storages celery 2>/dev/null | grep -E "^Name|^Version"
For each significant package, confirm the target Django version is supported (changelog, classifiers, or issue tracker). Packages to check carefully:
- Django REST Framework or another API layer
- django-storages, django-allauth, django-filter, and other widely used add-ons
- Celery and its Django integration
- database drivers (psycopg, mysqlclient)
- anything that touches internals: custom middleware helpers, admin extensions, template libraries
Unmaintained packages are the most common blocker. Plan replacements before the upgrade, not during it.
3. Fix deprecations on the current version
Before changing versions, run the test suite with warnings visible:
python -W error::DeprecationWarning -m pytest
# or
python -Wa manage.py test
Django emits RemovedInDjangoXXWarning warnings for APIs that the next versions remove. Fixing them while still on the current version means the upgrade itself changes very little. Address your own code first; for warnings from third-party packages, check whether a newer package release fixes them.
Deprecation warnings also appear at runtime, so a staging environment with -Wa or logging configured to capture warnings can reveal code paths tests miss.
4. Read the release notes
For every version between current and target, read:
- Backward incompatible changes: the section that breaks upgrades.
- Features removed: APIs that no longer exist.
- Deprecated features: what will break in the next upgrade, useful for planning.
- Database backend notes: minimum database versions can change.
- Security notes: behavior changes that affect settings or defaults.
Keep a short list of items that apply to your project, and check them off as you handle them.
5. Upgrade in a branch
git checkout -b upgrade-django-5.2
Update the pin and lockfile:
# pip-tools
sed -i 's/Django>=5.1,<5.2/Django>=5.2,<5.3/' requirements.in
pip-compile requirements.in
pip install -r requirements.txt
# or uv
uv lock --upgrade-package django
uv sync --locked
Then work through failures in this order: imports, settings, models and migrations, views and forms, templates, tests.
6. Check settings and migrations
python manage.py check
python manage.py check --deploy
python manage.py makemigrations --check --dry-run
python manage.py migrate --plan
Notes:
- new Django versions sometimes generate migrations for existing models (for example changed field defaults or index naming); review and commit them deliberately
makemigrations --checkfailing after an upgrade means the new version wants schema or state changes; inspect withsqlmigratebefore applying- settings that changed defaults may need explicit values if you relied on the old behavior
7. Run the full test suite
python -W error::DeprecationWarning -m pytest -x
Pay attention to:
- authentication and sessions
- forms and validation messages (wording can change)
- template rendering differences
- admin customizations
- timezone and date handling
- serialization formats in APIs
Test coverage determines how much confidence this step gives. For areas without tests, plan manual verification in staging.
8. Staging deployment
Deploy the branch to staging with production-like data:
- run migrations and record their duration
- exercise critical flows manually: signup, login, password reset, checkout or the equivalent core action, file upload, admin edits
- check background tasks and scheduled jobs
- watch logs for new warnings and errors
- compare page and API response times with the previous version
Leave the upgrade in staging for a few days if you can; some issues only appear with time or scheduled jobs.
9. Production deployment
Plan the release like any other risky deploy:
- deploy during a low-traffic window with the team available
- run migrations as a separate, monitored step; ensure they are backward compatible if old code may still run
- keep the previous release artifact ready for rollback
- monitor error rates, latency, and background queues closely for the first hours
- have a rollback decision point and criteria
For containerized deployments, the previous image tag is the rollback path. For virtualenv-based deployments, keep the previous release directory with its own environment.
10. After the upgrade
- remove compatibility shims added for the old version
- update documentation and the project's minimum supported versions
- note the next version's deprecations to address before the following upgrade
- schedule the next upgrade rather than waiting for a forcing event
Patch releases
Patch upgrades (for example 5.2.4 to 5.2.6) are much simpler: they contain bug and security fixes with no intentional backward-incompatible changes. Treat them as routine dependency updates: update the pin, run CI, deploy. Security releases should go out the same day when the vulnerability affects features you use.
Verify
Verification check:
- the target Django version runs in production with
check --deploypassing - no
RemovedInDjango*Warningwarnings appear in staging logs - migrations applied cleanly with recorded duration
- critical user flows tested manually in staging and spot-checked in production
- error rate and latency match pre-upgrade baselines
- dependencies are on versions that support the new Django
Explanation
Django's deprecation policy gives you a path: features are deprecated in one release, warned about for at least two, and removed later. Upgrading one feature release at a time, with warnings turned into errors, converts an upgrade into a series of small mechanical fixes. The remaining risk is behavioral rather than syntactic, which is why staging with real data and careful monitoring after the production deploy matter more than the version bump itself.
Edge cases / notes
Several major changes at once
Avoid upgrading Django, Python, the database, and infrastructure in one release. Sequence them so each change can be verified and rolled back independently.
Very old projects
Projects several years behind sometimes benefit from upgrading in a dedicated branch with intermediate deployments to staging, or from replacing unmaintained dependencies first.
Custom database backends or forks
Custom backends and patched Django forks need extra review, because internal APIs change between releases without deprecation cycles.
Areas that break most often
From experience across projects, these areas deserve extra attention during a Django upgrade:
| Area | What changes | How to check |
|---|---|---|
| Settings defaults | new defaults for storages, password hashers, form rendering | diff python manage.py diffsettings output before and after (filtering secrets) |
| Storages configuration | the STORAGES setting replaced older storage settings | confirm static and media backends resolve as intended |
| Form and widget rendering | template-based rendering and CSS class changes | visual check of key forms, especially custom widgets |
| Admin templates | overridden admin templates drift from upstream | compare your overrides with the new version's originals |
| Timezone and date handling | stricter parsing or changed defaults | tests around scheduling, reports, and filters |
| Third-party middleware | changed middleware APIs | run the full request cycle in tests |
| Database backend features | minimum database versions, new SQL | check --database default, staging migrations |
Overridden Django templates are a frequent source of subtle breakage: they keep working but lose upstream fixes and new markup. Track which templates you override and review them at each upgrade.
Estimating effort
A rough guide for planning:
- Patch release: minutes to an hour, including CI and deploy.
- One feature release, well-maintained project: half a day to two days, most of it reading release notes and fixing deprecations.
- Multiple releases behind, or unmaintained dependencies: a week or more, dominated by dependency replacement rather than Django itself.
The cheapest strategy is to keep up: each step stays in the first two categories.
Tracking supported versions
Add a recurring calendar reminder to check Django's supported versions page each quarter, recording:
- the version you run and its end-of-life date
- the next version you plan to move to, and when
- Python versions supported by both
When the end-of-life date is within two quarters, schedule the upgrade work explicitly instead of leaving it to spare time.
Common mistakes
- Skipping release notes and debugging surprises later.
- Jumping several feature releases in one step.
- Upgrading Django and Python together.
- Deploying an upgrade without a rollback artifact ready.
Next steps
- Keep dependencies current with Automate Django Dependency and Security Updates.
- Verify releases with the Post-Deploy Verification Checklist for Django.
- Prepare rollbacks with How to Roll Back a Django Release Safely.
- Test upgrades first with Set Up a Staging Environment for Django.
FAQ
How often should I upgrade Django?
Apply patch releases promptly, especially security releases. Move to a new feature or LTS release well before your current version reaches the end of its support window.
Can I upgrade Django several versions at once?
It is possible but harder. Upgrading one feature release at a time lets deprecation warnings guide the changes and keeps each step small enough to test.
How do I find code that will break in the next Django version?
Run the test suite with deprecation warnings as errors (python -W error::DeprecationWarning) on your current version, and read the release notes of the target version.
Should Django and Python be upgraded together?
Prefer separate releases. Each has its own compatibility surface, and separating them makes it clear which change caused a problem.