How does Django's deprecation policy decide when a deprecated API is removed, and how do you surface those warnings in your project?
answer
- at least two feature releases
- B.0, or B.1 if deprecated in X.2
- RemovedInDjangoXYWarning classes
- silent unless you ask
- python -Wa manage.py test
basics
~20 sAn API deprecated in release A.x keeps working with a RemovedInDjangoXYWarning and is removed in B.0, or B.1 if deprecated in the X.2 LTS, so shims last two feature releases. Surface them with python -Wa manage.py test.
solid answer
~50 sDjango deprecates before it removes. A feature deprecated in release `A.x` keeps working for the rest of the `A` series but emits a warning such as `RemovedInDjango70Warning`, and it is removed in `B.0` — or in `B.1` if it was deprecated in the last release of the series, the `X.2` LTS — so every shim survives at least two feature releases. The docs' example: something deprecated in 4.2 raised `RemovedInDjango51Warning`, still worked in 5.0 and was removed in 5.1. The warning class names the release that removes the API. Python hides these warnings by default, so turn them on while running the test suite, `python -Wa manage.py test` or `PYTHONWARNINGS=always`, fix every warning raised by your own code, and treat warnings raised inside third-party packages as something to track rather than a failure of yours.
code
bash · 3 linespython -Wa manage.py test # show every deprecation warning
PYTHONWARNINGS=always python manage.py test # the same through the environment
python -Wa manage.py test 2>&1 | grep RemovedInDjangogo deeper
Recall that deprecated Django features keep working with a warning for at least two feature releases, and that python -Wa manage.py test shows the warnings.
Explain the B.0 versus B.1 removal rule, the RemovedInDjangoXYWarning naming, DeprecationWarning versus PendingDeprecationWarning, and why warnings are silent by default.
Show how you drive a codebase to zero Django warnings before upgrading, separate your warnings from third-party ones, and lock the result in with CI filters.
Use the policy to plan: warnings are a free, dated work list, so budget them continuously instead of discovering them during a forced upgrade.
## The policy in one rule Django's **deprecation policy** says: *if a feature is deprecated in feature release A.x, it continues to work in all A.x versions but raises warnings; it is removed in B.0, or in B.1 when it was deprecated in the last A.x release.* The purpose is that every deprecation lives through **at least two feature releases**, so there is always at least one release in which your code runs *and* tells you what to change. ## The generic timeline The release-process documentation spells it out: | Release | What happens to shims | |---|---| | X.0, X.1 | New deprecations may start | | X.2 (LTS) | New deprecations may start | | Y.0 | Shims added in X.0 and X.1 are dropped | | Y.1 | Shims added in X.2 are dropped | | Y.2 (LTS) | Nothing dropped, easing LTS-to-LTS upgrades | | Z.0 | Shims added in Y.0 and Y.1 are dropped | A concrete case from 4.2: the `DEFAULT_FILE_STORAGE` and `STATICFILES_STORAGE` settings were deprecated in favour of `STORAGES`, `Meta.index_together` in favour of `Meta.indexes`, and the `length_is` template filter in favour of `length`. All kept working in 4.2 and 5.0 and were removed in **5.1**. ## The warning classes Each deprecation calls `warnings.warn()` with a class from `django.utils.deprecation` whose name states the removal release: `RemovedInDjango51Warning` in the 4.2 example, `RemovedInDjango70Warning` for APIs deprecated in 6.0 and 6.1. Two details matter: - The class for removal in the **next** release derives from `DeprecationWarning`; the class for the release **after** next derives from `PendingDeprecationWarning`. In 6.1, `RemovedInDjango70Warning` is a `PendingDeprecationWarning`, because 6.2 comes first. - Both base classes are **hidden by default** by Python for code outside the main script, so a project can emit hundreds of them without anyone noticing. ## Surfacing them The upgrade guide's advice is to resolve warnings on your *current* version before moving: 1. Run the test suite with warnings forced on: `python -Wa manage.py test`. The `a` means "always": every occurrence is printed. (The release-process page shows `-Wd`, the "default" action, which prints each distinct warning once per location; either makes them visible.) 2. With another runner, use `PYTHONWARNINGS=always` and make sure the runner does not capture the output. 3. You can also start `manage.py runserver` with `-Wa` and click through the application manually. 4. Fix every warning whose stack points into **your** code. 5. For warnings raised **inside third-party packages**, check whether a newer release fixes them; packages often use deprecated APIs deliberately to support several Django versions, so the warning alone does not mean a bug. ## Where deprecations are written down Two documents list them, and an upgrade plan uses both: 1. Each release's notes have a **Features deprecated in A.B** section, explaining what to use instead. 2. The **deprecation timeline** in Django's internals documentation lists, per removal release, everything that disappears in it — "5.1: the `DEFAULT_FILE_STORAGE` and `STATICFILES_STORAGE` settings will be removed", and so on. The warnings at runtime tell you where *your* code is affected; the documents tell you the replacement and the date. ## Making it stick Once the suite is clean, keep it clean: turn Django's own warnings into errors in CI (for example with `warnings.filterwarnings("error", category=RemovedInDjango70Warning)` in the test settings), so a new deprecation cannot creep in unnoticed. Leave third-party warnings visible but non-fatal. ## What the policy does not cover The policy is not pure semantic versioning. Each feature release may still contain a few **documented backwards-incompatible changes** where a deprecation path was impossible or not worth the cost. Those appear in the *Backwards incompatible changes* section of each release's notes, never as warnings, so reading the notes remains part of every upgrade.
- Why does Django remove X.2 deprecations in Y.1 rather than Y.0?To keep the two-feature-release minimum. Something deprecated in the X.2 LTS must survive into Y.0 so there is a release after the LTS in which it still works with a warning; removing it in Y.0 would give it only one release. The same rule means Y.2 drops nothing, keeping third-party apps able to support both LTS releases.
- Your 4.2 project's test run shows RemovedInDjango50Warning from a third-party package; is that your problem?Not directly. Packages often call deprecated APIs on purpose to support several Django versions. What matters is whether the package has a release that supports your target version; check its changelog and upgrade it, or raise an issue. Warnings pointing into your own code are the ones you fix before upgrading.
A deprecation warning is a road-closure sign posted two junctions early: the road still works for now, the sign names the date it closes, and only drivers who look up (turn warnings on) get to re-route calmly.
saying these in an interview costs you the question
- Deprecated APIs are removed in the very next feature release
- Deprecation warnings are shown by default in every Django project
- A warning-free test run guarantees nothing breaks on upgrade
- Any third-party deprecation warning is a bug you must fix yourself
- Deprecations follow strict semantic versioning with no exceptions