Almost everything is optimized for simplicity. If you’re maintaining dozens of Django sites, you don’t want to have to wade through a lot of files. The Django project is designed to be deployable-to Heroku.
Here’s the layout of a fictional Django project called “elderberry” with one app called “camelot”:
├─elderberry
│ ├─apps/
│ │ └─camelot
│ │ ├─static/camelot
│ │ │ ├─app.css
│ │ │ └─app.js
│ │ ├─static_src
│ │ │ ├─js
│ │ │ └─sass
│ │ ├─templates/camelot
│ │ │ └─index.html
│ │ ├─tests/
│ │ │ ├─test_models.py
│ │ │ └─test_views.py
│ │ ├─factories.py
│ │ ├─models.py
│ │ ├─urls.py
│ │ └─views.py
│ ├─libs/
│ ├─settings.py
│ ├─urls.py
│ └─wsgi.py
├─.env
├─LICENSE
├─Makefile
├─Procfile
├─README.md
├─manage.py
├─package.json
└─requirements.txt
If it looks really complicated for a “simple” project, I think so too. I’m considering changing things up. In particuar, the apps folder makes for very long import statements and might be confusing with the new Django app registry.
I store private configuration in an .env file. It usually looks something like:
DJANGO_SETTINGS_MODULE=elderberry.settings
DATABASE_URL=postgres://docker@elderberry-db1/elderberry_dev
I default to Apache. I hear it has better patent clauses.
I use make as my general task runner. For bigger projects, I add Invoke too.
My makefile commands are:
For Heroku support. You’ll notice I use waitress instead of gunicorn like the Heroku docs say to use.
I use Markdown because I don’t like using ReStructured Text. Projects are never published to PyPI, so I can use the format I prefer.
This is the one Django file I leave in the root because Heroku’s default Python buildpack will automatically run collectstatic for you if manage.py is in the root.
JavasScript requirements. I use Grunt for compiling static media. It just works, has good support, and good documentation. I use CoffeeScript syntax for my Gruntfiles because it’s terser, and so I don’t have to mess with trailing commas. This also means git diffs are more readable.
I use one requirements file containing production, dev, testing, and ci requirements. For a bigger project, I would split these up so each environment only installs what it needs. For this simple project, I just throw everything into one file. Everything is pinned to a specific version to prevent pulling a breaking change.
Notable requirements:
I use a single settings.py file, not a settings module. Logic for different environments are done with environment variables.
I almost always add a namespace to my includes, even if my project only has one app. Odds are, you’re probably adding a namespace to your URL names one way or another. You might as well do it the way the developers intended. TODO: favicon.ico and robots.txt
This is the standard Django wsgi.py with whitenoise to serve static media. If you’ve read the Heroku Django guide, it recommends dj-static, but my personal experience is that whitenoise is much better.
All internal Django apps go here. This means that your project will be full Based on research into other Django project layouts, and the django-skel project. If it’s in your INSTALLED_APPS, it goes in the apps/ directory.
I have a libs/ directory like django-skel too, except I use it as a dumping ground for Python code that’s local to the project, but not in INSTALLED_APPS. That usually means it’s full of little utilities, but there can be Django views here too (just nothing with models). Think of this as a site-packages inside your project.
I keep my SASS and JS files in static_src/ directories, and Grunt compiles them into the typical static/ directory. There are many reasons why I split them apart:
The static/ directory is in .gitignore so compiled files stay out of source control. Version pinning in package.json makes it so everyone generates the same output. For Heroku, the compiled files are force added to the source, but only for Heroku. For deployment elsewhere, the Dockerfile also builds and collects staticfiles.