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.
The recommended layout
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:
- App-level:
catalog/templates/catalog/product_list.html - 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.