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.
The Model-View-Controller pattern separates application logic into three interconnected components:
Model (Data Layer)
View (Presentation Layer)
Controller (Logic Layer)
# Traditional MVC (conceptual example)
# Controller
class ProductController:
def list_products(self, request):
products = ProductModel.get_all() # Model interaction
return ProductView.render(products) # View rendering
# Model
class ProductModel:
@staticmethod
def get_all():
return database.query("SELECT * FROM products")
# View
class ProductView:
@staticmethod
def render(products):
return template.render("product_list.html", {"products": products})
Django adapts the MVC pattern into MVT, where the framework itself acts as the controller:
Model (Data Layer)
View (Logic Layer)
Template (Presentation Layer)
Django's URL dispatcher and framework core act as the controller:
| Component | Traditional MVC | Django MVT | Responsibility |
|---|---|---|---|
| Model | Model | Model | Data management, business logic |
| View | View | Template | User interface, presentation |
| Controller | Controller | View + Framework | Request processing, coordination |
| Routing | Part of Controller | URL Dispatcher | Request routing |
# MVC Structure
class BlogController:
def show_post(self, request, post_id):
post = BlogModel.get_post(post_id)
if not post:
return ErrorView.render_404()
return BlogView.render_post(post)
class BlogModel:
@staticmethod
def get_post(post_id):
return database.get("SELECT * FROM posts WHERE id = ?", post_id)
class BlogView:
@staticmethod
def render_post(post):
return render_template("post.html", post=post)
# Django MVT Structure
# Model (models.py)
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
def get_absolute_url(self):
return reverse('blog:post_detail', kwargs={'pk': self.pk})
# View (views.py) - Acts like MVC Controller
def post_detail(request, post_id):
post = get_object_or_404(BlogPost, pk=post_id) # Model interaction
context = {'post': post}
return render(request, 'blog/post_detail.html', context) # Template rendering
# Alternative class-based view
class PostDetailView(DetailView):
model = BlogPost
template_name = 'blog/post_detail.html'
context_object_name = 'post'
# URL Configuration (urls.py) - Framework Controller
urlpatterns = [
path('post/<int:post_id>/', views.post_detail, name='post_detail'),
path('post/<int:pk>/', views.PostDetailView.as_view(), name='post_detail_cbv'),
]
# Template (post_detail.html) - Acts like MVC View
"""
{% extends 'base.html' %}
{% block content %}
<article>
<h1>{{ post.title }}</h1>
<p class="meta">By {{ post.author }} on {{ post.created_at|date:"F d, Y" }}</p>
<div class="content">
{{ post.content|linebreaks }}
</div>
</article>
{% endblock %}
"""
1. User Request → Router
2. Router → Controller
3. Controller → Model (data retrieval)
4. Model → Controller (data returned)
5. Controller → View (data passed)
6. View → User (rendered response)
1. User Request → URL Dispatcher
2. URL Dispatcher → View (Django's "Controller")
3. View → Model (data retrieval)
4. Model → View (data returned)
5. View → Template (context passed)
6. Template → User (rendered HTML response)
# 1. URL Resolution
# urls.py
urlpatterns = [
path('products/<int:pk>/', views.ProductDetailView.as_view(), name='product_detail'),
]
# 2. Middleware Processing (Framework Controller)
class SecurityMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Process request
response = self.get_response(request)
# Process response
return response
# 3. View Processing (MVT View = MVC Controller)
class ProductDetailView(DetailView):
model = Product # Model interaction
template_name = 'products/detail.html' # Template specification
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. Model Layer (Same as MVC Model)
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
def get_discounted_price(self, discount_percent):
return self.price * (1 - discount_percent / 100)
# 5. Template Rendering (MVT Template = MVC View)
# products/detail.html
"""
{% extends 'base.html' %}
{% block content %}
<div class="product-detail">
<h1>{{ product.name }}</h1>
<p class="price">${{ product.price }}</p>
<h3>Related Products</h3>
{% for related in related_products %}
<div class="related-product">
<a href="{{ related.get_absolute_url }}">{{ related.name }}</a>
</div>
{% endfor %}
</div>
{% endblock %}
"""
Django handles the controller logic automatically:
Views can be functions or classes:
# Function-based view
def product_list(request):
products = Product.objects.all()
return render(request, 'products/list.html', {'products': products})
# Class-based view
class ProductListView(ListView):
model = Product
template_name = 'products/list.html'
context_object_name = 'products'
paginate_by = 10
Templates provide clean separation:
<!-- Template inheritance -->
{% extends 'base.html' %}
<!-- Template inclusion -->
{% include 'partials/product_card.html' %}
<!-- Template filters -->
{{ product.price|floatformat:2 }}
<!-- Template tags -->
{% for product in products %}
{% if product.is_available %}
<div>{{ product.name }}</div>
{% endif %}
{% endfor %}
MVT promotes reusability:
# Reusable model across apps
class TimeStampedModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
# Reusable view mixins
class LoginRequiredMixin:
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect('login')
return super().dispatch(request, *args, **kwargs)
# Reusable templates
{% extends 'base.html' %}
{% include 'partials/pagination.html' %}
Reality: Django's URL dispatcher and framework core act as controllers, plus views handle controller responsibilities.
Reality: Templates handle presentation logic but are more powerful than traditional views, with inheritance, inclusion, and custom tags.
Reality: MVT reflects Django's specific implementation where the framework handles controller concerns, allowing developers to focus on business logic.
# Good: Focused view
def create_order(request):
if request.method == 'POST':
form = OrderForm(request.POST)
if form.is_valid():
order = form.save(commit=False)
order.user = request.user
order.save()
return redirect('order_success')
else:
form = OrderForm()
return render(request, 'orders/create.html', {'form': form})
# Better: Use class-based view
class OrderCreateView(LoginRequiredMixin, CreateView):
model = Order
form_class = OrderForm
template_name = 'orders/create.html'
success_url = reverse_lazy('order_success')
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
# 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):
return sum(item.subtotal for item in self.items.all())
def can_be_cancelled(self):
return self.status in ['pending', 'confirmed']
def cancel(self):
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():
messages.success(request, 'Order cancelled successfully.')
else:
messages.error(request, 'Order cannot be cancelled.')
return redirect('order_detail', order_id=order.id)
<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Site{% endblock %}</title>
{% block extra_css %}{% endblock %}
</head>
<body>
{% include 'partials/header.html' %}
<main>
{% block content %}{% endblock %}
</main>
{% include 'partials/footer.html' %}
{% block extra_js %}{% endblock %}
</body>
</html>
<!-- products/list.html -->
{% extends 'base.html' %}
{% block title %}Products - {{ block.super }}{% endblock %}
{% block content %}
{% for product in products %}
{% include 'partials/product_card.html' %}
{% endfor %}
{% endblock %}
Understanding Django's MVT pattern and how it relates to traditional MVC helps you write better Django applications by leveraging the framework's strengths while maintaining clean, maintainable code architecture.
Key Concepts and Philosophy
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.
Project Structure Overview
Understanding Django's project structure is crucial for effective development. Django organizes code into projects and apps, each serving specific purposes in your web application architecture.