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.
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.
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.
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.
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):
mysite) will be lost in the middle of apps folders. I would rather have an apps folder where I would put all my apps, wouldn’t you?mysite naming (one for my Git root, one for my Django project): not only it feels weird to type mysite/mysite when I need to edit my site configuration, but also the Django project itself will mostly contain configuration, so I would rather name it confmanage.py at my Git rootwsgi.py/asgi.py at the same level as manage.py, while their job is essentially the same (bootstrap the application and load the configuration, just for different interfaces)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
justrecipe to start a Django app in this structure can be seen here
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:
.env files? If so, what about secrets? And how are we going to load envvars into Django? Is it gonna be environs
? python-dotenv
? I thought loading envvars into an application was the environments job, not the applications…But what if I told you you can solve all this without any Python dependency?
🗨️ 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:
settings/base.py is basically the settings.py that startproject command generateddefault.py imports everything from base.py, and everything from every Python module found in settings/apps ; it’s called default because everything it contains is imported by __init__.py ; this makes conf.settings a perfectly valid Django settings module, allowing default manage.py/wsgi.py/asgi.py to work out-of-the-box
just startapp myapp recipe, a myapp.py is created within settings/appssettings/apps/THE_APP.py in order to find them easily laterdevel.py and testing.py import everything from default.py and exist in order to override things for development or testing only ; it’s usually small files with a few overridesWith 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:
base.pyMIDDLEWARE by simply from ..base import MIDDLEWARE🤔 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 (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:
.env file into our application’s environment?.env.dist file that each developer has to copy? What about the production .env file?.env files are physical files, how do we manage secrets?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
:
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
.envfiles look like in the example project based on the template here
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.
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:
.env file.env files, such as productionBoth 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 😬.
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 .
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
SOPSand secrets management to the project template can be seen here .
Now that is an area of web development where I feel the Django ecosystem could do better. We are stuck in a place where:
staticfiles app makes a wonderful job at collecting JS/CSS/image files from every installed app and put them into a unified storage ready to be served. It can even avoid the cache hell with the cache-busting feature of the ManifestStaticFilesStorage!import for using third-party code, they want linting, maybe even typing!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:
package.json for use with npm: never import CSS/JS from CDN again, and keep your dependencies up-to-date!static_src directory, it gets compiled to the static directory, so that Django’s staticfiles awesomeness can shine (you can use {% static %} templatetag, compiled files get collected)ManifestStaticFilesStorage enabled by defaultui where you can centralize your foundation UI stuff like base styles, components, UI kit, etc.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.
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: