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/
.
Note
Why source? It makes the location of source code (defined above) obvious.
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
Note
Why main? As a convention of Python, the word main indicates that this is the "main" thing; that is, the Django project settings where everything starts. Keeping the Django project files separate makes it clear where configuration and URLs are to be defined.
Homing Apps
We've found it beneficial to separate Django apps into three broad classes:
- 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. - Local functionality. Local apps provide functionality that is specific or core to the purpose of the project.
- 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.
Note
local/ is the only directory we create for every project. Large projects often incoprorate shared/ and sometimes contrib/. Why bother with the separation? Creating contrib apps keeps the project moving without refactoring -- this can be done later. Shared functionality is anything that is re-used across local apps where it makes more sense to keep it separate rather than located within a local (and more specific) app, e.g. lookup models, configuration models, integrations, and so on.
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
.
Note
We first started using the glue app when building web sites with Wagtail and then also adopted this approach for creating menus with SuperDjango UI. It is especially useful for Wagtail hooks, defining dashboard views, menus, and overriding templates. These days, we scarcely have a project that does not utilize a glue app. Why "glue"? Well, because it glues various resources together.
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 alogo.jpg
orlogo.png
, andfavicon.png
.js/custom.js
: Custom JavaScript for the project.
Note
Separating project static files from theme static files (below) makes it much more obvious where and how to locate and edit files. The static files are "above" the theme files so they may always be thought of as overriding the theme's files.
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.
Note
The word "theme" always indicates a collection of templates and static files. Your templates and includes may differ, but "base.html" is always recommended.
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.
Note
Supporting directories are not source code, so they are located in project root.
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.
Note
Why files with extensions? A lot of projects (including third-party apps and libraries) simply have a LICENSE or VERSION file (for example). Adding the extensions makes the file type obvious.
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 ofactive
,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.
Note
We use the .status and meta.ini files in conjunction with command line tools for managing projects.
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.