Because after 7 years working on the same big Django codebase, when I wanted to start a new project from scratch (a small demo for another blog post that’s yet to come 😅), I had a hard time Because none of the Django project templates I found on the web felt right to me (either too opinionated, or too weak on some aspects) Because tooling has evolved greatly recently 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. ⚒️ A quick word on tooling# 🐍 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): Having my Django apps nested under my Django project: not only it doesn’t help me thinking about my apps as potentially reusable (ensuring separation of concerns), but also it creates a situation where the configuration folder (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? Having a redundant 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 conf Not having manage.py at my Git root Not having wsgi.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 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: How do we split our settings to keep them readable? Is it gonna be django-split-settings ? Do we need a third-party lib for that? How do we separate environments (devel/testing/staging/prod)? Is it gonna be django-configurations ? Do we need a third-party lib for that? How do we even manage environment variables? Is it gonna be .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? 🎷 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: settings/base.py is basically the settings.py that startproject command generated default.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 Whenever you use the just startapp myapp recipe, a myapp.py is created within settings/apps Whenever you install a third-party app, you can put its settings in settings/apps/THE_APP.py in order to find them easily later devel.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 overrides 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: Each settings file stays thin, even base.py Each apps settings file can insert stuff into MIDDLEWARE by simply from ..base import MIDDLEWARE Environment-specific overrides can be found immediately 🤔 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: How to load envvars from the .env file into our application’s environment? What about version-control? Do we distribute a generic .env.dist file that each developer has to copy? What about the production .env file? As the .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: I don’t want two separate mechanisms to manage my envvars, according to their need of secrecy I don’t want to ask myself “Is this a secret?” every time I add an envvar And hell I don’t want to be forced to depend on a cloud providers “vault” feature, even if the cost is near-zero 🗨️ (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 .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: One for you, i.e. for your own .env file One for the team, i.e. for shared .env files, such as production 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: Django is awesome! Its templating system is both powerful and easy to use The built-in 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! But for modern web UIs, static files are not enough: The CSS that CSS specialists want to write is not the one they want to send to the browsers: they want it minified, they want to use nested selectors that are not yet supported, maybe even SCSS mixins and third-party libs! The same is true for Javascript, but at a much higher severity: Javascript specialists want to write ES modules and classes, they want to use import for using third-party code, they want linting, maybe even typing! And guess what: they are right! We simply cannot deliver static jQuery plugins like it’s 2010: web UI elements of a website cannot be considered second-class citizens! 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: CSS and Javascript dependency management with standard package.json for use with npm: never import CSS/JS from CDN again, and keep your dependencies up-to-date! Fast build/transpilation/lint/bundling/minification with esbuild Write your CSS and JS code as you want in each Django apps 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 default A Django app named ui 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. ❓ 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: If it’s considered useless, I’ll stop working on it If it’s considered useful but flawed, I may take some time to improve it If it’s considered useful but just imperfect/incomplete, I will take some time to fix bugs and add features 📜 Legal notice# The repository is released under GPLv3 license, which means: You are allowed to use this template for any kind of project, unconditionally If you modify this template or include it as part of a larger project template, you have to release your changes and your derived work under GPLv3