Skip to main content

Django Project Structure

Introduction

When you create a new Django project, it sets up a specific directory structure with various files and folders. Understanding this structure is crucial for effective Django development as it follows the Model-View-Controller (MVC) pattern (though Django calls it Model-View-Template or MVT). This organized approach helps you maintain clean, reusable, and maintainable code.

In this tutorial, we'll explore the standard Django project structure, explain what each component does, and share best practices for organizing your Django applications.

Basic Project Structure

Let's start by creating a new Django project to see the default structure it generates. You'll need Django installed in your environment first:

bash
# Install Django if you haven't already
pip install django

# Create a new project
django-admin startproject myproject

After running this command, Django creates a directory structure that looks like this:

myproject/
├── manage.py
└── myproject/
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py

Understanding Each Component

Let's examine each file and understand its purpose:

manage.py

This is a command-line utility that allows you to interact with your Django project. You'll use it to run the server, create migrations, apply migrations, and more.

bash
# Run development server
python manage.py runserver

# Create migrations
python manage.py makemigrations

# Apply migrations
python manage.py migrate

myproject/ (Inner Directory)

This is the actual Python package for your project. Its name is what you'll use to import modules from it.

myproject/__init__.py

An empty file that tells Python this directory should be considered a Python package.

myproject/settings.py

This file contains all the configuration for your Django project. Here's where you specify:

  • Database configuration
  • Installed applications
  • Middleware
  • Static files settings
  • And much more
python
# Example settings.py snippet
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Your custom apps go here
]

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

# Static files settings
STATIC_URL = '/static/'

myproject/urls.py

This file contains URL declarations for your project - essentially your site's "table of contents."

python
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
# Add your app URLs here
# path('myapp/', include('myapp.urls')),
]

myproject/asgi.py

An entry-point for ASGI-compatible web servers to serve your project. ASGI (Asynchronous Server Gateway Interface) allows for asynchronous Django applications.

myproject/wsgi.py

An entry-point for WSGI-compatible web servers to serve your project. WSGI (Web Server Gateway Interface) is what most production deployments use.

Adding Applications

Django follows a "project and apps" structure. A project contains multiple apps, and each app serves a specific purpose in your project.

Let's add an app to our project:

bash
python manage.py startapp blog

This creates a new directory structure:

myproject/
├── blog/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── manage.py
└── myproject/
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py

Understanding App Components

Each app directory contains these key files:

admin.py

Where you register your models with the Django admin site.

python
from django.contrib import admin
from .models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'created_date', 'published_date')
list_filter = ('published_date', 'author')
search_fields = ('title', 'content')

apps.py

Contains configuration specific to this app.

python
from django.apps import AppConfig

class BlogConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'blog'

migrations/

Directory containing database migrations for this app.

models.py

Where you define your database models using Django's ORM.

python
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User

class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)

def publish(self):
self.published_date = timezone.now()
self.save()

def __str__(self):
return self.title

tests.py

Where you write tests for your app.

python
from django.test import TestCase
from django.contrib.auth.models import User
from .models import Post

class PostModelTests(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='testuser', password='password')

def test_create_post(self):
post = Post(title="Test Post", content="Test Content", author=self.user)
post.save()
self.assertEqual(post.title, "Test Post")
self.assertEqual(post.content, "Test Content")
self.assertEqual(post.author, self.user)

views.py

Contains the view functions or classes that process HTTP requests and return responses.

python
from django.shortcuts import render, get_object_or_404
from .models import Post

def post_list(request):
posts = Post.objects.filter(published_date__isnull=False).order_by('-published_date')
return render(request, 'blog/post_list.html', {'posts': posts})

def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
return render(request, 'blog/post_detail.html', {'post': post})

Enhanced Project Structure for Larger Applications

As your project grows, the basic structure might become inadequate. Here's a more advanced structure recommended for larger projects:

myproject/
├── apps/
│ ├── blog/
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── forms.py
│ │ ├── managers.py
│ │ ├── migrations/
│ │ ├── models.py
│ │ ├── services.py
│ │ ├── tests/
│ │ │ ├── __init__.py
│ │ │ ├── test_forms.py
│ │ │ ├── test_models.py
│ │ │ └── test_views.py
│ │ ├── urls.py
│ │ └── views.py
│ └── users/
│ ├── ...similar structure as blog
├── config/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── dev.py
│ │ └── prod.py
│ ├── urls.py
│ └── wsgi.py
├── media/
├── static/
│ ├── css/
│ ├── js/
│ └── images/
├── templates/
│ ├── base.html
│ ├── blog/
│ │ ├── post_detail.html
│ │ └── post_list.html
│ └── users/
│ ├── login.html
│ └── profile.html
├── manage.py
├── requirements/
│ ├── base.txt
│ ├── dev.txt
│ └── prod.txt
└── .env

Key Enhancements

  1. Separate apps directory: Keeps all your applications in one place.

  2. Config directory: Replaces the inner project directory and better communicates its purpose.

  3. Split settings: Divides settings into base, development, and production files.

    python
    # base.py - Settings common to all environments
    DEBUG = False
    # ... other common settings

    # dev.py
    from .base import *

    DEBUG = True
    # ... other development-specific settings

    # prod.py
    from .base import *

    DEBUG = False
    # ... other production-specific settings
  4. Dedicated templates directory: Keeps all templates in one place, organized by app.

  5. Static and media directories: Clear organization for static files and user-uploaded content.

  6. Requirements split: Separate requirement files for different environments.

Best Practices for Django Project Structure

  1. Follow Django's app-based architecture: Each app should do one thing and do it well.

  2. Keep apps small and focused: If an app grows too large, consider splitting it.

  3. Use meaningful app names: Names should clearly indicate the app's purpose.

  4. Create reusable apps: Design apps to be reusable across projects when possible.

  5. Organize templates and static files clearly: Use namespacing to avoid conflicts.

  6. Split settings by environment: Use different settings for development, testing, and production.

  7. Use environment variables for sensitive data: Never commit sensitive information like API keys or passwords.

  8. Follow URL namespacing conventions: Use app name as URL namespace.

Real-World Example: Blog Application

Let's look at a more detailed example of our blog app structure with all the files we might need:

blog/
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── managers.py
├── migrations/
├── models.py
├── services.py
├── templates/
│ └── blog/
│ ├── base.html
│ ├── post_confirm_delete.html
│ ├── post_detail.html
│ ├── post_form.html
│ └── post_list.html
├── tests/
│ ├── __init__.py
│ ├── test_forms.py
│ ├── test_models.py
│ └── test_views.py
├── urls.py
└── views.py

With these files:

forms.py:

python
from django import forms
from .models import Post

class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'content')

urls.py:

python
from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [
path('', views.post_list, name='post_list'),
path('post/<int:pk>/', views.post_detail, name='post_detail'),
path('post/new/', views.post_new, name='post_new'),
path('post/<int:pk>/edit/', views.post_edit, name='post_edit'),
path('post/<int:pk>/delete/', views.post_delete, name='post_delete'),
]

services.py (for business logic):

python
from django.utils import timezone
from .models import Post

def publish_post(post_id):
"""Service to publish a post"""
post = Post.objects.get(id=post_id)
post.published_date = timezone.now()
post.save()
return post

managers.py (for custom model managers):

python
from django.db import models

class PublishedPostManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(published_date__isnull=False)

Updating models.py to use the custom manager:

python
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from .managers import PublishedPostManager

class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)

# Managers
objects = models.Manager() # Default manager
published = PublishedPostManager() # Custom manager

def publish(self):
self.published_date = timezone.now()
self.save()

def __str__(self):
return self.title

Summary

Django's project structure is designed to promote clean, maintainable, and scalable code. By understanding each component's purpose and following best practices, you'll build applications that are easier to develop, test, and maintain.

Remember these key points:

  • Django follows a "project and apps" structure
  • Each app should have a single responsibility
  • As projects grow, organize them with separate directories for static files, media, templates, etc.
  • Split settings by environment
  • Keep sensitive data in environment variables
  • Test your code thoroughly

With this solid foundation in Django's project structure, you'll be well-prepared to build robust web applications.

Additional Resources

Exercises

  1. Create a new Django project and add two apps: a blog app and a users app.
  2. Restructure an existing Django project to follow the enhanced project structure described in this tutorial.
  3. Create a custom settings structure with base, development, and production settings.
  4. Set up a project that uses environment variables for database credentials and secret key.
  5. Create an app with models, views, forms, and tests following the structure outlined in this tutorial.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)