Django 入门教程 —— MTV 架构与快速建站
本章目标
- 理解 Django 的 MTV 架构与"电池内置"哲学
- 掌握模型、视图、模板、路由四大核心
- 完成一个完整的博客系统 CRUD
1. Django 的 MTV 架构
Django 采用 MTV(Model-Template-View)分层:
URL Dispatcher
│
▼
┌──────────────────────┐
│ View(业务逻辑) │ ← "Controller" 的角色
│ 处理请求、协调 Model │
│ 和 Template │
└────┬─────────┬────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ Model │ │ Template │
│ 数据层 │ │ 表现层 │
│ ORM + DB │ │ HTML/模板│
└──────────┘ └──────────┘
与传统 MVC 的对应关系:
- Model ≈ M
- Template ≈ V
- View ≈ C(Controller)
2. 模型(Model)—— 数据层
# blog/models.py
from django.db import models
from django.utils.text import slugify
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=50, unique=True, blank=True)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField("标题", max_length=200)
slug = models.SlugField(max_length=200, unique=True)
body = models.TextField("正文")
tags = models.ManyToManyField(Tag, related_name="posts", 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=["-created_at"]),
models.Index(fields=["slug"]),
]
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super().save(*args, **kwargs)
def get_absolute_url(self):
from django.urls import reverse
return reverse("post_detail", kwargs={"slug": self.slug})
模型字段类型速查
| 字段 |
用途 |
CharField(max_length=n) |
短文本 |
TextField() |
长文本 |
IntegerField() |
整数 |
BooleanField() |
布尔值 |
DateTimeField(auto_now_add=True) |
创建时间 |
DateTimeField(auto_now=True) |
更新时间 |
ForeignKey(to, on_delete) |
一对多 |
ManyToManyField(to) |
多对多 |
SlugField() |
URL 友好字符串 |
EmailField() |
邮箱 |
ImageField(upload_to=...) |
图片上传 |
查询 API(ORM)
# 基本 CRUD
Post.objects.create(title="Hello", body="World") # 创建
post = Post.objects.get(slug="hello") # 单个查询
posts = Post.objects.filter(tags__name="django") # 过滤
Post.objects.filter(status="draft").update(status="pub") # 批量更新
Post.objects.filter(created_at__year=2024).delete() # 批量删除
# 聚合
from django.db.models import Count, Avg, Sum
Post.objects.aggregate(Count("id")) # 总数
# 关联查询
post.tags.all() # 获取文章的所有标签
tag.posts.filter(status="published") # 获取标签下已发布文章
# 链式条件
Post.objects.filter(
models.Q(status="published") &
(models.Q(title__icontains="Django") | models.Q(body__icontains="Django"))
)
3. 视图(View)—— 业务逻辑
函数视图 vs 类视图
# 函数视图 —— 简单直接
def post_list(request):
posts = Post.objects.filter(status="published")
return render(request, "blog/list.html", {"posts": posts})
# 类视图 —— 可复用、带 mixin
from django.views.generic import ListView, DetailView, CreateView
class PostListView(ListView):
model = Post
template_name = "blog/list.html"
context_object_name = "posts"
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset().filter(status="published")
tag_slug = self.kwargs.get("tag_slug")
if tag_slug:
queryset = queryset.filter(tags__slug=tag_slug)
return queryset
常用通用视图
| 类视图 |
用途 |
关键属性 |
ListView |
对象列表 |
model, paginate_by, queryset |
DetailView |
对象详情 |
model, slug_field, slug_url_kwarg |
CreateView |
创建对象 |
model, fields, success_url |
UpdateView |
更新对象 |
同上 |
DeleteView |
删除对象 |
model, success_url |
TemplateView |
纯模板 |
template_name |
4. 模板(Template)—— 表现层
<!-- templates/blog/list.html -->
{% extends "base.html" %}
{% block title %}博客文章列表{% endblock %}
{% block content %}
<h1>📝 文章列表</h1>
{% for post in posts %}
<article>
<h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>
<p>{{ post.body|truncatewords:30 }}</p>
<div class="meta">
<time>{{ post.created_at|date:"Y-m-d" }}</time>
<span class="tags">
{% for tag in post.tags.all %}
<a href="{% url 'post_list_by_tag' tag.slug %}">{{ tag.name }}</a>
{% endfor %}
</span>
</div>
</article>
{% empty %}
<p>暂无文章</p>
{% endfor %}
<!-- 分页 -->
{% if page_obj.has_other_pages %}
<nav class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">上一页</a>
{% endif %}
<span>第 {{ page_obj.number }} / {{ page_obj.paginator.num_pages }} 页</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">下一页</a>
{% endif %}
</nav>
{% endif %}
{% endblock %}
模板标签速查
| 标签 |
说明 |
{% extends %} |
继承父模板 |
{% block %} |
定义可替换的内容块 |
{% include %} |
引入子模板 |
{% url "name" %} |
反向 URL 解析 |
{% csrf_token %} |
CSRF 令牌 |
{% for %} / {% endfor %} |
循环 |
{% if %} / {% endif %} |
条件 |
{% with %} |
变量赋值 |
{% now "Y-m-d" %} |
当前日期时间 |
模板过滤器
| 过滤器 |
效果 |
| `{{ text |
truncatewords:30 }}` |
| `{{ date |
date:"Y-m-d" }}` |
| `{{ content |
safe }}` |
| `{{ text |
linebreaks }}` |
| `{{ value |
default:"暂无" }}` |
5. 管理后台自定义
# blog/admin.py
from django.contrib import admin
from .models import Post, Tag
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ["name", "slug"]
prepopulated_fields = {"slug": ("name",)}
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ["title", "slug", "created_at", "updated_at"]
list_filter = ["tags", "created_at"]
search_fields = ["title", "body"]
prepopulated_fields = {"slug": ("title",)}
date_hierarchy = "created_at"
filter_horizontal = ["tags"]
readonly_fields = ["created_at", "updated_at"]
fieldsets = [
("基本信息", {"fields": ["title", "slug", "body"]}),
("分类", {"fields": ["tags"]}),
("时间信息", {"fields": ["created_at", "updated_at"]}),
]
6. 常用管理命令
python manage.py runserver
python manage.py startapp <
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
python manage.py shell
python manage.py collectstatic
python manage.py test
python manage.py showmigrations
python manage.py dbshell
思考题
- Django 的 MTV 与经典 MVC 有何异同?为什么 Django 称其 View 为 Controller?
ForeignKey 的 on_delete=models.CASCADE vs SET_NULL 有何区别?
- 什么场景应该使用函数视图而非类视图?
- Django 管理后台适合直接给最终用户使用吗?为什么?