Django Framework Architecture

Module 23: Web Frameworks II (Python) - Wednesday, Lecture 1

Introduction to Django

Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. Created in 2003-2005 and named after the jazz guitarist Django Reinhardt, it's designed to help developers take applications from concept to completion as quickly as possible.

"Django was invented to meet fast-moving newsroom deadlines, while satisfying the tough requirements of experienced web developers."

— Django Software Foundation

Key characteristics of Django include:

The Orchestra Analogy

Think of Django as a well-organized orchestra where each section (component) has a specific role, all playing together under the guidance of a conductor (the framework). The brass section handles databases, the strings manage templates, the woodwinds take care of routing, and the percussion handles security. Just as an orchestra can play various styles of music, Django can power different types of web applications while maintaining harmony between components.

Django vs Other Web Frameworks

To understand Django's architectural choices, it's helpful to compare it with other web frameworks:

Framework Philosophy Architecture Strengths
Django "Batteries included" Full-stack, MVT Completeness, security, admin interface
Flask "Micro-framework" Minimalist core with extensions Simplicity, flexibility, learning curve
FastAPI "Modern, fast" API-focused, async Performance, type hints, async
Ruby on Rails "Convention over configuration" Full-stack, MVC Developer productivity, conventions
Express.js "Unopinionated, minimal" Web server framework for Node.js JavaScript ecosystem, simplicity

Django's architectural approach lies somewhere between the minimalist philosophy of Flask and the strict conventions of Ruby on Rails. It provides a comprehensive set of tools but allows flexibility in how you use them.

The Architecture of Django

Django follows a modified Model-View-Controller (MVC) pattern that's often referred to as Model-View-Template (MVT). In this architecture:

graph TD A[Client Browser] -->|HTTP Request| B[URLs/Routing] B -->|Routes to| C[View] C -->|Accesses| D[Model] D -->|Interacts with| E[(Database)] E -->|Returns data| D D -->|Returns data| C C -->|Renders| F[Template] F -->|HTML Response| A

Example: Processing a Blog Post Request

  1. A user visits example.com/blog/post/123/
  2. Django's URL dispatcher matches this URL to a view function
  3. The view function uses models to fetch post #123 from the database
  4. The view passes the post data to a template
  5. The template renders HTML with the post content
  6. Django returns the rendered HTML to the user's browser

Core Components of Django

Django is built around several key components that work together to form a complete web framework:

1. ORM (Object-Relational Mapper)

Django's ORM lets you interact with your database using Python objects instead of SQL:

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    bio = models.TextField()
    
    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    publication_date = models.DateField()
    isbn = models.CharField(max_length=13)
    
    def __str__(self):
        return self.title

2. URL Dispatcher

Maps URL patterns to views:

from django.urls import path
from . import views

urlpatterns = [
    path('books/', views.book_list, name='book_list'),
    path('books//', views.book_detail, name='book_detail'),
    path('authors/', views.author_list, name='author_list'),
    path('authors//', views.author_detail, name='author_detail'),
]

3. View System

Handles HTTP requests and returns responses:

from django.shortcuts import render, get_object_or_404
from .models import Book

def book_list(request):
    books = Book.objects.all().order_by('-publication_date')
    return render(request, 'books/book_list.html', {'books': books})

def book_detail(request, pk):
    book = get_object_or_404(Book, pk=pk)
    return render(request, 'books/book_detail.html', {'book': book})

4. Template Engine

Renders data into HTML using templates:

<!-- book_list.html -->
{% extends "base.html" %}

{% block content %}
    <h1>Book List</h1>
    
    <ul>
        {% for book in books %}
            <li>
                <a href="{% url 'book_detail' book.pk %}">
                    {{ book.title }} by {{ book.author.name }}
                </a>
            </li>
        {% empty %}
            <li>No books found.</li>
        {% endfor %}
    </ul>
{% endblock %}

5. Forms

Handles form rendering, validation, and processing:

from django import forms
from .models import Book

class BookForm(forms.ModelForm):
    class Meta:
        model = Book
        fields = ['title', 'author', 'publication_date', 'isbn']
        widgets = {
            'publication_date': forms.DateInput(attrs={'type': 'date'})
        }

6. Admin Interface

Provides a built-in admin panel for managing your site's data:

from django.contrib import admin
from .models import Author, Book

@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
    list_display = ('name',)
    search_fields = ('name',)

@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'publication_date')
    list_filter = ('publication_date', 'author')
    search_fields = ('title', 'author__name')

7. Authentication System

Handles user authentication, permissions, and sessions:

from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User

@login_required
def profile(request):
    return render(request, 'profile.html', {'user': request.user})

Project Structure

Django organizes code into projects and apps:

Project vs. Apps

A typical Django project structure looks like this:

myproject/                  # Project root directory
│
├── manage.py               # Command-line utility for administrative tasks
│
├── myproject/              # Project package (same name as root directory)
│   ├── __init__.py         # Makes the directory a Python package
│   ├── settings.py         # Project settings/configuration
│   ├── urls.py             # Project-level URL declarations
│   ├── asgi.py             # ASGI configuration for async servers
│   └── wsgi.py             # WSGI configuration for traditional servers
│
├── myapp/                  # A Django app
│   ├── __init__.py         # Makes the directory a Python package
│   ├── admin.py            # Admin site configurations
│   ├── apps.py             # App configuration
│   ├── migrations/         # Database migrations
│   │   └── __init__.py
│   ├── models.py           # Data models
│   ├── tests.py            # Test cases
│   ├── urls.py             # App-level URL declarations
│   └── views.py            # Views/controllers
│
└── templates/              # Templates directory (could be inside apps too)
    └── myapp/              # App-specific templates
        ├── base.html
        └── index.html

This modular structure allows for:

The Django Request-Response Cycle

When a request comes to a Django application, it goes through several stages:

graph TD A[Client Request] --> B[WSGI/ASGI Server] B --> C[Django Middleware] C --> D[URL Resolver] D --> E[View Function/Class] E --> F[Database Interaction] F --> G[Template Rendering] G --> H[Middleware] H --> I[Response] I --> J[Client]
  1. HTTP Request: The browser sends an HTTP request to the server
  2. WSGI/ASGI: The web server passes the request to Django via WSGI or ASGI
  3. Middleware: Request goes through middleware layers (security, sessions, etc.)
  4. URL Resolution: Django matches the URL to a view using url patterns
  5. View Processing: The view function/class processes the request
  6. Model Interaction: The view interacts with models if needed
  7. Template Rendering: The view renders data using a template
  8. Middleware (again): Response passes through middleware layers
  9. HTTP Response: Django sends the response back to the client

A Closer Look at Middleware

Middleware acts as a bridge between the web server and your Django application. Each middleware component can:

# Example of a simple middleware
class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        
    def __call__(self, request):
        # Code executed for each request before the view is called
        print(f"Processing request to {request.path}")
        
        # Call the next middleware or view
        response = self.get_response(request)
        
        # Code executed for each response after the view is called
        print(f"Returning response for {request.path}")
        
        return response

Django includes several built-in middleware components:

Django Settings and Configuration

Django's behavior is controlled by settings in the settings.py file. Here are some key settings:

Core Settings

# Database configuration
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydatabase',
        'USER': 'mydatabaseuser',
        'PASSWORD': 'mypassword',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

# Installed applications
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',
    'another_app',
]

# Middleware configuration
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# Template configuration
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Security Settings

# Secret key (keep this secret!)
SECRET_KEY = 'your-secret-key-here'

# Debug mode (turn off in production)
DEBUG = True

# Allowed hosts
ALLOWED_HOSTS = ['example.com', 'www.example.com']

# HTTPS settings
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000  # 1 year

Managing Different Environments

In real projects, you'll typically have different settings for development, testing, and production. A common approach is to use separate settings files:

# settings/
├── __init__.py
├── base.py         # Common settings
├── development.py  # Development-specific settings
├── testing.py      # Testing-specific settings
└── production.py   # Production-specific settings

Then, use an environment variable to select the appropriate settings:

# wsgi.py or manage.py
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings.production")

Settings Best Practices

  • Never commit sensitive settings (like SECRET_KEY) to version control
  • Use environment variables for sensitive or environment-specific settings
  • Always set DEBUG=False in production
  • Restrict ALLOWED_HOSTS in production
  • Use a settings structure that supports multiple environments

Django's Application Modularity

One of Django's key architectural strengths is its modularity through the apps system. A well-designed Django project consists of several specialized apps:

graph TD A[Django Project] --> B[User Management App] A --> C[Blog App] A --> D[E-commerce App] A --> E[API App] B --> B1[User Models] B --> B2[Authentication Views] B --> B3[Profile Management] C --> C1[Post Models] C --> C2[Comment System] C --> C3[Blog Views] D --> D1[Product Models] D --> D2[Cart System] D --> D3[Payment Integration] E --> E1[API Endpoints] E --> E2[Serializers] E --> E3[Authentication]

App Design Best Practices

Example: E-commerce Project Structure

ecommerce_project/
│
├── accounts/              # User accounts and profiles
│   ├── models.py          # User profiles, etc.
│   ├── views.py           # Login, registration, profile views
│   └── ...
│
├── products/              # Product catalog
│   ├── models.py          # Product, Category models
│   ├── views.py           # Product listing, detail views
│   └── ...
│
├── cart/                  # Shopping cart
│   ├── models.py          # Cart, CartItem models
│   ├── views.py           # Cart views
│   └── ...
│
├── orders/                # Order processing
│   ├── models.py          # Order, OrderItem models
│   ├── views.py           # Checkout, order history views
│   └── ...
│
├── payments/              # Payment processing
│   ├── models.py          # Payment models
│   ├── views.py           # Payment views
│   └── ...
│
└── api/                   # REST API
    ├── serializers.py     # Data serializers
    ├── views.py           # API endpoints
    └── ...

This structure separates concerns and allows different team members to work on different apps simultaneously.

Django Deployment Architecture

When deploying Django applications to production, multiple components work together:

graph LR A[Client] -->|HTTP Request| B[Web Server] B -->|Proxy| C[WSGI/ASGI Server] C -->|Interface| D[Django Application] D -->|Queries| E[(Database)] D -->|Caching| F[(Cache)] D -->|Static Files| G[Static File Storage] D -->|Media Files| H[Media File Storage]

WSGI vs. ASGI

Django traditionally uses WSGI (Web Server Gateway Interface) to communicate with web servers, but now also supports ASGI (Asynchronous Server Gateway Interface) for asynchronous operations:

WSGI ASGI
Synchronous requests only Supports both sync and async
Traditional, mature standard Newer standard, evolving
Used with Gunicorn, uWSGI Used with Uvicorn, Daphne
HTTP only HTTP, WebSockets, and more

Django 3.0+ projects include both wsgi.py and asgi.py files for compatibility with both deployment options.

Django's Growth and Adaptability

Django has evolved significantly since its creation, adapting to changing web development trends:

Modern Django Features

Django's Architectural Evolution

While maintaining its core architectural principles, Django has adapted to include:

Example: Async Views

Modern Django supports async views for handling long-running operations without blocking:

async def async_view(request):
    # This non-blocking operation runs while other requests are processed
    query_result = await database.fetch_all("SELECT * FROM some_table")
    
    # Process results
    processed_data = process_data(query_result)
    
    # Render template with results
    return render(request, 'results.html', {'data': processed_data})

Real-World Django: Success Stories

Django powers some of the most popular websites and applications in the world, demonstrating its scalability and versatility:

These examples show that Django's architecture can scale from small projects to enterprise-level applications handling millions of users and billions of interactions.

Django Architecture: Strengths and Considerations

Strengths

Considerations

When to Choose Django

Django is an excellent choice when:

  • Building content-driven websites
  • Creating applications with complex data relationships
  • Needing built-in admin capabilities
  • Security is a primary concern
  • Development speed is important
  • Building applications that may need to scale

Consider alternatives when:

  • Building microservices or very small applications
  • Creating real-time applications focused on WebSockets
  • Needing extreme customization of every component

Practice Activity

Let's explore Django's architecture by analyzing the components needed for a blog application:

  1. Identify the Django components needed for a blog, including:
    • Models (Post, Category, Comment, Author)
    • Views (list posts, show post, create/edit post)
    • Templates (base layout, post list, post detail)
    • URLs (routing patterns for all views)
    • Forms (post creation/editing, comments)
    • Admin interface configurations
  2. Draw a diagram (on paper or digitally) showing how these components interact during a request to view a blog post
  3. Sketch a project structure for a Django blog application, identifying:
    • Project-level files (settings.py, urls.py)
    • App-level files (models.py, views.py, etc.)
    • Template organization
    • Static files organization
  4. Consider how you would divide functionality into apps (e.g., blog, accounts, comments)
  5. Identify potential middleware that would be useful for a blog application

This exercise will help you understand how Django's components work together in a practical application.

Further Topics to Explore