24
Don't abuse Django's DEBUG setting
It's tempting to treat settings.DEBUG
as a feature flag. Many devs see it as analogous to is the app not running in prod. However, using settings.DEBUG
to change the flow of code adds complexities to testing and maintaining the codebase.
As a concrete example, let's say we want to activate Django admin only in local development to reduce the surface area for hackers to attack in prod. We could do this:
from django.conf import settings
from django.contrib import admin
urlpatterns = [...]
if settings.DEBUG:
# not exposing admin to prod so it can't be hacked
urlpatterns.append(path('admin/', admin.site.urls))
This will work, but the code will be harder to test in isolation because the value of settings.DEBUG
changes other Django behaviours including error handling (should we see detailed crash report in browser? Should the error be emailed to admins?). Therefore instead of using settings.DEBUG
in this way consider using a feature flag:
from django.conf import settings
from django.contrib import admin
urlpatterns = [...]
if settings.IS_ADMIN_ENABLED:
# not exposing admin to prod so it can't be hacked
urlpatterns.append(path('admin/', admin.site.urls))
The reason for this complexity is the fact settings.DEBUG
is for switching whether Django should run in debug mode. Indeed it's for switching behaviour of the framework and framework libraries, not application code. Using settings.DEBUG
for also controlling the behaviour of the application code built on top of the framework makes the codebase harder to maintain and test. Mixing the layers and responsibilities like this adds complexity.
For example, DEBUG=True
shows detailed error pages for use during local development, while DEBUG=False
can result in the error instead being emailed to settings.ADMINS
. Do you want to cause either of those behavioural changes while testing your code? Probably not, as this is inconvenient and also goes against the principle of least surprise.
Aside from the complexities in testing, Twelve factor App suggests aiming for dev/prod parity. Using settings.DEBUG
to control which block of code the application runs prevents dev/prod parity because most developers will not test or run their local environment with DEBUG=False
. To solve this instead consider adding a feature flag specifically for this one check, which you can switch easily during unit tests and with none of the other side effects inherent with settings.DEBUG
.
If we spot this issue in your GitHub pull request we give this advice:
# settings.py
from ast import iteral_eval
from os import getenv
IS_ADMIN_ENABLED = literal_eval(getenv('IS_ADMIN_ENABLED', 'False'))
24