Color Django shell by development environment
Tags: django, linux, python
Published: 2023-05-15 14:14:41

I often perform operational tasks such as modifying data or debugging failures using the Django shell. In fact, anything I can't do through the admin or through management commands I use the shell for. The package [django-extensions](https://github.com/django-extensions/django-extensions) provides a shell on steroids that I much prefer over the built in shell. It allows you to run a custom REPL such as IPython or Ptpython and has [autocomplete, command history, automatic model class imports](https://django-extensions.readthedocs.io/en/latest/shell_plus.html) and a myriad of other dev tools. shell_plus is for a Django programmer what a scalpel is for a surgeon.

Most Django sites that have receive some amount of traffic have multiple development environments. Naturally there is always a production environment and usually a staging and (local) development environment. In the past I've fat-fingered and corrupted production data while thinking I was working on the staging environment. To avoid the fiasco I've built tooling to reduce the probability of future fuck-ups.

Using the shell and some Python configuration we can get visual indicators of what environment we're in. I like to mark the production environment in some tone of red to indicate danger and staging in blue to indicate that careless hacking is fine.

Local development
![Local development](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1vkvvnyljylkiaxayyx.png)

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

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

### Installing shell_plus and ptpython

To use a programmable shell we need the following ingredients:

- [django-extensions/django-extensions](https://github.com/django-extensions/django-extensions) which contains the management command `shell_plus` and many other great tools
- [prompt-toolkit/ptpython](https://github.com/prompt-toolkit/ptpython#features) a better Python REPL which will power the shell_plus experience. You could use other REPLs such as IPython or BPython, but I prefer ptpython.

Enable django_extensions by adding it to your INSTALLED_APPS

```py
INSTALLED_APPS = (
    ...
    "django_extensions",
    ...
)
```

By default shell_plus will detect and use an enhanced REPL if one is installed.

### Customizing ptpython

Ptpython reads from a configuration file where we can specify custom keybindings and the REPL colorscheme. The default configuration file can be copied from [prompt-toolkit/ptpython/blob/master/examples/ptpython_config/config.py](https://github.com/prompt-toolkit/ptpython/blob/master/examples/ptpython_config/config.py).

At the bottom of the configure method we'll add code that detects the current environment and changes the REPL title and colors depending the environment. I use Sentry in nearly all of my projects and filter issues based on the environment. [SENTRY_ENVIRONMENT](https://docs.sentry.io/product/sentry-basics/environments/) is injected by the deployment tool and we can use that variable to paint the shell distinctly for each environment. I like black for local development, blue for staging and red for production. Additionally we'll make the ptpython toolbar use bold font for for staging and production.

```py
"""
Configuration example for ``ptpython``.
Copy this file to $XDG_CONFIG_HOME/ptpython/config.py
On Linux, this is: ~/.config/ptpython/config.py
"""
import os

from prompt_toolkit.styles import Style

__all__ = ["configure"]


def configure(repl):
	# Default configuration above...

	# Read the environment variable from what we set for Sentry
    environment = os.environ.get("SENTRY_ENVIRONMENT", "local")

    if "prod" in environment:
        custom_colorscheme = {"status-toolbar": "bg:#C70039 #000000 bold"}
        title = "PRODUCTION"
    elif "staging" in environment:
        custom_colorscheme = {"status-toolbar": "bg:#005ca4 #000000 bold"}
        title = "STAGING"
    else:
        custom_colorscheme = {"status-toolbar": "#000000"}
        title = "DEV"

    repl.install_ui_colorscheme("custom", Style.from_dict(custom_colorscheme))
    repl.use_ui_colorscheme("custom")
    repl.title = title
```

All that's left to do is to start the shell

```
./manage.py shell_plus
```

If you're trying out different colors and want to reflect the changes in real-time you can set the variable for the command and copy the configuration file to reflect the changes.

```
cp ptpython_config.py ~/.config/ptpython/config.py && SENTRY_ENVIRONMENT=production ./manage.py shell_plus
```

### Configuring ptpython for Docker

```docker
FROM python:3.10.8

# Install your dependencies here

COPY ptpython_config.py /root/.config/ptpython/config.py

COPY . /app/

CMD ["gunicorn", "-b", "0.0.0.0:80", "config.wsgi"]
```