Skip to content

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

ComponentDjango MTVTraditional MVCDescription
Data layerModelModelData model and business logic
Presentation layerTemplateViewUser interface and data display
Control layerViewControllerRequest 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 page

2. Complete URL configuration

python
# 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

python
# 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

  1. Field types: CharField, TextField, DateTimeField, etc.
  2. Field options: max_length, blank, null, default, etc.
  3. Relationship fields: ForeignKey, ManyToManyField, OneToOneField
  4. Meta class: Defines model metadata
  5. 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

python
# 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 context

Access 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 2

Template 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>&copy; 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>
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

  1. Variables:
  2. Tags: {% tag %}
  3. Filters: 0
  4. Template inheritance: {% extends %} and {% block %}
  5. Template inclusion: {% include %}

MTV Best Practices

1. Model design principles

python
# 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

python
# 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

html
<!-- 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:

  1. ✅ Model responsible for data storage and business logic processing
  2. ✅ Template responsible for user interface and data display
  3. ✅ View responsible for request handling and business logic control
  4. ✅ Three components work together to implement complete web application functions
  5. ✅ 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.

2.2 Django App →

Directory

Return to Course Directory

Released under the [BY-NC-ND License](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.en).