Django's design philosophy shapes every aspect of the framework, creating a consistent and predictable development experience. Understanding these principles will help you write better Django code and make architectural decisions that align with the framework's strengths.
Every piece of knowledge should have a single, unambiguous representation within a system.
In Practice:
# Bad: Repeating validation logic
class UserForm(forms.Form):
email = forms.EmailField()
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError("Email already exists")
return email
class UserRegistrationForm(forms.Form):
email = forms.EmailField()
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists(): # Repeated logic
raise forms.ValidationError("Email already exists")
return email
# Good: Centralized validation
class UniqueEmailMixin:
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError("Email already exists")
return email
class UserForm(UniqueEmailMixin, forms.Form):
email = forms.EmailField()
class UserRegistrationForm(UniqueEmailMixin, forms.Form):
email = forms.EmailField()
Django favors explicit configuration over implicit conventions, making code more readable and maintainable.
Examples:
# Explicit URL patterns
urlpatterns = [
path('articles/<int:year>/<int:month>/', views.month_archive),
path('articles/<int:article_id>/', views.article_detail),
]
# Explicit model relationships
class Article(models.Model):
author = models.ForeignKey(
User,
on_delete=models.CASCADE, # Explicit deletion behavior
related_name='articles' # Explicit reverse relationship name
)
category = models.ForeignKey(
'Category', # Explicit string reference
on_delete=models.SET_NULL,
null=True,
blank=True
)
# Explicit template inheritance
{% extends 'base.html' %} {# Explicit parent template #}
{% load static %} {# Explicit template tag loading #}
Django's components should have minimal knowledge of each other, promoting reusability and maintainability.
Implementation:
# Loose coupling through signals
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
# Loose coupling through dependency injection
class EmailService:
def send_notification(self, user, message):
# Email sending logic
pass
class SMSService:
def send_notification(self, user, message):
# SMS sending logic
pass
class NotificationManager:
def __init__(self, service):
self.service = service # Injected dependency
def notify_user(self, user, message):
self.service.send_notification(user, message)
Django should require as little code as possible and make use of Python's dynamic capabilities.
Examples:
# Minimal model definition with maximum functionality
class BlogPost(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
class Meta:
ordering = ['-created_at']
# Automatic admin interface
admin.site.register(BlogPost)
# Generic views reduce boilerplate
class PostListView(ListView):
model = BlogPost
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
Django uses MVT, a variation of the Model-View-Controller (MVC) pattern:
# MODEL: Data layer and business logic
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
description = models.TextField()
in_stock = models.BooleanField(default=True)
def get_discounted_price(self, discount_percent):
"""Business logic in the model"""
return self.price * (1 - discount_percent / 100)
@property
def is_expensive(self):
return self.price > 100
# VIEW: Request processing and response logic
from django.shortcuts import render, get_object_or_404
from django.http import JsonResponse
def product_list(request):
"""Function-based view"""
products = Product.objects.filter(in_stock=True)
context = {'products': products}
return render(request, 'products/list.html', context)
class ProductDetailView(DetailView):
"""Class-based view"""
model = Product
template_name = 'products/detail.html'
context_object_name = 'product'
def product_api(request, product_id):
"""API view returning JSON"""
product = get_object_or_404(Product, id=product_id)
data = {
'name': product.name,
'price': str(product.price),
'in_stock': product.in_stock
}
return JsonResponse(data)
# TEMPLATE: Presentation layer
# products/list.html
{% extends 'base.html' %}
{% block content %}
<h1>Products</h1>
{% for product in products %}
<div class="product-card">
<h3>{{ product.name }}</h3>
<p>${{ product.price }}</p>
{% if product.is_expensive %}
<span class="badge">Premium</span>
{% endif %}
</div>
{% endfor %}
{% endblock %}
Understanding Django's request-response cycle is crucial for effective development:
# 1. URL RESOLUTION
# urls.py
urlpatterns = [
path('products/<int:pk>/', views.ProductDetailView.as_view(), name='product_detail'),
]
# 2. MIDDLEWARE PROCESSING (Request Phase)
class CustomMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Process request before view
request.custom_data = "processed"
response = self.get_response(request)
# Process response after view
response['X-Custom-Header'] = 'Django'
return response
# 3. VIEW PROCESSING
class ProductDetailView(DetailView):
model = Product
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['related_products'] = Product.objects.filter(
category=self.object.category
).exclude(pk=self.object.pk)[:3]
return context
# 4. TEMPLATE RENDERING
# The template system processes the template with context data
# 5. RESPONSE GENERATION
# Django creates an HttpResponse object
# 6. MIDDLEWARE PROCESSING (Response Phase)
# Middleware processes the response in reverse order
graph TD
A[Browser Request] --> B[Django Server]
B --> C[URL Resolver]
C --> D[Middleware Stack]
D --> E[View Function/Class]
E --> F[Model Layer]
F --> G[Database]
G --> F
F --> E
E --> H[Template Engine]
H --> I[Rendered HTML]
I --> D
D --> J[HTTP Response]
J --> A
Models define your data structure and encapsulate business logic:
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models.SlugField(unique=True)
description = models.TextField(blank=True)
class Meta:
verbose_name_plural = "categories"
ordering = ['name']
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('category_detail', kwargs={'slug': self.slug})
class Product(models.Model):
CONDITION_CHOICES = [
('new', 'New'),
('used', 'Used'),
('refurbished', 'Refurbished'),
]
name = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
condition = models.CharField(max_length=20, choices=CONDITION_CHOICES)
description = models.TextField()
image = models.ImageField(upload_to='products/', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
indexes = [
models.Index(fields=['category', 'price']),
models.Index(fields=['created_at']),
]
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('product_detail', kwargs={'slug': self.slug})
@property
def is_new(self):
return self.condition == 'new'
Views handle request processing and business logic:
from django.shortcuts import render, get_object_or_404
from django.views.generic import ListView, DetailView, CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
# Function-based view
def product_search(request):
query = request.GET.get('q')
products = []
if query:
products = Product.objects.filter(
Q(name__icontains=query) |
Q(description__icontains=query)
).select_related('category')
context = {
'products': products,
'query': query,
}
return render(request, 'products/search.html', context)
# Class-based views
class ProductListView(ListView):
model = Product
template_name = 'products/list.html'
context_object_name = 'products'
paginate_by = 12
def get_queryset(self):
queryset = Product.objects.select_related('category')
category_slug = self.kwargs.get('category_slug')
if category_slug:
queryset = queryset.filter(category__slug=category_slug)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
class ProductCreateView(LoginRequiredMixin, CreateView):
model = Product
fields = ['name', 'category', 'price', 'condition', 'description', 'image']
template_name = 'products/create.html'
def form_valid(self, form):
form.instance.seller = self.request.user
return super().form_valid(form)
Templates separate presentation from logic:
<!-- 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 Store{% endblock %}</title>
{% load static %}
<link rel="stylesheet" href="{% static 'css/main.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<nav class="navbar">
<a href="{% url 'home' %}" class="logo">My Store</a>
<ul class="nav-links">
<li><a href="{% url 'product_list' %}">Products</a></li>
{% if user.is_authenticated %}
<li><a href="{% url 'profile' %}">Profile</a></li>
<li><a href="{% url 'logout' %}">Logout</a></li>
{% else %}
<li><a href="{% url 'login' %}">Login</a></li>
{% endif %}
</ul>
</nav>
</header>
<main>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2024 My Store. All rights reserved.</p>
</footer>
<script src="{% static 'js/main.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
<!-- products/list.html -->
{% extends 'base.html' %}
{% load static %}
{% block title %}Products - {{ block.super }}{% endblock %}
{% block content %}
<div class="container">
<h1>Products</h1>
<div class="filters">
<h3>Categories</h3>
<ul>
<li><a href="{% url 'product_list' %}">All</a></li>
{% for category in categories %}
<li>
<a href="{% url 'category_products' category.slug %}">
{{ category.name }}
</a>
</li>
{% endfor %}
</ul>
</div>
<div class="products-grid">
{% for product in products %}
<div class="product-card">
{% if product.image %}
<img src="{{ product.image.url }}" alt="{{ product.name }}">
{% endif %}
<h3>
<a href="{{ product.get_absolute_url }}">
{{ product.name }}
</a>
</h3>
<p class="price">${{ product.price }}</p>
<p class="condition">{{ product.get_condition_display }}</p>
</div>
{% empty %}
<p>No products found.</p>
{% endfor %}
</div>
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="?page=1">« first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last »</a>
{% endif %}
</span>
</div>
{% endif %}
</div>
{% endblock %}
URL patterns map requests to views:
# project/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('products.urls')),
path('accounts/', include('django.contrib.auth.urls')),
path('api/', include('api.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# products/urls.py
from django.urls import path
from . import views
app_name = 'products'
urlpatterns = [
path('', views.ProductListView.as_view(), name='product_list'),
path('search/', views.product_search, name='product_search'),
path('create/', views.ProductCreateView.as_view(), name='product_create'),
path('category/<slug:category_slug>/', views.ProductListView.as_view(), name='category_products'),
path('<slug:slug>/', views.ProductDetailView.as_view(), name='product_detail'),
]
Keep business logic in models, not views:
# Good: Business logic in model
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
total = models.DecimalField(max_digits=10, decimal_places=2)
status = models.CharField(max_length=20, default='pending')
def calculate_total(self):
"""Business logic in model"""
return sum(item.subtotal for item in self.items.all())
def can_be_cancelled(self):
"""Business logic in model"""
return self.status in ['pending', 'confirmed']
def cancel(self):
"""Business logic in model"""
if self.can_be_cancelled():
self.status = 'cancelled'
self.save()
return True
return False
# Thin view
def cancel_order(request, order_id):
order = get_object_or_404(Order, id=order_id, user=request.user)
if order.cancel(): # Business logic delegated to model
messages.success(request, 'Order cancelled successfully.')
else:
messages.error(request, 'Order cannot be cancelled.')
return redirect('order_detail', order_id=order.id)
Encapsulate common queries:
class PublishedManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status='published')
class PostQuerySet(models.QuerySet):
def published(self):
return self.filter(status='published')
def by_author(self, author):
return self.filter(author=author)
def recent(self, days=7):
return self.filter(
created_at__gte=timezone.now() - timedelta(days=days)
)
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.CharField(max_length=20, default='draft')
created_at = models.DateTimeField(auto_now_add=True)
objects = models.Manager() # Default manager
published = PublishedManager() # Custom manager
# Custom QuerySet
objects = PostQuerySet.as_manager()
# Usage
recent_published_posts = Post.objects.published().recent(days=30)
author_posts = Post.objects.by_author(user).published()
This comprehensive understanding of Django's philosophy and core concepts provides the foundation for building robust, maintainable web applications that leverage the framework's full potential.
What is Django
Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers at the Lawrence Journal-World newspaper, it takes care of much of the hassle of web development, so you can focus on writing your app without needing to reinvent the wheel.
MVC vs MVT: Understanding Django's Architecture
Django implements the Model-View-Template (MVT) architectural pattern, which is a variation of the traditional Model-View-Controller (MVC) pattern. Understanding the differences and similarities between these patterns is crucial for effective Django development.