Chapter 2: Django MTV Architecture
2.1 Django MTV Architecture Pattern
What is MTV Architecture
Django uses MTV (Model-Template-View) architecture pattern, which is a variant of traditional MVC (Model-View-Controller) pattern. MTV pattern divides application into three main components:
- Model: Responsible for data storage and business logic
- Template: Responsible for data display and user interface
- View: Responsible for handling user requests and business logic control
This article just understands concepts, we will explain each component application in detail later.
MTV vs MVC Comparison
| Component | Django MTV | Traditional MVC | Description |
|---|---|---|---|
| Data layer | Model | Model | Data model and business logic |
| Presentation layer | Template | View | User interface and data display |
| Control layer | View | Controller | Request handling and logic control |
Django's "View" is more like "Controller" in traditional MVC, while "Template" corresponds to "View" in traditional MVC. Use fresh fish to dried fish making process analogy to explain, Model is fish, View is fish to dried fish processing process, Template is dried fish packaging. Model is business essence, determines what business data system produces.
MTV Collaboration Process
Let's understand how MTV collaborates through a complete request-response process:
1. User accesses article list page
User request: GET /articles/
↓
URL routing: urls.py matches path
↓
View processing: article_list function in views.py
↓
Model query: Article.objects.filter() in models.py
↓
Template rendering: templates/blog/article_list.html
↓
HTTP response: Returns rendered HTML page2. Complete URL configuration
# urls.py
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
# Function views
path('', views.article_list, name='article_list'),
path('article/<slug:slug>/', views.article_detail, name='article_detail'),
path('category/<slug:slug>/', views.category_detail, name='category_detail'),
# Class views
# path('', views.ArticleListView.as_view(), name='article_list'),
# path('article/<slug:slug>/', views.ArticleDetailView.as_view(), name='article_detail'),
]3. Data flow diagram
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Browser │ │ Django │ │ Database │
│ │ │ │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ 1. HTTP Request │ │
├──────────────────►│ │
│ │ 2. Query Data │
│ ├──────────────────►│
│ │ 3. Return Data │
│ │◄──────────────────┤
│ │ 4. Render Template│
│ │ │
│ 5. HTTP Response │ │
│◄──────────────────┤ │Model Detailed Explanation
Model is the single, definitive source of information about data. It contains basic fields and behaviors for storing data.
Basic model example
# models.py
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Category(models.Model):
"""Article category model"""
name = models.CharField(max_length=100, verbose_name="Category name")
slug = models.SlugField(unique=True, verbose_name="URL alias")
description = models.TextField(blank=True, verbose_name="Category description")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created at")
class Meta:
verbose_name = "Article category"
verbose_name_plural = "Article categories"
ordering = ['name']
def __str__(self):
return self.name
class Article(models.Model):
"""Article model"""
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
('archived', 'Archived'),
]
title = models.CharField(max_length=200, verbose_name="Title")
slug = models.SlugField(unique_for_date='publish_date', verbose_name="URL alias")
content = models.TextField(verbose_name="Content")
# Relationship fields
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Author")
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, verbose_name="Category")
# Status and time fields
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft', verbose_name="Status")
publish_date = models.DateTimeField(default=timezone.now, verbose_name="Publish date")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created at")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Updated at")
# Statistics fields
view_count = models.PositiveIntegerField(default=0, verbose_name="View count")
class Meta:
verbose_name = "Article"
verbose_name_plural = "Articles"
ordering = ['-publish_date']
indexes = [
models.Index(fields=['status', 'publish_date']),
models.Index(fields=['category', 'status']),
]
def __str__(self):
return self.title
def get_absolute_url(self):
"""Get article detail page URL"""
from django.urls import reverse
return reverse('article_detail', kwargs={'slug': self.slug})
def is_published(self):
"""Check if article is published"""
return self.status == 'published' and self.publish_date <= timezone.now()Model key features
- Field types: CharField, TextField, DateTimeField, etc.
- Field options: max_length, blank, null, default, etc.
- Relationship fields: ForeignKey, ManyToManyField, OneToOneField
- Meta class: Defines model metadata
- Model methods: Custom business logic methods
View Detailed Explanation
View receives web requests and returns web responses. It contains business logic for processing requests.
Function view example
# views.py
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, Http404
from django.core.paginator import Paginator
from django.db.models import Q, F
from django.utils import timezone
from .models import Article, Category
def article_list(request):
"""Article list view"""
# Get query parameters
search_query = request.GET.get('search', '')
category_slug = request.GET.get('category', '')
# Base queryset
articles = Article.objects.filter(
status='published',
publish_date__lte=timezone.now()
).select_related('author', 'category')
# Search filter
if search_query:
articles = articles.filter(
Q(title__icontains=search_query) |
Q(content__icontains=search_query)
)
# Category filter
if category_slug:
articles = articles.filter(category__slug=category_slug)
# Pagination
paginator = Paginator(articles, 10) # 10 articles per page
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
# Get all categories
categories = Category.objects.all()
context = {
'articles': page_obj,
'categories': categories,
'search_query': search_query,
'category_slug': category_slug,
'is_paginated': page_obj.has_other_pages(),
'page_obj': page_obj,
}
return render(request, 'blog/article_list.html', context)
def article_detail(request, slug):
"""Article detail view"""
article = get_object_or_404(
Article,
slug=slug,
status='published',
publish_date__lte=timezone.now()
)
# Increase view count
Article.objects.filter(pk=article.pk).update(view_count=F('view_count') + 1)
# Get related articles
related_articles = Article.objects.filter(
category=article.category,
status='published'
).exclude(pk=article.pk)[:5]
context = {
'article': article,
'related_articles': related_articles,
}
return render(request, 'blog/article_detail.html', context)
def category_detail(request, slug):
"""Category detail view"""
category = get_object_or_404(Category, slug=slug)
articles = Article.objects.filter(
category=category,
status='published',
publish_date__lte=timezone.now()
).select_related('author')
# Pagination
paginator = Paginator(articles, 10)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
context = {
'category': category,
'articles': page_obj,
'is_paginated': page_obj.has_other_pages(),
'page_obj': page_obj,
}
return render(request, 'blog/category_detail.html', context)Class view example
# views.py (class view version)
from django.views.generic import ListView, DetailView
from django.db.models import Q, F
from django.utils import timezone
class ArticleListView(ListView):
"""Article list class view"""
model = Article
template_name = 'blog/article_list.html'
context_object_name = 'articles'
paginate_by = 10
def get_queryset(self):
queryset = Article.objects.filter(
status='published',
publish_date__lte=timezone.now()
).select_related('author', 'category')
# Search function
search_query = self.request.GET.get('search')
if search_query:
queryset = queryset.filter(
Q(title__icontains=search_query) |
Q(content__icontains=search_query)
)
# Category filter
category_slug = self.request.GET.get('category')
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()
context['search_query'] = self.request.GET.get('search', '')
context['category_slug'] = self.request.GET.get('category', '')
return context
class ArticleDetailView(DetailView):
"""Article detail class view"""
model = Article
template_name = 'blog/article_detail.html'
context_object_name = 'article'
slug_field = 'slug'
slug_url_kwarg = 'slug'
def get_queryset(self):
return Article.objects.filter(
status='published',
publish_date__lte=timezone.now()
)
def get_object(self, queryset=None):
obj = super().get_object(queryset)
# Increase view count
Article.objects.filter(pk=obj.pk).update(view_count=F('view_count') + 1)
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Get related articles
context['related_articles'] = Article.objects.filter(
category=self.object.category,
status='published'
).exclude(pk=self.object.pk)[:5]
return contextAccess URL examples
http://127.0.0.1:8000/articles/ - All articles
http://127.0.0.1:8000/articles/?search=django - Search "django"
http://127.0.0.1:8000/articles/?category=python - Python category articles
http://127.0.0.1:8000/articles/?page=2 - Page 2Template Detailed Explanation
Template is responsible for defining how data is presented to users. Django uses its own template language (DTL).
Basic template example
<!-- 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 Blog{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'home' %}">My Blog</a>
<div class="navbar-nav">
<a class="nav-link" href="{% url 'article_list' %}">Articles</a>
<a class="nav-link" href="{% url 'category_list' %}">Categories</a>
</div>
</div>
</nav>
<main class="container mt-4">
{% block content %}
{% endblock %}
</main>
<footer class="bg-light text-center py-3 mt-5">
<p>© 2025 My Blog. All rights reserved.</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html><!-- templates/blog/article_list.html -->
{% extends 'base.html' %}
{% load static %}
{% block title %}Article List - {{ block.super }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8">
<h2>Latest Articles</h2>
{% for article in articles %}
<article class="card mb-4">
<div class="card-body">
<h3 class="card-title">
<a href="{{ article.get_absolute_url }}" class="text-decoration-none">
{{ article.title }}
</a>
</h3>
<p class="card-text text-muted">
<small>
By {{ article.author.username }} on {{ article.publish_date|date:"Y-m-d" }}
| Category: {{ article.category.name }}
| Views: {{ article.view_count }} times
</small>
</p>
<p class="card-text">{{ article.excerpt|default:article.content|truncatewords:30 }}</p>
<a href="{{ article.get_absolute_url }}" class="btn btn-primary">Read more</a>
</div>
</article>
{% empty %}
<div class="alert alert-info">
<p>No articles published yet.</p>
</div>
{% endfor %}
<!-- Pagination -->
{% if is_paginated %}
<nav aria-label="Article pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5>Article Categories</h5>
</div>
<div class="card-body">
{% for category in categories %}
<a href="{% url 'category_detail' category.slug %}" class="badge bg-secondary text-decoration-none me-2 mb-2">
{{ category.name }}
</a>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}article_list usage in template
<!-- Generate article list links -->
<a href="{% url 'article_list' %}">All articles</a>
<!-- Links with parameters -->
<a href="{% url 'article_list' %}?search=django&category=python">
Search Django related Python articles
</a>
<!-- Pagination links -->
<a href="{% url 'article_list' %}?page=2">Next page</a>Template language features
- Variables:
- Tags:
{% tag %} - Filters:
0 - Template inheritance:
{% extends %}and{% block %} - Template inclusion:
{% include %}
MTV Best Practices
1. Model design principles
# Good practice
class Article(models.Model):
# Use descriptive field names
title = models.CharField(max_length=200, verbose_name="Title")
# Add appropriate constraints
slug = models.SlugField(unique_for_date='publish_date')
# Use suitable field types
status = models.CharField(max_length=20, choices=STATUS_CHOICES)
# Add indexes to improve query performance
class Meta:
indexes = [
models.Index(fields=['status', 'publish_date']),
]
# Provide useful methods
def get_absolute_url(self):
return reverse('article_detail', kwargs={'slug': self.slug})2. View design principles
# Keep views concise
def article_list(request):
# Single responsibility: only handle list display
articles = Article.objects.published() # Use model manager
return render(request, 'blog/article_list.html', {'articles': articles})
# Use class views for complex logic
class ArticleCreateView(CreateView):
model = Article
form_class = ArticleForm
template_name = 'blog/article_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)3. Template design principles
<!-- Use template inheritance to reduce duplication -->
{% extends 'base.html' %}
<!-- Use meaningful block names -->
{% block page_title %}Article List{% endblock %}
<!-- Use filters to format data -->
{{ article.publish_date|date:"Y-m-d" }}
<!-- Use template tags to handle logic -->
{% for article in articles %}
{% include 'blog/article_card.html' %}
{% empty %}
<p>No articles</p>
{% endfor %}Summary
Django's MTV architecture pattern provides clear code organization:
- ✅ Model responsible for data storage and business logic processing
- ✅ Template responsible for user interface and data display
- ✅ View responsible for request handling and business logic control
- ✅ Three components work together to implement complete web application functions
- ✅ Following MTV pattern helps code maintenance and team collaboration
Understanding MTV architecture is the foundation for mastering Django development.
Next Article
We will learn Django app creation and management.