Django Project Structure Best Practices

Django Project Structure Best Practices

Whether you're just starting with Django or you've been using it for a while, getting your project structure right is crucial. A well-organized project makes your code more maintainable, scalable, and easier for other developers to understand. Let's dive into the best practices that will help you structure your Django projects like a pro.

The Standard Django Project Layout

When you create a new Django project using django-admin startproject myproject, you get a basic structure that looks like this:

myproject/
    manage.py
    myproject/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py

This is a good starting point, but for any non-trivial application, you'll want to expand on this structure. The key is to separate your project configuration from your actual applications.

Best practice: Think of your project as a container for apps, and each app should handle a specific piece of functionality.

Organizing Your Applications

Django encourages you to build your project as a collection of reusable apps. Each app should focus on doing one thing well. Here's how I like to structure my projects:

myproject/
    manage.py
    config/
        __init__.py
        settings/
            __init__.py
            base.py
            development.py
            production.py
            testing.py
        urls.py
        wsgi.py
        asgi.py
    apps/
        __init__.py
        core/
            __init__.py
            models.py
            views.py
            admin.py
            apps.py
            tests/
                __init__.py
                test_models.py
                test_views.py
            migrations/
                __init__.py
        users/
            __init__.py
            models.py
            views.py
            # ... other app files
    static/
        css/
        js/
        images/
    templates/
        base.html
        core/
            index.html
        users/
            login.html
            profile.html
    media/
    requirements/
        base.txt
        development.txt
        production.txt
    docs/
    .env
    .gitignore

This structure keeps everything organized and makes it clear where to find specific components.

Settings Management

One of the most important improvements you can make is splitting your settings into multiple files. This allows you to have different configurations for development, testing, and production environments.

Create a settings directory and move your original settings.py into it, then create separate files:

# config/settings/base.py
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent.parent

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'apps.core',
    'apps.users',
]

# ... rest of your base settings

# config/settings/development.py
from .base import *

DEBUG = True
SECRET_KEY = 'your-development-secret-key'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

Then update your manage.py and wsgi.py to point to the appropriate settings:

# manage.py
#!/usr/bin/env python
import os
import sys

if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.development')
    # ... rest of the file

This approach gives you much better control over your environment-specific configurations and keeps sensitive production settings separate.

Application Structure Within Apps

Each app within your project should also follow a consistent structure. Here's what a well-organized app looks like:

apps/users/
    __init__.py
    admin.py
    apps.py
    models.py
    views.py
    urls.py
    forms.py
    managers.py
    signals.py
    tests/
        __init__.py
        test_models.py
        test_views.py
        test_forms.py
    migrations/
        __init__.py
    templates/
        users/
            login.html
            profile.html
            register.html
    static/
        users/
            css/
            js/

Notice how each app has its own templates and static files organized in subdirectories with the app name. This prevents naming conflicts and makes it clear which app a template belongs to.

File/Folder Purpose Importance
tests/ Contains all test files High - Ensures code quality
templates/app_name/ App-specific templates High - Prevents naming conflicts
static/app_name/ App-specific static files High - Organized asset management
migrations/ Database migration files Critical - Never delete manually

URL Configuration

Instead of putting all your URLs in the main urls.py file, let each app manage its own URLs:

# config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('apps.core.urls')),
    path('users/', include('apps.users.urls')),
]

# apps/core/urls.py
from django.urls import path
from . import views

app_name = 'core'

urlpatterns = [
    path('', views.home, name='home'),
    path('about/', views.about, name='about'),
]

# apps/users/urls.py
from django.urls import path
from . import views

app_name = 'users'

urlpatterns = [
    path('login/', views.login_view, name='login'),
    path('logout/', views.logout_view, name='logout'),
    path('profile/', views.profile, name='profile'),
]

This approach makes your URL configuration much more maintainable as your project grows.

Template Organization

When it comes to templates, I recommend using a base template that other templates extend:

<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}My Project{% endblock %}</title>
    {% block extra_head %}{% endblock %}
</head>
<body>
    <header>
        <!-- Navigation here -->
    </header>

    <main>
        {% block content %}
        {% endblock %}
    </main>

    <footer>
        <!-- Footer content here -->
    </footer>

    {% block extra_js %}{% endblock %}
</body>
</html>

<!-- templates/core/index.html -->
{% extends "base.html" %}

{% block title %}Home - My Project{% endblock %}

{% block content %}
<h1>Welcome to our site!</h1>
<p>This is the home page content.</p>
{% endblock %}

Using template inheritance like this prevents code duplication and makes your templates much easier to maintain.

Static Files Management

For static files, Django's built-in system works well, but you need to organize them properly:

# In your settings
STATIC_URL = '/static/'
STATICFILES_DIRS = [
    BASE_DIR / "static",
]
STATIC_ROOT = BASE_DIR / "staticfiles"

# For production, you'll also want:
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / "media"

Organize your static files by app to avoid conflicts:

static/
    core/
        css/
            style.css
        js/
            main.js
        images/
            logo.png
    users/
        css/
            user.css
        js/
            user.js

When you need to reference static files in templates, use the {% static %} tag:

{% load static %}
<link rel="stylesheet" href="{% static 'core/css/style.css' %}">
<img src="{% static 'core/images/logo.png' %}" alt="Logo">

Environment Variables and Secrets

Never hardcode sensitive information like API keys, database passwords, or secret keys. Use environment variables:

# config/settings/base.py
import os
from django.core.exceptions import ImproperlyConfigured

def get_env_variable(var_name, default=None):
    try:
        return os.environ[var_name]
    except KeyError:
        if default is not None:
            return default
        error_msg = f"Set the {var_name} environment variable"
        raise ImproperlyConfigured(error_msg)

SECRET_KEY = get_env_variable('DJANGO_SECRET_KEY')
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': get_env_variable('DB_NAME'),
        'USER': get_env_variable('DB_USER'),
        'PASSWORD': get_env_variable('DB_PASSWORD'),
        'HOST': get_env_variable('DB_HOST', 'localhost'),
        'PORT': get_env_variable('DB_PORT', '5432'),
    }
}

Then create a .env file (add it to .gitignore!) for development:

DJANGO_SECRET_KEY=your-super-secret-key-here
DB_NAME=myproject_dev
DB_USER=myproject_user
DB_PASSWORD=secure_password
DB_HOST=localhost
DB_PORT=5432
DEBUG=True

Use python-decouple or django-environ packages to make this even easier.

Testing Structure

A good test structure is essential for maintaining code quality. Organize your tests to mirror your app structure:

apps/users/tests/
    __init__.py
    test_models.py
    test_views.py
    test_forms.py
    test_managers.py
    factories.py

Write tests for each component:

# apps/users/tests/test_models.py
from django.test import TestCase
from django.contrib.auth import get_user_model
from ..models import UserProfile

User = get_user_model()

class UserModelTest(TestCase):
    def test_create_user(self):
        user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        self.assertEqual(user.username, 'testuser')
        self.assertEqual(user.email, 'test@example.com')
        self.assertTrue(user.check_password('testpass123'))

Regular testing saves you from countless headaches down the road and makes refactoring much safer.

Managing Requirements

Instead of a single requirements.txt file, use multiple files for different environments:

requirements/
    base.txt
    development.txt
    production.txt
    testing.txt

Your base.txt contains packages needed in all environments:

Django==4.2.7
psycopg2-binary==2.9.9
Pillow==10.0.1

development.txt includes base plus development tools:

-r base.txt
ipython==8.17.2
django-debug-toolbar==4.2.0
django-extensions==3.2.3

Then install with: pip install -r requirements/development.txt

Deployment Considerations

When structuring your project, keep deployment in mind. Here are some files you might need:

myproject/
    ...
    Dockerfile
    docker-compose.yml
    .dockerignore
    nginx/
        nginx.conf
    scripts/
        deploy.sh
        backup.sh
    .github/
        workflows/
            ci.yml

Thinking about deployment from the beginning will save you from major restructuring later.

Common Pitfalls to Avoid

  • Don't put all your code in one giant app - break it into logical components
  • Avoid putting business logic in views - use services or model methods
  • Don't hardcode settings - use environment variables
  • Avoid putting all templates in one flat directory - use app-specific subdirectories
  • Don't neglect tests - they're your safety net

Remember that the perfect structure doesn't exist - it depends on your specific project needs. Start with these best practices and adapt them as your project grows. The most important thing is consistency - whatever structure you choose, stick with it throughout your project.

What Django project structure tips have worked well for you? I'd love to hear about your experiences and any additional best practices you've discovered in your Django journey!