🧐 Why?

One of the reasons Django is awesome is because it’s unopinionated: it lets you make your own choices. But sometimes it’s intimidating. The idea here is to be opinionated on what’s around Django (Python tooling, structure, environment, UI tooling) but let you make your own implementation choices when it comes to your application.

🚀 TL;DR

This blog post introduces a Django project template I’ve been working on in the past few weeks. You can find it here and try it by yourself (and you can also find an example project created using the template ), but as it’s a bit unusual, you may want to read this.

🐍 Python dependency management: uv, what else?

I’ve been a long-time pip-tools + native venv (+ sometimes pyenv ) user. When pipenv , then poetry , then pdm came, some people tried to tell me that I should switch. I was never convinced. I was always doubtful about an all-in-one tool pretending to replace my custom-tailored assembly of well established specialized tools, I didn’t see enough value.

But then came uv . It made me realize that no matter my theoretical doubts, if a tool a just 100x faster, I had to embrace it.

So don’t be surprised to find this Django project template using uv.

βš™οΈ Task runner: just use just

I’ve been a long-time Makefile user. But I was frustrated by the .PHONY thing, and by the fact that some behaviors seemed weird to me (because I was hacking a build tool to make a task runner out of it).

just is what I needed from the beginning, and it doesn’t just avoid Makefile’s caveats, it also offers other advantages. So I’ve used it in this template.

πŸ—οΈ Project structure

The default project structure proposed by the official Django tutorials always felt odd to me. At the end of step 1 , you end up with:

.
β”œβ”€β”€ mysite
β”‚   β”œβ”€β”€ manage.py
β”‚   β”œβ”€β”€ mysite
β”‚   β”‚   β”œβ”€β”€ asgi.py
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ settings.py
β”‚   β”‚   β”œβ”€β”€ urls.py
β”‚   β”‚   └── wsgi.py
β”‚   └── polls
β”‚       β”œβ”€β”€ admin.py
β”‚       β”œβ”€β”€ apps.py
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ migrations
β”‚       β”‚   └── __init__.py
β”‚       β”œβ”€β”€ models.py
β”‚       β”œβ”€β”€ tests.py
β”‚       └── views.py
└── pyproject.toml

What I don’t like about it (ordered by -criticity):

It may seem like minor concerns, but when you work with a codebase on a daily basis, some of them can feel annoying.

Here is the proposed structure:

.
β”œβ”€β”€ apps
β”‚   └── polls
β”‚       β”œβ”€β”€ admin.py
β”‚       β”œβ”€β”€ apps.py
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ migrations
β”‚       β”‚   └── __init__.py
β”‚       β”œβ”€β”€ models.py
β”‚       β”œβ”€β”€ tests.py
β”‚       └── views.py
β”œβ”€β”€ conf
β”‚   β”œβ”€β”€ settings.py
β”‚   └── urls.py
β”œβ”€β”€ asgi.py
β”œβ”€β”€ manage.py
β”œβ”€β”€ pyproject.toml
└── wsgi.py

With this, anyone can immediately tell that my project is a collection of apps assembled with some configuration, and anyone immediately knows where to find anything.

Fortunately, Django is awesome: native commands startproject and startapp take arguments! I just had to write some recipes to get what I wanted. The only thing that I couldn’t get to work is having asgi.py and wsgi.py at the same level as manage.py, but that was my least critical issue.

πŸ’‘ In case you wonder how it’s done:

  • As said in the message of the commit creating the template structure, the command run was uv run manage.py startproject conf .
  • The just recipe to start a Django app in this structure can be seen here

🧱 Configuration

This is one of the main pain points I’ve seen in web development in general, and using vanilla Django doesn’t help as much as it should/could, at least when the project grows. We have our conf/settings.py file, it’s fine. But what happens when we add many third-party apps with their own settings, and when we write many home-made apps, as it’s usually done as soon as Django is used beyond a simple blog? (and please, use Django for way more than a simple blog, it’s awesome! ✨). Here are the key issues:

But what if I told you you can solve all this without any Python dependency?

🎷 Django settings

πŸ—¨οΈ A place for everything and everything in its place

Consider this structure:

.
β”œβ”€β”€ apps
β”‚   β”œβ”€β”€ …
β”‚   └── …
β”œβ”€β”€ conf
β”‚   β”œβ”€β”€ …
β”‚   β”œβ”€β”€ settings
β”‚   β”‚   β”œβ”€β”€ apps
β”‚   β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”‚   └── myapp.py
β”‚   β”‚   β”œβ”€β”€ base.py
β”‚   β”‚   β”œβ”€β”€ default.py
β”‚   β”‚   β”œβ”€β”€ devel.py
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   └── testing.py
β”‚   └── …

Here is how it works:

With this simple Python package/module/import structure, leveraging nothing else than Pythons awesomeness, I’ve found myself able to easily manage settings of dozens of Django apps (both third-party and home-made) and multiple environments (devel/testing/staging/prod) without any friction:

πŸ€” You might think that this settings structure is too much for many Django projects. And you would be right! This Django project template doesn’t target small Django applications: it proposes solutions that have proven to be effective when working on a Django project that went big (~15 third-party apps + ~15 home-made apps).

πŸ—ƒοΈ Environment variables and secrets

Environment variables (envvars) are the right way to load environment-specific settings into an application, we know that for some time now. The .env non-standard has become a de-facto standard, but it comes with its own challenges:

In my experience this has been a giant pain ; not because it’s impossible to solve, but because the solutions I’ve seen working always felt horribly over-complicated to my taste. And everybody seems to be fine with it. To be clear:

πŸ—¨οΈ (slamming the table) There must be a better way!

And there is! Behold… SOPS . Erm, yeah I know, I know, it’s not exactly new. But I haven’t met a Django project that makes use of it yet, so I gave it a try.

In this Django project template, every environment gets a version-controlled .env file containing all its envvars, including the secrets!

See how easy it is to edit an encrypted .env file using this small PyCharm plugin :

Screencast of editing a .env file in PyCharm with a plugin called β€œSimple SOPS Edit”. When opening the file, the .env keys appear unencrypted, but their values are encrypted. The plugin shows a banner saying β€œSOPS file detected” and offers to either view it unencrypted or edit it. Once the file is edited, closing it saves the encrypted version, which can be viewed unencrypted on-demand.

I also chose to put all .env files in a dedicated directory named… envs, with a dedicated subdirectory for all devel environments of your team. After project initialization (just init david), you get:

.
β”œβ”€β”€ apps
β”‚   β”œβ”€β”€ …
β”œβ”€β”€ conf
β”‚   β”œβ”€β”€ …
β”œβ”€β”€ envs
β”‚   β”œβ”€β”€ devel
β”‚   β”‚   └── david.enc.env
β”‚   └── production.enc.env
└── …

Files with extension .enc.env can safely be version-controlled, as they are encrypted by SOPS.

πŸ’‘ You can see what SOPS-encrypted .env files look like in the example project based on the template here

πŸ” Secrets encryption

In order to be fully independent, I chose age (pronounced [aɑe̞] ), the encryption tool that comes with SOPS. Future versions of this project template may include the ability of choosing your encryption method.

🀝 Collaboration

As SOPS keeps .env files keys human-readable (only values are encrypted), you can see when one of your teammates adds a new envvar somewhere (but you can’t see the value if it’s in their own .env file).

I chose to generate two distinct encryption key pairs:

Both public keys live in envvars defined in your .env file.

The secret keys live in a file within your $HOME, so you need to find a way to share the shared secret keys with your teammates. And probably you should make backups 😬.

πŸš€ Deployment and decryption

SOPS comes with a handy exec-env command (see the docs ) that decrypts an encrypted .env file and exports every envvar found in it before starting as a child process the command you want it to execute. Perfect for working with Gunicorn!

Obviously the tricky part is to provision the production secret key to your production infrastructure. Methods will vary greatly, but you have an example with Flux there .

πŸ’» Working on local devel environment

Obviously on your local environment you will keep an unencrypted version of your .env file (which is Git-ignored in the template), and it will be automatically loaded if you start Django using the following command:

Yes! just loads .env files, and I wrote the recipe to leverage uv in order to start the right Python, with the right virtualenv. Everything works out-of-the-box! You can find a list of handful commands in the template’s README .

πŸ’‘ Introduction of SOPS and secrets management to the project template can be seen here .

πŸ’… β€œFront-end” Web UI

Now that is an area of web development where I feel the Django ecosystem could do better. We are stuck in a place where:

Between 2015 and 2022, many teams have worked around this by reducing Django to an API β€œback-end”, and writing a Javascript-first β€œfront-end” with horrific maintenance costs. That was, in many cases, an absurd choice. Fortunately, some people are getting convinced that the web platform should (and can) follow the hypermedia principles, and more and more websites are sending HTML over the wire again.

❓ So how do we make web UI stuff first-class citizens with Django? I introduced this in my follow-up of the β€œMother of all Htmx Demos” , but not in a detailed way. Today I’m going a step further: it’s integrated in my proposed Django project template. Here is what it offers:

Basically the idea is to get the best of both worlds, in order to make it realistic to build a modern website or webapp with Django, and make web UI specialists want to work with Django!

πŸ’‘ How it works is visible here .

πŸ’‘ A typical integration of the famous Bootstrap UI framework can be seen in the example project based on the template, here . Yes it’s a bit more verbose than just downloading Bootstrap from a CDN, but it’s the cost of getting things done the right way.

❓ So what now?

This project template is at very early stage. Some things are missing, others are imperfect. But I hope you get the idea, and I hope that the idea makes sense to you. Feedback is more than welcome, because:

The repository is released under GPLv3 license, which means: