Django: Laying out projects

A brief glance at how I like to maintain stuff, and possibly, the better way.

What’s the optimal layout for Django applications, configuration files and it’s associated directories?

I use the following directory structure. It is very opinionated, but I’ve worked upon it for so long and seems to be the most logical one for projects both small and large.

.
└── project_name # top level, everything under here is in git repo
├── Dockerfile
├── apps
│ └── app_name
│ ├── admin.py
│ ├── forms.py
│ ├── models.py
│ ├── templates # app specific templates
│ ├── urls.py
│ └── views.py
├── assets
├── requirements # dependencies for the project: prod, test etc
├── docs # documentation, if any
├── project_name # inner project directory
| ├── urls.py
| ├── views.py
| └── wsgi.py
├── scripts # CI scripts, manage.py, etc
├── settings # settings for the project: prod, test etc.
| ├── base.py
| ├── prod.py
| └── test.py
├── setup.py
├── templates # top level templates
└── tests # tests specific to every app
├── app_name
└── app_name_1
Note: Starting from Django 1.11, we get a new application configuration using the full path of AppConfig derived classes within the Django app, as specified here.

Current default hierarchy

Kickstarting an example project foo using django-admin startproject foo , we get a default directory structure like this:

foo
├── foo
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py

This layout is a great starting place, we have a top level directory foo which contains our manage.py the project directory foo/foo/ inside it. This is the directory you would check into your version control system such as git.

Configuring Settings

To fix bad settings, we’re gonna prefer usage of multiple environments with imports from base.py . Suppose we have three environments, viz. test, staging, and production.

  1. In foo/ make a settings directory and create an empty __init__.py file inside it.
  2. Move foo/foo/settings.py into foo/settings/base.py
  3. Create the individual base.py, staging.py, test.py and production.py files in foo/foo/settings/. Each of these 4 environment specific files should simply contain the following:
from base import *

So why is this important? Well for local development you want DEBUG=True, but it’s pretty easy to accidentally push out production code with it on, so just open up foo/foo/settings/production.py and after the initial import from base just add DEBUG=False. Now if your production site is safe from that silly mistake.

What else can you customize? Well it should be pretty obvious you’ll likely have staging, test, and production all pointing at different databases, likely even on different hosts. So adjust those settings in each environment file.

Using these settings is easy, no matter which method you typically use. To use the OS’s environment you just do:

export DJANGO_SETTINGS_MODULE="foo.settings.test"

And boom, you’re now using the test configuration.

Anything else to be changed?

Yes. Another useful tip is the usage of lists instead of tuples, this way we can reuse configuration in other setting files. It’s often better to break up apps into two lists, one as prerequisites and other for actual applications within the project.

PREREQUSITE_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
...
'wysiwyg_editor',
'memcached',
'haystack',
]

PROJECT_APPS = [
'homepage',
'users',
'blog',
]

INSTALLED_APPS = PREREQUISITE_APPS + PROJECT_APPS

Why is this useful? For one it helps better distinguish between Django core apps, third party apps, and your own internal project specific applications. However, PROJECT_APPS often comes in handy as a list of your specific apps for things like testing and code coverage. You have a list of your apps, so you can easily and automagically make sure their tests are run and coverage is recorded just for them, not including any third party apps, without having to maintain the list in two separate places.

Nested URLs

For small projects, it’s an easy way to get all your URLs in one place at foo/urls.py . But if you’re willing to reuse apps, you’ve define them each in their own app. So, instead of:

urlpatterns = [
url(r’^$’, HomePageView.as_view(), name=‘home’),
url(r’^blog/$’, BlogList.as_view(), name=‘blog_list’),
...
url(r’^user/list/$’, UserList.as_view(), name=‘user_list’),
url(r’^user/(?P<username>\w+)/$’, UserDetail.as_view(),
name=‘user_detail’),
]

we should do:

urlpatterns = [
url(r'^$', HomePageView.as_view(), name='home'),
url(r'^blog/', include('blog.urls')),
url(r'^user/', include('user.urls')),
...
]

Use project paths

By default, django apps within the project have to be present within the root directory.

In apps.py, AppConfig derived classes have a name attribute useful for pointing the exact location of the application.

  1. Create an apps directory with __init__.py at foo/apps
  2. Create an empty directory bar at foo/apps/bar for bar django app.
  3. Kickstart the app by using django-admin startapp bar foo/apps/bar
  4. Now change the contents of foo/apps/bar/apps.py to:
from django.apps import AppConfig
class BarConfig(AppConfig):
name = 'apps.bar'
...

5. Use the full app config path in INSTALLED_APPS setting.

INSTALLED_APPS = [
...,
'apps.bar.apps.BarConfig',
]

That’s it, you’re good to go and show off your new and better Django layout.

Footnote

In relation to GitMate and Google Summer of Code 2017, I followed these steps at code.gitmate.io to migrate all gitmate plugins into plugins/ subdirectory.