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.