How to Structure a Django Project

Lots of Examples in the Wild

Judging from the available examples, there are clearly many (many) opinions among Django developers on how to best structure a Django project. There is more than one way to do it, and that's okay, because the structure of a project may depend, to some extent, on the need of a given project.

In many cases, the structure of a given project seems to make no sense whatever. It probably makes sense to the developer or developers, but not so much for someone new.

First, Some Definitions

Project Root is the top-most directory of the project where all project files and directories are stored. Most, though not all, of its contents will be under version control.

Source Code refers to all of the Python code, Django apps, and related static and configuration files that come into use with runserver.

Django Project Structure

We've used this structure on countless projects. It has been refined over more than a decade of constant Django development.

Starting Point

The most basic structure includes a source/ directory in which the Django project is defined as main/.

This may be accomplished with the following commands:

mkdir -p project_name/source;
cd project_name/source;
django-admin startproject main .;

Our starting structure is therefore:

project_name
`-- source
    |-- main
    |   |-- __init__.py
    |   |-- settings.py
    |   |-- urls.py
    |   `-- wsgi.py
    `-- manage.py

Homing Apps

We've found it beneficial to separate Django apps into three broad classes:

  1. Contributing functionality. Located in contrib/, these apps (or libraries) provide non-specific resources that may be used across all other areas of the project. Contrib apps may be good candidates for eventual re-factoring into stand-alone apps that may be installed via pip.
  2. Local functionality. Local apps provide functionality that is specific or core to the purpose of the project.
  3. Shared functionality. Shared apps (or libraries) provide specific resources across all other areas of the project. One example is commonly used lookup (validation) data. While a contrib app is completely generic, and a local app is completely specific, a shared app is somewhere in between.

Apps are therefore created like so:

./manage.py startapp contrib_app_name;
mv contrib_app_name contrib/app_name; 

./manage.py startapp local_app_name;
mv local_app_name local/app_name;

./manage.py startapp shared_app_name;
mv shared_app_name shared/app_name;

Note that the app's config will need to be updated, for example:

class DefaultConfig(AppConfig):
    name = "local.app_name"
    label = "app_name"
    verbose_name = _("App Name")

Our revised structure now looks like this:

project_name
`-- source
    |-- contrib
    |   `-- __init__.py
    |-- local
    |   `-- __init__.py
    |-- main
    |   |-- __init__.py
    |   |-- settings.py
    |   |-- urls.py
    |   `-- wsgi.py
    |-- manage.py
    `-- shared
        `-- __init__.py

The Glue App

The "glue" app may be used to tie various elements of the project together by customizing CMS or UI functionality, providing additional views, template overrides, and so on.

It's created with ./manage.py startapp glue.

Theme and Static Files

The static/ directory contains project-specific static files. The default structure includes:

  • css/custom.css: Custom styles for the project. Loaded last in the template so that the included styles can override any previous definition.
  • fonts/: Additional font files to be included.
  • images/: Images specific to the project; for example a logo.jpg or logo.png, and favicon.png.
  • js/custom.js: Custom JavaScript for the project.

The theme/ directory contains the main HTML (and static) files used to deliver the look and feel of the project. It may or may not be project specific, which is why static/ contains custom.css and (sometimes) custom.js.

Theming is a complicated subject and depends greatly on the nature of the user interface. For example, the theme for a SaaS application will be very different from a site that is using Django primarily as a CMS.

The theme/ directory includes its own static directory which follows the structure of static/ above. Our templates typically include:

  • 403.html: The permission denied template.
  • 404.html: The page not found template.
  • 500.html: The 500 error template.
  • 503.html: The 503 error template.
  • base.html: Is the base template for the entire project.
  • sidebar_left.html: Extends base to provide a left sidebar.
  • sidebar_right.html: Extends base to provide a right sidebar.
  • three_columns.html: Extends base to provide a three-column layout.

Additionally, we often break out the following sub-templates in the includes/ directory of the theme:

  • breadcrumbs.html: Renders site breadcrumbs.
  • footer.html: Renders the footer, including any footer menu, legal info, etc.
  • menu.html: Renders top-level menus.
  • messages.html: Renders messages or alerts to be displayed to the user.
  • navbar.html: Renders the branding and main navigation of the site.
  • submenu.html: Renders sub-menu elements.

Finally, we like to collect static files into a www/ directory.

So now our structure looks like this:

project_name
`-- source
    |-- contrib
    |   `-- __init__.py
    |-- glue
    |   `-- __init__.py
    |-- local
    |   `-- __init__.py
    |-- main
    |   |-- __init__.py
    |   |-- settings.py
    |   |-- urls.py
    |   `-- wsgi.py
    |-- manage.py
    |-- shared
    |   `-- __init__.py
    |-- static
    |   |-- css
    |   |   `-- custom.css
    |   |-- fonts
    |   |-- images
    |   `-- js
    |       `-- custom.js
    |-- theme
    |   |-- 403.html
    |   |-- 404.html
    |   |-- 500.html
    |   |-- 503.html
    |   |-- base.html
    |   |-- includes
    |   |   |-- breadcrumbs.html
    |   |   |-- footer.html
    |   |   |-- menu.html
    |   |   |-- messages.html
    |   |   |-- navbar.html
    |   |   `-- submenu.html
    |   |-- sidebar_left.html
    |   |-- sidebar_right.html
    |   |-- static
    |   |   |-- css
    |   |   |-- fonts
    |   |   |-- images
    |   |   `-- js
    |   `-- three_columns.html
    `-- www

Supporting Directories

A number of supporting directories may also be included:

  • The assets/ directory contains the source files for any static files included in the project.
  • The deploy/ directory contains instructions, scripts, and other resources used to deploy the project.
  • The docs/ directory contains source files and build scripts for generating project documentation.
  • In the development environment, the logs/ directory may be created in project root. It should be excluded from source code and deployments.
  • The provision/ directory contains instructions and (possibly) scripts that may be used to provision deployment resources for the project.
  • The tests/ directory contains unit tests for the project.

Now our structure looks like:

project_name
|-- assets
|-- deploy
|-- docs
|-- logs
|-- provision
|-- source
|   |-- contrib
|   |   `-- __init__.py
|   |-- glue
|   |   `-- __init__.py
|   |-- local
|   |   `-- __init__.py
|   |-- main
|   |   |-- __init__.py
|   |   |-- settings.py
|   |   |-- urls.py
|   |   `-- wsgi.py
|   |-- manage.py
|   |-- shared
|   |   `-- __init__.py
|   |-- static
|   |   |-- css
|   |   |   `-- custom.css
|   |   |-- fonts
|   |   |-- images
|   |   `-- js
|   |       `-- custom.js
|   |-- theme
|   |   |-- 403.html
|   |   |-- 404.html
|   |   |-- 500.html
|   |   |-- 503.html
|   |   |-- base.html
|   |   |-- includes
|   |   |   |-- breadcrumbs.html
|   |   |   |-- footer.html
|   |   |   |-- menu.html
|   |   |   |-- messages.html
|   |   |   |-- navbar.html
|   |   |   `-- submenu.html
|   |   |-- sidebar_left.html
|   |   |-- sidebar_right.html
|   |   |-- static
|   |   |   |-- css
|   |   |   |-- fonts
|   |   |   |-- images
|   |   |   `-- js
|   |   `-- three_columns.html
|   `-- www
`-- tests

Recommended Meta Files

The following meta files should generally always be included:

  • .gitignore: Controls what is included in source code management.
  • LICENSE.txt: The license for the project, even if just a simple copyright statement for proprietary or closed-source licensing.
  • README.markdown: Basic information and documentation regarding the project.
  • VERSION.txt: The version/release identifier for the project.

Suggested Meta Files

The following meta files are recommended:

  • .coveragerc: Coverage options for testing.
  • .status: The current development status of the project; suggested to be one of active, hold, unknown, maintenance, closed. This is useful (among other things) for scanning a directory of projects to determine the status of each.
  • DESCRIPTION.txt: A brief description of the project.
  • pytest.ini: Configuration options for testing with pytest. (See also the pytest-django plugin)
  • Makefile: Various build options codified in make format.
  • meta.ini: Meta data regarding the project.

The Final Structure

The final structure now looks something like this:

project_name
|-- assets
|-- deploy
|-- docs
|-- logs
|-- provision
|-- source
|   |-- contrib
|   |   `-- __init__.py
|   |-- glue
|   |   `-- __init__.py
|   |-- local
|   |   `-- __init__.py
|   |-- main
|   |   |-- __init__.py
|   |   |-- settings.py
|   |   |-- urls.py
|   |   `-- wsgi.py
|   |-- shared
|   |   `-- __init__.py
|   |-- static
|   |   |-- css
|   |   |   `-- custom.css
|   |   |-- fonts
|   |   |-- images
|   |   `-- js
|   |       `-- custom.js
|   |-- theme
|   |   |-- includes
|   |   |   |-- breadcrumbs.html
|   |   |   |-- footer.html
|   |   |   |-- menu.html
|   |   |   |-- messages.html
|   |   |   |-- navbar.html
|   |   |   `-- submenu.html
|   |   |-- static
|   |   |   |-- css
|   |   |   |-- fonts
|   |   |   |-- images
|   |   |   `-- js
|   |   |-- 403.html
|   |   |-- 404.html
|   |   |-- 500.html
|   |   |-- 503.html
|   |   |-- base.html
|   |   |-- sidebar_left.html
|   |   |-- sidebar_right.html
|   |   `-- three_columns.html
|   |-- www
|   `-- manage.py
|-- tests
|-- DESCRIPTION.txt
|-- LICENSE.txt
|-- Makefile
|-- README.markdown
|-- VERSION.txt
|-- meta.ini
`-- pytest.ini

And the final structure with some example apps:

project_name
|-- assets
|-- deploy
|-- docs
|-- logs
|-- provision
|-- source
|   |-- contrib
|   |   `-- __init__.py
|   |-- glue
|   |   |-- templates
|   |   |   `-- glue
|   |   |       `-- dashboard.html
|   |   |-- __init__.py
|   |   |-- ui.py
|   |   `-- views.py
|   |-- local
|   |   |-- projects
|   |   |   |-- migrations
|   |   |   |   `-- __init__.py
|   |   |   |-- __init__.py
|   |   |   |-- apps.py
|   |   |   |-- models.py
|   |   |   |-- ui.py
|   |   |   `-- views.py
|   |   |-- todos
|   |   |   |-- migrations
|   |   |   |   `-- __init__.py
|   |   |   |-- __init__.py
|   |   |   |-- models.py
|   |   |   |-- ui.py
|   |   |   `-- views.py
|   |   `-- __init__.py
|   |-- main
|   |   |-- __init__.py
|   |   |-- settings.py
|   |   |-- urls.py
|   |   `-- wsgi.py
|   |-- shared
|   |   |-- integrations
|   |   |   |-- migrations
|   |   |   |   `-- __init__.py
|   |   |   |-- __init__.py
|   |   |   |-- models.py
|   |   |   |-- ui.py
|   |   |   `-- views.py
|   |   |-- lookups
|   |   |   |-- migrations
|   |   |   |   `-- __init__.py
|   |   |   |-- __init__.py
|   |   |   |-- models.py
|   |   |   |-- ui.py
|   |   |   `-- views.py
|   |   `-- __init__.py
|   |-- static
|   |   |-- css
|   |   |   `-- custom.css
|   |   |-- fonts
|   |   |-- images
|   |   `-- js
|   |       `-- custom.js
|   |-- theme
|   |   |-- includes
|   |   |   |-- breadcrumbs.html
|   |   |   |-- footer.html
|   |   |   |-- menu.html
|   |   |   |-- messages.html
|   |   |   |-- navbar.html
|   |   |   `-- submenu.html
|   |   |-- static
|   |   |   |-- css
|   |   |   |-- fonts
|   |   |   |-- images
|   |   |   `-- js
|   |   |-- 403.html
|   |   |-- 404.html
|   |   |-- 500.html
|   |   |-- 503.html
|   |   |-- base.html
|   |   |-- sidebar_left.html
|   |   |-- sidebar_right.html
|   |   `-- three_columns.html
|   |-- www
|   `-- manage.py
|-- tests
|-- DESCRIPTION.txt
|-- LICENSE.txt
|-- Makefile
|-- README.markdown
|-- VERSION.txt
|-- meta.ini
`-- pytest.ini

And there you have it.



Posted in Django by Shawn Davis, May 15, 2020