楼层: 首页/ 软件技术/ Python Web 开发/ Django 5.x 实战
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 置默认值。选哪个看业务——文章删了评论要不要一起删?