4
Django 5.x 实战
Django: Batteries Included
Django 的口号是 "batteries included"(电池自带):ORM、Admin、认证、表单、路由、模板、缓存、信号,全给你装好了。它的杀手锏是 Admin 后台——模型定义完,一个网页版管理后台自动生成,省掉半个 CRUD。
安装与项目创建(MTV 架构)
pip install django
django-admin startproject mysite .
python manage.py startapp blog
# 目录:mysite/(settings/urls) blog/(models/views/admin)
python manage.py runserver
# * Starting development server at http://127.0.0.1:8000/
论MTV 架构
Django 是 MTV:Model(数据模型,对应数据库表)、Template(HTML 模板)、View(业务逻辑,接收请求返回响应)。URLconf 把 URL 映射到 View。和经典 MVC 对应:View 相当于 Controller,Template 相当于 View。
DTL 模板语言速查
| 语法 | 作用 |
{{ var }} | 输出变量 |
{% for x in list %} | 循环,{% empty %} 处理空列表 |
{% if %}{% else %}{% endif %} | 条件 |
{% extends "base.html" %} | 模板继承 |
{% block name %}{% endblock %} | 可覆盖块 |
{% url 'name' arg %} | 反向生成 URL |
{% csrf_token %} | CSRF 隐藏字段 |
{{ x|date:"Y-m-d" }} | 过滤器 |
{% static 'js/main.js' %} | 静态文件 URL |
表单系统:forms.Form 与 ModelForm
# forms.Form:手写字段,适合非数据库表单
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=50)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
# ModelForm:直接从模型生成,省一半代码
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ["title", "body", "category"]
# exclude = ["created"] # 排除某些字段
# views.py:用 ModelForm 创建文章
from django.shortcuts import redirect, render, get_object_or_404
from .forms import PostForm
from .models import Post
def create_post(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save() # 直接存库
return redirect("post_detail", pk=post.pk)
else:
form = PostForm()
return render(request, "post_form.html", {"form": form})
认证系统详解
# Django 自带 auth 应用,注册 INSTALLED_APPS 后开箱即用
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
def my_login(request):
if request.method == "POST":
user = authenticate(
request,
username=request.POST["username"],
password=request.POST["password"])
if user:
login(request, user) # 写 session
return redirect("/dashboard")
@login_required
def dashboard(request):
# request.user 就是当前登录用户
return render(request, "dash.html", {"user": request.user})
# 改密码:set_password 自动哈希;check_password 验密码
user.set_password("newpass")
user.save()
user.check_password("newpass") # True
模型 ORM 与迁移
# blog/models.py
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=50)
def __str__(self): return self.name
class Post(models.Model):
title = models.CharField(max_length=120)
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="posts")
tags = models.ManyToManyField("Tag")
# 生成迁移文件并执行
python manage.py makemigrations
python manage.py migrate
查询 API:ORM 是 Django 的另一把利器
Post.objects.all() # 全部
Post.objects.filter(title__contains="Python") # 过滤,__contains 是字段查找
Post.objects.get(pk=1) # 取单个,找不到抛 DoesNotExist
Post.objects.create(title="x", body="y")
Post.objects.filter(pk=1).update(title="新")
Post.objects.filter(pk=1).delete()
# 关联查询防 N+1:select_related(一对一/外键,JOIN)/ prefetch_related(多对多,二次查询)
Post.objects.select_related("category").all()
# Q 对象复杂查询 / annotate 聚合
from django.db.models import Q
Post.objects.filter(Q(title__startswith="A") | Q(title__startswith="B"))
高级查询:annotate / aggregate / F / Q
from django.db.models import Count, Avg, F, Q
# annotate:给每个对象加一个计算字段
categories = Category.objects.annotate(num_posts=Count("posts"))
for c in categories:
print(c.name, c.num_posts)
# aggregate:对整个 QuerySet 算汇总
from django.db.models import Avg
Post.objects.aggregate(avg_id=Avg("id"))
# F:引用字段本身,避免先取到 Python 再写回(并发安全)
Post.objects.update(views=F("views") + 1)
# Q:复杂 OR 条件
Post.objects.filter(Q(title__startswith="A") | Q(title__startswith="B"))
Admin 后台:Django 的杀手锏
# blog/admin.py
from django.contrib import admin
from .models import Post, Category
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ("title", "category", "created")
list_filter = ("category",)
search_fields = ("title", "body")
# 改完访问 /admin,登录后就是现成的管理后台
Admin 常用定制项
| ModelAdmin 属性 | 效果 |
list_display | 列表页显示哪些字段 |
list_filter | 右侧过滤器 |
search_fields | 顶部搜索框搜哪些字段 |
list_editable | 列表页直接编辑 |
raw_id_fields | 外键用 ID 输入替代下拉 |
fieldsets | 编辑页分组 |
inlines | 父子表一起编辑(文章+评论) |
actions | 批量操作(批量发布/删除) |
先建超级管理员
python manage.py createsuperuser
# 按提示输用户名邮箱密码,然后 /admin 登录
视图与 URL:FBV 与 CBV
# blog/views.py —— FBV 函数视图
from django.shortcuts import render, get_object_or_404
from .models import Post
def post_list(request):
posts = Post.objects.all().order_by("-created")
return render(request, "blog/list.html", {"posts": posts})
# CBV 类视图:ListView 自动查全部并分页
from django.views.generic import ListView
class PostListView(ListView):
model = Post
paginate_by = 10
template_name = "blog/list.html"
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("blog.urls")),
]
# blog/urls.py
from django.urls import path
from .views import post_list, PostListView
urlpatterns = [
path("", PostListView.as_view(), name="post-list"),
]
模板与表单
{# templates/blog/list.html DTL 模板语言 #}
{% raw %}{% extends "base.html" %}
{% block content %}
<h1>文章列表</h1>
{% for post in posts %}
<h2>{{ post.title }}</h2>
<p>{{ post.body|truncatewords:20 }}</p>
{% empty %}
<p>还没有文章</p>
{% endfor %}
<form method="post">{% csrf_token %}{{ form.as_p }}{% endraw %}
<button>提交</button></form>
{% raw %}{% endblock %}{% endraw %}
# blog/forms.py —— ModelForm 自动从模型生成表单
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ["title", "body", "category"]
认证系统
from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required
@login_required
def dashboard(request):
# 没登录自动跳转 /accounts/login/
return render(request, "dash.html")
CBV 类视图详解与自定义 User 模型
# blog/views.py —— 五种通用 CBV,几乎覆盖 CRUD
from django.views.generic import (
ListView, DetailView, CreateView, UpdateView, DeleteView
)
from django.urls import reverse_lazy
from .models import Post
class PostListView(ListView): # GET 列表
model = Post
paginate_by = 10
class PostDetailView(DetailView): # GET 单个
model = Post
class PostCreateView(CreateView): # GET 表单 + POST 保存
model = Post
fields = ["title", "body", "category"]
success_url = reverse_lazy("post-list")
class PostUpdateView(UpdateView): # 编辑
model = Post
fields = ["title", "body"]
class PostDeleteView(DeleteView): # 删除确认页
model = Post
success_url = reverse_lazy("post-list")
# 自定义 User 模型(强烈建议新项目一开始就建,后期换非常痛)
# accounts/models.py
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
avatar = models.ImageField(upload_to="avatars/", blank=True)
# settings.py 告诉 Django 用自定义 User
AUTH_USER_MODEL = "accounts.CustomUser"
国际化 i18n
# settings.py
USE_I18N = True
LANGUAGES = [("en", "English"), ("zh-hans", "中文")]
LOCALE_PATHS = [BASE_DIR / "locale"]
# 模板里标记可翻译字符串
# {% load i18n %}
# {% trans "Welcome" %}
# 命令行:makemessages 生成翻译文件,compilemessages 编译
python manage.py makemessages -l zh_Hans
python manage.py compilemessages
中间件、缓存与信号
# settings.py 注册中间件(请求进来按顺序过,响应反过来)
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
]
# 缓存:用 Redis 加速热门查询
from django.core.cache import cache
cache.set("hot_posts", posts, 60) # 存 60 秒
posts = cache.get("hot_posts")
# 信号:模型保存后自动干别的(如发通知)
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=Post)
def notify_on_new_post(sender, instance, created, **kwargs):
if created:
print(f"新文章发布:{instance.title}")
静态文件与生产 settings
# settings.py 静态文件配置
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles" # collectstatic 收集到这
STATICFILES_DIRS = [BASE_DIR / "static"]
# 生产 settings 完整版
import os
DEBUG = False
ALLOWED_HOSTS = ["your-domain.com", "www.your-domain.com"]
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
}
}
# 收集静态 + 启动
python manage.py collectstatic --noinput
gunicorn mysite.wsgi:application --bind 0.0.0.0:8000
部署:DEBUG=False 三件套
# settings.py 生产环境必须改
DEBUG = False
ALLOWED_HOSTS = ["your-domain.com"]
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
# 收集静态文件给 Nginx
python manage.py collectstatic
# 生产启动
gunicorn mysite.wsgi:application --bind 0.0.0.0:8000
完整案例:Django 内容管理系统(CMS)
mysite/
├── mysite/ # settings.py / urls.py
└── blog/
├── models.py # Post/Category/Tag/Comment
├── admin.py # 注册到 Admin
├── views.py # ListView/DetailView/CreateView
├── urls.py
└── templates/blog/ # list/detail/form
blog/urls.py 和 views.py 完整配置
# blog/urls.py
from django.urls import path
from .views import PostListView, PostDetailView, PostCreateView
urlpatterns = [
path("", PostListView.as_view(), name="post-list"),
path("post/<int:pk>/", PostDetailView.as_view(), name="post-detail"),
path("post/new/", PostCreateView.as_view(), name="post-new"),
]
# 建完模型后跑:
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
# 访问 /admin 录文章,访问 / 看列表
建完模型、注册 Admin、写三个 CBV、配 URL,一个带后台、列表、详情、分页的 CMS 就齐了——这就是 Django "batteries included" 的威力。生产部署三件套:DEBUG=False、ALLOWED_HOSTS、SECRET_KEY 走环境变量;python manage.py collectstatic 收静态文件;前面 Gunicorn + Nginx。
本章面试题 · Django
1.(概念题)什么是 N+1 查询?Django 怎么解决?
查看答案
答案:查 100 篇文章,循环里每篇又查一次它的 category——1(查文章)+ 100(查分类)= 101 次 SQL。select_related("category") 用 JOIN 一次查完(外键/一对一);prefetch_related 用二次 IN 查询(多对多)。看 django.db.connection.queries 或 Django Debug Toolbar 能数 SQL 条数。
2.(概念题)Django 的 CSRF 防护是什么?模板里为什么要写 {% csrf_token %}?
查看答案
答案:CSRF 是跨站请求伪造——恶意网站诱导已登录用户发 POST。Django 在表单里塞一个随机 token,服务端校验 token 才认。{% csrf_token %} 渲染这个隐藏字段;API 用 AJAX 时从 cookie 读 token 放 Header。GET 请求不校验(本就不该改数据)。
3.(概念题)makemigrations 和 migrate 有什么区别?
查看答案
答案:makemigrations 根据 models.py 的变化生成迁移文件(Python 文件,可 review);migrate 才真正把迁移应用到数据库。前者是"写变更单",后者是"执行变更单"。
4.(思考题)Django 的 Model 里 on_delete=models.CASCADE 是什么意思?
查看答案
答案:外键引用的父对象被删时,关联的子对象怎么办。CASCADE 级联删除;PROJECT 拦着不让删;SET_NULL 置空;SET_DEFAULT 置默认值。选哪个看业务——文章删了评论要不要一起删?