Django silently ignores settings that were removed in older versions - how do I find them?
I maintain a Django project that has been around since Django 1.11 and has been upgraded step by step to 5.2. The upgrades themselves went fine — tests are green, everything runs.
What worries me is settings.py. It has grown for years and I'm fairly sure there are leftovers in there that do absolutely nothing anymore:
# settings.py (excerpt)
TEMPLATE_DIRS = [BASE_DIR / "templates"] # ???
SEND_BROKEN_LINK_EMAILS = True # ???
USE_L10N = True
FILE_CHARSET = "utf-8"
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
The problem is that Django never complains about any of this. The settings object is essentially a namespace — if nothing reads an attribute, the attribute is just dead weight. python manage.py check is happy, runserver is happy, the tests are happy. TEMPLATE_DIRS has been ignored since 1.10 and I only found out by accident.
What I tried:
Deprecation warnings (
python -Wall manage.py check). These only help while a feature is deprecated. Once it's actually removed, the code that emitted the warning is gone too, so you get silence. For a project that skipped several versions, that window has long closed.django-upgrade/pyupgrade. Great tools, but they rewrite code patterns (ugettext,url(), ...). They don't tell me that a settings variable stopped having any effect five versions ago.Reading the release notes. The "Features removed in X" section exists for every release, but going through 30+ releases by hand and diffing that against my settings is not something I want to do again on the next upgrade.
Is there a way to get this checked automatically — ideally as part of the regular manage.py check run so I can wire it into CI?
Why Django stays quiet
This is expected behaviour, not a bug. django.conf.settings is a lazy wrapper around your settings module and does not validate names. Any uppercase attribute is accepted, and unknown ones are simply never read.
Deprecation warnings live inside the code that consumes a setting. When a feature is removed, that consuming code is deleted — and the warning goes with it. So the timeline looks like this:
| Django version | What you get for USE_L10N |
|---|---|
| 4.0 - 4.2 | RemovedInDjango50Warning |
| 5.0+ | nothing at all, the setting is dead |
Miss the window and there is no runtime signal left. The only authoritative source is the "Features removed in X" section of each release note.
Automating it
You can turn those release notes into system checks. There is a small package that does exactly that: django-removals. It ships a curated list of settings removed between Django 1.4 and current main, and registers a system check for them — no runtime overhead, no new dependency chain, roughly 100 lines of code.
pip install django-removals
# settings.py
INSTALLED_APPS = [
# ...
"django_removals",
]
Since it only registers system checks (which don't run in production), you can scope it to local development:
if DEBUG:
INSTALLED_APPS += ("django_removals",)
Running python manage.py check against the settings from the question then gives you:
System check identified some issues:
WARNINGS:
TEMPLATE_DIRS: (removals.W0110/template_dirs) The 'TEMPLATE_DIRS' setting was removed in Django 1.10 and its use is not recommended.
HINT: Please refer to the documentation: https://docs.djangoproject.com/en/stable/releases/1.10/#features-removed-in-1-10.
SEND_BROKEN_LINK_EMAILS: (removals.W018/send_broken_link_emails) The 'SEND_BROKEN_LINK_EMAILS' setting was removed in Django 1.8 and its use is not recommended.
HINT: Please refer to the documentation: https://docs.djangoproject.com/en/stable/releases/1.8/#features-removed-in-1-8.
FILE_CHARSET: (removals.W031/file_charset) The 'FILE_CHARSET' setting was removed in Django 3.1 and its use is not recommended.
HINT: Please refer to the documentation: https://docs.djangoproject.com/en/stable/releases/3.1/#features-removed-in-3-1.
USE_L10N: (removals.W050/use_l10n) The 'USE_L10N' setting was removed in Django 5.0 and its use is not recommended.
HINT: Please refer to the documentation: https://docs.djangoproject.com/en/stable/releases/5.0/#features-removed-in-5-0.
DEFAULT_FILE_STORAGE: (removals.W051/default_file_storage) The 'DEFAULT_FILE_STORAGE' setting was removed in Django 5.1 and its use is not recommended.
HINT: Please refer to the documentation: https://docs.djangoproject.com/en/stable/releases/5.1/#features-removed-in-5-1.
Each warning links to the release note that removed the setting, which is usually where the migration path is described (DEFAULT_FILE_STORAGE → STORAGES, USE_L10N → always on, etc.).
Wiring it into CI
Because these are ordinary system checks, --fail-level works:
python manage.py check --fail-level WARNING
That turns a forgotten setting into a red pipeline instead of a surprise three years later. If you have a leftover you deliberately want to keep (a third-party package reading it, for instance), silence that one check by ID:
SILENCED_SYSTEM_CHECKS = ["removals.W018/send_broken_link_emails"]
Two things worth knowing
The check compares against your installed Django version and only reports settings that are already gone. Settings that are merely deprecated aren't reported — that's what
RemovedInDjangoXXWarningis for. So run it after bumping Django, not before.It covers settings only. For removed APIs in your actual code,
django-upgraderemains the better tool. The two complement each other rather than overlap.
Manual alternative if you'd rather not add a dependency: the removed-settings list is a single dict in checks/settings.py — you can copy it into your own AppConfig.ready() check and skip the package entirely.
Disclosure: I'm a contributor to django-removals. The manual approach above works just as well; the package only saves you from maintaining the list yourself.