Don't abuse Django's DEBUG setting
Tags: django, python, webdev, programming
Published: 2021-10-12 16:33:30

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:

```py
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](https://en.wikipedia.org/wiki/Feature_toggle):

```py
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](https://en.wikipedia.org/wiki/Principle_of_least_astonishment).

Aside from the complexities in testing, [Twelve factor App](https://12factor.net/) suggests aiming for [dev/prod parity](https://12factor.net/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:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rb0rkyw41frifamgstpo.png)

## Feature flag implimentation

[12 factor app](https://12factor.net) suggests feature flags can be switched on or off using [environment variables](https://12factor.net/config). You could install a [library](https://django-environ.readthedocs.io/en/latest/) to handle the parsing of environment variables or do something like:

```py
# settings.py
from ast import iteral_eval
from os import getenv

IS_ADMIN_ENABLED = literal_eval(getenv('IS_ADMIN_ENABLED', 'False'))
```

[`ast.iteral_eval`](https://docs.python.org/3/library/ast.html#ast.literal_eval) is that helpful function you always needed but never knew was provided out of the box by Python. It can convert string `"False"` to boolean `False` and string `"True"` to boolean `True`.

### Does your Django codebase have tech debt like this?

[Django Doctor](https://django.doctor) can find and fix over 40 types of common Django tech debt, and can even [review your GitHub Pull Requests](https://github.com/marketplace/django-doctor/) for you.