deployment

Django Project Structure

How to organize a Django project for maintainability and scale. Covers app layout, settings organization, template structure, static files, tests, and the patterns that keep large projects navigable.

⏱ 13 min read intermediate
Project directory tree diagram showing a well-organized Django project

A Django project’s directory structure determines how easily the team can navigate, extend, and maintain the codebase six months from now. The default startproject output is fine for a tutorial but falls short for production applications. This guide covers the project layout patterns I use for Django projects that need to last: where to put apps, how to organize settings, where templates and static files live, how to structure tests, and the conventions that prevent a growing codebase from becoming a maze. For a broader view of the framework, see our Framework overview.

The key principle is predictability. When a developer opens the project for the first time, they should be able to find any component within seconds. Good structure is not clever. It is boringly consistent.

myproject/
  .venv/                    # Local, gitignored
  .python-version           # Python version for pyenv
  requirements/
    base.txt
    dev.txt
    production.txt
  src/
    manage.py
    myproject/
      __init__.py
      settings/
        __init__.py
        base.py
        dev.py
        production.py
        test.py
      urls.py
      wsgi.py
      asgi.py
    accounts/               # Custom user model and auth
      __init__.py
      models.py
      views.py
      urls.py
      admin.py
      forms.py
      tests/
        __init__.py
        test_models.py
        test_views.py
      templates/
        accounts/
          login.html
          profile.html
    catalog/                # Example domain app
      __init__.py
      models.py
      views.py
      urls.py
      admin.py
      forms.py
      services.py
      selectors.py
      tests/
      templates/
        catalog/
      static/
        catalog/
    templates/
      base.html
      404.html
      500.html
    static/
      css/
      js/
      img/
  docker/
    Dockerfile
    docker-compose.yml
  docs/
  scripts/
  .gitignore
  README.md
  pyproject.toml

Project root versus source root

Place manage.py inside a src/ directory. This keeps the project root clean for configuration files, documentation, and tooling. The alternative is having manage.py at the top level alongside README.md, docker-compose.yml, and a dozen config files. The src/ convention creates a clear boundary.

If you use src/, update manage.py and wsgi.py to reference the correct settings path, and set your IDE’s source root accordingly.

App design principles

Each Django app should represent a distinct domain concept. Good app boundaries follow these rules:

  • Single responsibility: an app does one thing well
  • Self-contained: models, views, templates, and tests live together
  • Describable: you can explain the app’s purpose in one sentence
  • Decoupled: apps communicate through well-defined interfaces, not by importing each other’s models freely

Bad app names: utils, common, misc, helpers. These become dumping grounds for everything that does not fit elsewhere. If a piece of code does not belong in an existing app, either it deserves its own app or the existing app boundaries need rethinking.

Settings organization

Split settings into a package with environment-specific modules:

# settings/base.py - Everything shared
INSTALLED_APPS = [...]
MIDDLEWARE = [...]
TEMPLATES = [...]
AUTH_USER_MODEL = 'accounts.User'

# settings/dev.py - Development overrides
from .base import *
DEBUG = True
DATABASES = {'default': {'ENGINE': '...sqlite3', 'NAME': BASE_DIR / 'db.sqlite3'}}

# settings/production.py - Production configuration
from .base import *
DEBUG = False
# ... environment-variable-driven settings

Set the active module through DJANGO_SETTINGS_MODULE. The production settings guide covers this pattern in full detail.

Template organization

Templates can live in two places:

  1. App-level: catalog/templates/catalog/product_list.html
  2. Project-level: src/templates/base.html

The app-level convention keeps templates co-located with the views that render them. The project-level directory holds base templates, error pages, and any templates shared across apps.

Always namespace app templates inside a subdirectory matching the app name. This prevents collisions when two apps have templates with the same filename.

TEMPLATES = [{
    'DIRS': [BASE_DIR / 'templates'],
    'APP_DIRS': True,
    # ...
}]

Static files layout

Follow the same namespacing pattern:

catalog/static/catalog/css/catalog.css
catalog/static/catalog/js/catalog.js

For project-wide static files (global CSS, shared JavaScript, fonts):

src/static/css/main.css
src/static/js/app.js
src/static/img/logo.png

Add the project-level directory to STATICFILES_DIRS:

STATICFILES_DIRS = [BASE_DIR / 'static']

The static files guide covers the full pipeline from development to production CDN delivery.

Test organization

For small apps, tests.py works. For apps with meaningful test suites, use a tests/ package:

catalog/
  tests/
    __init__.py
    test_models.py
    test_views.py
    test_forms.py
    test_services.py
    factories.py        # Factory Boy factories
    conftest.py         # Pytest fixtures

Keep test factories and fixtures alongside the tests they serve. This makes it easy to understand what test data each app needs. The testing strategy guide covers test patterns in more depth.

Services and selectors

For complex business logic, add a services.py module that contains functions or classes performing business operations:

# catalog/services.py
def create_order(user, cart_items):
    """Create an order from cart items, applying discounts and inventory checks."""
    ...

A selectors.py module handles complex query logic:

# catalog/selectors.py
def get_featured_products(category=None, limit=12):
    """Return featured products, optionally filtered by category."""
    qs = Product.objects.filter(is_featured=True).select_related('category')
    if category:
        qs = qs.filter(category=category)
    return qs[:limit]

This keeps views thin and business logic testable outside the request-response cycle.

URL organization

Each app owns its URLs:

# catalog/urls.py
from django.urls import path
from . import views

app_name = 'catalog'
urlpatterns = [
    path('', views.product_list, name='product_list'),
    path('<slug:slug>/', views.product_detail, name='product_detail'),
]

Include them in the root URL configuration:

# myproject/urls.py
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('products/', include('catalog.urls')),
    path('accounts/', include('accounts.urls')),
]

Using app_name enables namespaced URL reversal: reverse('catalog:product_detail', kwargs={'slug': 'widget'}).

Frequently asked questions

Should I use a mono-repo or separate repos for different services? For a single Django project, one repo is simpler. Separate repos make sense when you have genuinely independent services with different deployment cycles.

How do I handle shared code between apps? Create a dedicated app for shared functionality, like a core app with base models, middleware, or template tags. Keep it focused and avoid making it a catch-all.

When should I split a large app into smaller ones? When the app handles more than one distinct domain concept, when its models.py exceeds 500 lines, or when different team members consistently need to edit the same files for unrelated features.

Is there a standard Django project generator? cookiecutter-django is the most popular. It provides a comprehensive starting point with Docker, settings splitting, and common integrations. It is opinionated, so review its choices against your needs.