Django 6.0 从入门到实战教程(上机实操版)
服务器环境 :华为云 FlexusX ecs-60a4-0001 | Ubuntu 24.04.4 LTS | Python 3.12.3 | Django 6.0.6 IP :114.116.250.214(弹性公网)/ 192.168.0.238(私有) 规格 :8vCPU | 16GiB | x2e.8u.16g | 可用区7 | 5Mbit/s BGP 实操日期:2026-06-27
目录
| 篇章 | 章节 | 内容 |
|---|---|---|
| 入门篇 | 1-5 | 简介 → 安装 → 创建项目 → django-admin → 项目结构 |
| 核心篇 | 6-11 | 模板 → 模型 → 表单 → 视图 → 路由 → Admin |
| ORM篇 | 12-14 | 单表操作 → 多表操作 → 聚合查询 |
| 进阶篇 | 15-19 | Form组件 → Auth → Cookie/Session → 中间件 → FBV/CBV |
| 实战篇 | 20-24 | 配置管理 → 请求生命周期 → 博客项目 → 部署 → 测试 |
环境架构
lua
+----------------------------------------------------------+
| 华为云 FlexusX ecs-60a4-0001 |
| Ubuntu 24.04.4 LTS |
| 8vCPU / 16GiB |
| |
| +------------------+ +------------------+ |
| | Python 3.12.3 | | pip 24.0 | |
| +------------------+ +------------------+ |
| | | |
| v v |
| +------------------------------------------+ |
| | Django 6.0.6 | |
| | +------------+ +------------------+ | |
| | | sqlparse | | asgiref 3.11.1 | | |
| | | 0.5.5 | | | | |
| | +------------+ +------------------+ | |
| +------------------------------------------+ |
| | |
| v |
| +------------------------------------------+ |
| | SQLite 3 (内置) | |
| | db.sqlite3 | |
| +------------------------------------------+ |
+----------------------------------------------------------+
第一章 Django 简介
1.1 什么是 Django
Django 是一个高性能的 Python Web 框架,遵循 MTV(Model-Template-View) 架构模式,以 快速开发 和 DRY(Don't Repeat Yourself) 为设计哲学。
核心特性:
| 特性 | 说明 |
|---|---|
| ORM | 对象关系映射,无需写 SQL 即可操作数据库 |
| Admin | 内置管理后台,自动生成 CRUD 界面 |
| 模板引擎 | Django Template Language (DTL),支持继承和过滤器 |
| 表单处理 | Form / ModelForm,自动验证和渲染 |
| 认证系统 | 内置 User / Group / Permission / Session |
| 中间件 | 请求/响应处理管道,可插拔式扩展 |
| 安全防护 | CSRF / XSS / SQL 注入 / Clickjacking 防护 |
| 缓存框架 | 支持 Memcached / Redis / 数据库 / 文件系统 |
| 国际化 | 多语言、多时区支持 |
1.2 MTV 架构
sql
+------------------+
| Model | 数据模型层
| (ORM -> DB) | 定义数据结构
+--------+---------+
|
+--------+---------+
| View | 视图层 (控制器)
| (业务逻辑) | 处理请求,调用 Model
+--------+---------+
|
+--------+---------+
| Template | 模板层 (视图)
| (HTML 渲染) | 生成最终 HTML
+------------------+
与 MVC 的对应关系:
| MVC | Django MTV | 说明 |
|---|---|---|
| Model | Model | 数据模型 |
| View | Template | 展示层 |
| Controller | View | 业务逻辑控制器 |
1.3 Django 版本历史
| 版本 | 发布时间 | 重要特性 | LTS |
|---|---|---|---|
| 1.x | 2005-2017 | 奠定基础架构 | - |
| 2.x | 2017-2019 | 简化 URL 路由语法 | 2.2 |
| 3.x | 2019-2021 | ASGI 异步支持 | 3.2 |
| 4.x | 2021-2023 | 异步视图/ORM | 4.2 |
| 5.x | 2023-2025 | 数据库计算默认值、模型字段 | 5.2 |
| 6.0 | 2025-12 | 移除 Python 3.10/3.11 支持 | - |
第二章 Django 安装
2.1 系统环境
bash
root@ecs-60a4-0001:~# python3 --version
Python 3.12.3
root@ecs-60a4-0001:~# cat /etc/os-release | head -2
PRETTY_NAME="Ubuntu 24.04.4 LTS"
NAME="Ubuntu"
root@ecs-60a4-0001:~# nproc && free -h | head -2
8
total used free shared buff/cache available
Mem: 15Gi 2.1Gi 11Gi 124Mi 2.8Gi 13Gi
2.2 安装 Django
踩坑 1:Ubuntu 24.04 PEP 668 保护
Ubuntu 24.04 默认禁止 pip install 直接安装到系统环境(PEP 668),会报错:
go
error: externally-managed-environment
解决方案 :使用 --break-system-packages 标志或创建虚拟环境。
bash
# 方案一:直接安装(加 --break-system-packages)
pip3 install --break-system-packages django
# 方案二:使用虚拟环境(推荐)
python3 -m venv myenv
source myenv/bin/activate
pip install django
踩坑 2:pip 默认源超时
华为云服务器访问 PyPI 官方源可能超时,使用国内镜像:
bash
# 使用清华镜像源安装
pip3 install --break-system-packages -i https://pypi.tuna.tsinghua.edu.cn/simple django
实机输出:
bash
root@ecs-60a4-0001:~# pip3 install --break-system-packages -i https://pypi.tuna.tsinghua.edu.cn/simple django
Collecting django
Downloading https://pypi.tuna.tsinghua.edu.cn/packages/.../Django-6.0.6-py3-none-any.whl (8.4 MB)
Collecting asgiref>=3.9.1
Downloading https://pypi.tuna.tsinghua.edu.cn/packages/.../asgiref-3.11.1-py3-none-any.whl (24 kB)
Collecting sqlparse>=0.5.0
Downloading https://pypi.tuna.tsinghua.edu.cn/packages/.../sqlparse-0.5.5-py3-none-any.whl (46 kB)
Installing collected packages: sqlparse, asgiref, django
Successfully installed asgiref-3.11.1 django-6.0.6 sqlparse-0.5.5
2.3 验证安装
python
root@ecs-60a4-0001:~# python3 -c "import django; print(f'Django {django.get_version()}')"
Django 6.0.6
依赖版本:
| 包 | 版本 | 说明 |
|---|---|---|
| Django | 6.0.6 | Web 框架 |
| asgiref | 3.11.1 | ASGI 规范实现 |
| sqlparse | 0.5.5 | SQL 解析(用于查询分析) |
| Python | 3.12.3 | 运行时 |
第三章 创建第一个项目
3.1 使用 django-admin 创建项目
bash
mkdir /tmp/django_tutorial && cd /tmp/django_tutorial
django-admin startproject myproject
3.2 项目结构
scss
myproject/
manage.py # 命令行管理工具 (665 bytes)
myproject/ # 项目包目录
__init__.py # 空文件,标识 Python 包 (0 bytes)
settings.py # 项目配置 (3009 bytes)
urls.py # 根 URL 路由 (765 bytes)
asgi.py # ASGI 入口 (异步) (395 bytes)
wsgi.py # WSGI 入口 (同步) (395 bytes)
文件说明:
| 文件 | 作用 |
|---|---|
manage.py |
Django 命令行工具,与 django-admin 功能相同但自动设置 DJANGO_SETTINGS_MODULE |
settings.py |
项目全局配置(数据库、应用、中间件等) |
urls.py |
URL 路由配置,将 URL 映射到视图 |
wsgi.py |
WSGI 部署入口(Gunicorn / uWSGI) |
asgi.py |
ASGI 部署入口(异步,WebSocket) |
3.3 manage.py 源码解析
python
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
核心逻辑:
- 设置
DJANGO_SETTINGS_MODULE环境变量指向项目的settings.py - 调用
execute_from_command_line解析命令行参数并执行
3.4 创建第一个 App
bash
cd myproject
python3 manage.py startapp blog
App 结构:
bash
blog/
__init__.py # Python 包标识
admin.py # Admin 注册配置
apps.py # App 配置类
models.py # 数据模型定义
tests.py # 单元测试
views.py # 视图函数
migrations/ # 数据库迁移文件
__init__.py
3.5 注册 App
编辑 myproject/settings.py,将 blog 添加到 INSTALLED_APPS:
python
INSTALLED_APPS = [
'django.contrib.admin', # 管理后台
'django.contrib.auth', # 认证系统
'django.contrib.contenttypes', # 内容类型框架
'django.contrib.sessions', # Session 支持
'django.contrib.messages', # 消息框架
'django.contrib.staticfiles', # 静态文件管理
'blog', # ← 我们的 App
]
3.6 系统检查
bash
root@ecs-60a4-0001:~# python3 manage.py check
System check identified no issues (0 silenced).
第四章 django-admin 命令
4.1 django-admin 与 manage.py
| 命令 | 说明 |
|---|---|
django-admin |
全局命令,需手动设置 DJANGO_SETTINGS_MODULE |
python manage.py |
项目命令,自动读取当前项目的 settings.py |
python -m django |
等价于 django-admin |
4.2 常用命令
bash
root@ecs-60a4-0001:~# django-admin --version
6.0.6
root@ecs-60a4-0001:~# django-admin help
Type 'django-admin help <subcommand>' for help on a specific subcommand.
Available subcommands:
[django]
check # 系统检查
compilemessages # 编译国际化消息
createcachetable # 创建缓存表
dbshell # 数据库 Shell
diffsettings # 配置差异对比
dumpdata # 导出数据 (JSON)
flush # 清空数据库
inspectdb # 反向生成模型
loaddata # 导入数据
makemessages # 生成国际化消息
makemigrations # 生成迁移文件
migrate # 执行数据库迁移
optimizemigration # 优化迁移
runserver # 启动开发服务器
sendtestemail # 发送测试邮件
shell # Django 交互 Shell
showmigrations # 显示迁移状态
sqlflush # 查看 flush SQL
sqlmigrate # 查看迁移 SQL
sqlsequencereset # 重置序列 SQL
squashmigrations # 压缩迁移
startapp # 创建应用
startproject # 创建项目
test # 运行测试
testserver # 测试服务器
4.3 命令速查表
| 命令 | 说明 | 示例 |
|---|---|---|
startproject |
创建项目 | django-admin startproject myproject |
startapp |
创建应用 | python manage.py startapp blog |
runserver |
启动开发服务器 | python manage.py runserver 0.0.0.0:8000 |
makemigrations |
生成迁移文件 | python manage.py makemigrations |
migrate |
执行数据库迁移 | python manage.py migrate |
createsuperuser |
创建超级用户 | python manage.py createsuperuser |
collectstatic |
收集静态文件 | python manage.py collectstatic |
shell |
Django Shell | python manage.py shell |
dbshell |
数据库 Shell | python manage.py dbshell |
test |
运行测试 | python manage.py test |
check |
系统检查 | python manage.py check |
dumpdata |
导出数据 | python manage.py dumpdata blog |
loaddata |
导入数据 | python manage.py loaddata data.json |
inspectdb |
反向生成模型 | python manage.py inspectdb |
showmigrations |
显示迁移状态 | python manage.py showmigrations |
flush |
清空数据库 | python manage.py flush |
sqlmigrate |
查看迁移 SQL | python manage.py sqlmigrate blog 0001 |
第五章 项目结构解析
5.1 settings.py 关键配置
python
# ===== 应用配置 =====
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
]
# ===== 中间件配置 =====
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # 安全防护
'django.contrib.sessions.middleware.SessionMiddleware', # Session 处理
'django.middleware.common.CommonMiddleware', # 通用处理
'django.middleware.csrf.CsrfViewMiddleware', # CSRF 防护
'django.contrib.auth.middleware.AuthenticationMiddleware', # 认证
'django.contrib.messages.middleware.MessageMiddleware', # 消息框架
'django.middleware.clickjacking.XFrameOptionsMiddleware', # 点击劫持防护
]
# ===== URL 配置 =====
ROOT_URLCONF = 'myproject.urls'
# ===== 数据库配置 =====
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# ===== 国际化 =====
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_TZ = True
# ===== 静态文件 =====
STATIC_URL = 'static/'
5.2 中间件说明
| 序号 | 中间件 | 作用 |
|---|---|---|
| 1 | SecurityMiddleware | HTTPS 重定向、HSTS、X-Content-Type-Options |
| 2 | SessionMiddleware | Session 读取/写入(依赖 Cookie) |
| 3 | CommonMiddleware | URL 规范化(末尾斜杠)、DISALLOWED_USER_AGENTS |
| 4 | CsrfViewMiddleware | CSRF Token 验证 |
| 5 | AuthenticationMiddleware | 从 Session 读取用户,设置 request.user |
| 6 | MessageMiddleware | 消息框架(一次性通知) |
| 7 | XFrameOptionsMiddleware | X-Frame-Options 头(防点击劫持) |
5.3 多环境配置策略
csharp
myproject/
settings/
__init__.py
base.py # 公共配置
dev.py # 开发环境
prod.py # 生产环境
test.py # 测试环境
python
# base.py (公共配置)
SECRET_KEY = 'django-insecure-...'
INSTALLED_APPS = [...]
MIDDLEWARE = [...]
# dev.py (开发)
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['*']
# prod.py (生产)
from .base import *
DEBUG = False
ALLOWED_HOSTS = ['example.com']
DATABASES = {'default': {'ENGINE': 'django.db.backends.postgresql', ...}}
使用时设置环境变量:
bash
export DJANGO_SETTINGS_MODULE=myproject.settings.prod
第六章 Django 模板
6.1 模板渲染基础
python
from django.template import Template, Context
# 创建模板
t = Template('{{ name }}, 欢迎学习 Django!')
c = Context({'name': 'Python开发者'})
print(t.render(c))
# 输出: Python开发者, 欢迎学习 Django!
6.2 模板标签
django
{# 循环标签 #}
{% for item in items %}
{{ forloop.counter }}. {{ item }}
{% endfor %}
{# 条件标签 #}
{% if count > 5 %}
数量大于5
{% else %}
数量不超过5
{% endif %}
{# 实机输出 #}
1. Python
2. Django
3. Flask
4. FastAPI
数量不超过5
6.3 模板继承
django
{# base.html #}
<html>
<head><title>{% block title %}Base{% endblock %}</title></head>
<body>
<nav>Navigation</nav>
{% block content %}Default content{% endblock %}
<footer>Footer</footer>
</body>
</html>
{# child.html #}
{% extends "base.html" %}
{% block title %}Home Page{% endblock %}
{% block content %}<h1>Welcome Home</h1>{% endblock %}
6.4 内置过滤器(22个实测)
| 过滤器 | 语法 | 输入 | 输出 | 说明 |
|---|---|---|---|---|
| upper | `{{ s | upper }}` | hello | HELLO |
| lower | `{{ s | lower }}` | HELLO | hello |
| title | `{{ s | title }}` | hello world | Hello World |
| capfirst | `{{ s | capfirst }}` | hello | Hello |
| add | `{{ n | add:10 }}` | 5 | 15 |
| length | `{{ s | length }}` | django | 6 |
| truncatewords | `{{ s | truncatewords:2 }}` | one two three four | one two ... |
| truncatechars | `{{ s | truncatechars:10 }}` | abcdefghijklmnop | abcdefghi... |
| default | `{{ s | default:'N/A' }}` | (空) | N/A |
| yesno | `{{ s | yesno:'是,否,未知' }}` | True | 是 |
| yesno | `{{ s | yesno:'是,否,未知' }}` | False | 否 |
| yesno | `{{ s | yesno:'是,否,未知' }}` | None | 未知 |
| floatformat | `{{ f | floatformat:2 }}` | 3.14159 | 3.14 |
| cut | `{{ s | cut:' ' }}` | a b c d | abcd |
| safe | `{{ s | safe }}` | <b>bold</b> | bold |
| escape | `{{ s | escape }}` | <b>bold</b> | <b>bold</b> |
| striptags | `{{ s | striptags }}` | <p>Hello</p> | Hello |
| urlencode | `{{ s | urlencode }}` | hello world | hello%20world |
| wordcount | `{{ s | wordcount }}` | one two three | 3 |
| date | `{{ d | date:'Y-m-d' }}` | 2026-06-27 | 2026-06-27 |
| slugify | `{{ s | slugify }}` | Hello World Django | hello-world-django |
| filesizeformat | `{{ n | filesizeformat }}` | 1048576 | 1.0 MB |
第七章 Django 模型
7.1 模型定义
python
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
"""文章分类"""
name = models.CharField('分类名称', max_length=100)
slug = models.SlugField('URL别名', max_length=100, unique=True)
description = models.TextField('描述', blank=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
verbose_name = '分类'
verbose_name_plural = '分类'
ordering = ['-created_at']
def __str__(self):
return self.name
class Tag(models.Model):
"""文章标签"""
name = models.CharField('标签名', max_length=50, unique=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
def __str__(self):
return self.name
class Post(models.Model):
"""文章"""
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
]
title = models.CharField('标题', max_length=200)
slug = models.SlugField('URL别名', max_length=200, unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='作者')
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, verbose_name='分类')
tags = models.ManyToManyField(Tag, blank=True, verbose_name='标签')
content = models.TextField('内容')
status = models.CharField('状态', max_length=10, choices=STATUS_CHOICES, default='draft')
view_count = models.PositiveIntegerField('浏览量', default=0)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
verbose_name = '文章'
verbose_name_plural = '文章'
ordering = ['-created_at']
def __str__(self):
return self.title
class Comment(models.Model):
"""评论"""
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments', verbose_name='文章')
name = models.CharField('昵称', max_length=80)
email = models.EmailField('邮箱')
body = models.TextField('评论内容')
active = models.BooleanField('是否显示', default=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
ordering = ['-created_at']
7.2 字段类型对照表
| 字段类型 | Python 类型 | 数据库类型 | 说明 |
|---|---|---|---|
| CharField | str | VARCHAR | 短文本,必须指定 max_length |
| TextField | str | TEXT | 长文本 |
| IntegerField | int | INTEGER | 整数 |
| PositiveIntegerField | int | INTEGER UNSIGNED | 正整数 |
| BigIntegerField | int | BIGINT | 大整数 |
| FloatField | float | FLOAT | 浮点数 |
| DecimalField | Decimal | DECIMAL | 精确小数 |
| BooleanField | bool | BOOLEAN | 布尔值 |
| DateTimeField | datetime | DATETIME | 日期时间 |
| DateField | date | DATE | 日期 |
| EmailField | str | VARCHAR | 邮箱(验证) |
| URLField | str | VARCHAR | URL(验证) |
| SlugField | str | VARCHAR | URL 友好字符串 |
| UUIDField | UUID | UUID | UUID |
| ForeignKey | Model | INTEGER (FK) | 外键(多对一) |
| ManyToManyField | QuerySet | 中间表 | 多对多 |
| OneToOneField | Model | INTEGER (FK) | 一对一 |
| ImageField | FieldFile | VARCHAR | 图片文件 |
| FileField | FieldFile | VARCHAR | 文件 |
7.3 字段参数
| 参数 | 说明 | 示例 |
|---|---|---|
verbose_name |
人类可读名称 | verbose_name='标题' |
max_length |
最大长度 | max_length=200 |
null |
数据库允许 NULL | null=True |
blank |
表单允许为空 | blank=True |
default |
默认值 | default=0 |
unique |
唯一约束 | unique=True |
choices |
枚举选项 | choices=[('draft', '草稿')] |
on_delete |
外键删除策略 | on_delete=models.CASCADE |
auto_now |
每次保存更新 | auto_now=True |
auto_now_add |
创建时设置 | auto_now_add=True |
related_name |
反向关系名 | related_name='comments' |
7.4 on_delete 选项
| 选项 | 说明 |
|---|---|
CASCADE |
级联删除(删除引用对象时同时删除) |
PROTECT |
保护(阻止删除,抛出 ProtectedError) |
SET_NULL |
设为 NULL(需 null=True) |
SET_DEFAULT |
设为默认值(需 default=...) |
SET() |
设为指定值或函数返回值 |
DO_NOTHING |
不做任何操作(可能违反完整性约束) |
7.5 生成迁移
bash
root@ecs-60a4-0001:~# python3 manage.py makemigrations blog
Migrations for 'blog':
blog/migrations/0001_initial.py
+ Create model Category
+ Create model Tag
+ Create model Post
+ Create model Comment
7.6 执行迁移
bash
root@ecs-60a4-0001:~# python3 manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
...
Applying blog.0001_initial... OK
Applying sessions.0001_initial... OK
第八章 Django 视图
8.1 FBV(函数视图)
python
from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponse, JsonResponse
from .models import Post, Category
def index(request):
"""首页"""
posts = Post.objects.filter(status='published')[:5]
return HttpResponse(f'<h1>Blog Index</h1><p>Published posts: {posts.count()}</p>')
def post_detail(request, post_id):
"""文章详情"""
post = get_object_or_404(Post, pk=post_id)
post.view_count += 1
post.save()
return HttpResponse(f'<h1>{post.title}</h1><p>Views: {post.view_count}</p>')
def search(request):
"""搜索"""
query = request.GET.get('q', '')
if query:
results = Post.objects.filter(title__icontains=query)
return JsonResponse({
'query': query,
'count': results.count(),
'results': list(results.values('id', 'title'))
})
return JsonResponse({'error': 'No query provided'})
8.2 CBV(类视图)
python
from django.views import View
from django.views.generic import ListView, DetailView
class PostListView(View):
"""文章列表 - 基于 View"""
def get(self, request):
posts = Post.objects.filter(status='published')
data = [{'id': p.id, 'title': p.title, 'views': p.view_count} for p in posts]
return JsonResponse({'posts': data, 'count': len(data)})
def post(self, request):
return JsonResponse({'error': 'Method not allowed'}, status=405)
class PostDetailView(DetailView):
"""文章详情 - 基于通用视图"""
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
8.3 FBV vs CBV 对比
| 属性 | FBV | CBV |
|---|---|---|
| 定义方式 | def view(request): |
class View(View): |
| HTTP 方法 | if request.method == 'GET' |
def get(self, request) |
| 代码复用 | 低(需手动提取公共逻辑) | 高(继承 Mixin) |
| 装饰器 | @login_required |
@method_decorator(login_required) |
| Mixin | 不适用 | 可组合多个 Mixin |
| 可读性 | 简单直观 | 需要理解类视图体系 |
| 适用场景 | 简单逻辑 | RESTful API / CRUD |
| Django 内置 | render / redirect |
ListView/DetailView/CreateView 等 |
8.4 通用类视图
| 视图 | 说明 |
|---|---|
View |
基础类视图,需手动实现 HTTP 方法 |
TemplateView |
渲染模板 |
RedirectView |
重定向 |
ListView |
列表页(分页支持) |
DetailView |
详情页 |
CreateView |
创建(表单+模型) |
UpdateView |
更新(表单+模型) |
DeleteView |
删除(确认页面) |
FormView |
表单处理 |
第九章 Django 路由
9.1 URL 配置
python
# myproject/urls.py (项目级路由)
from django.contrib import admin
from django.urls import path, include
from django.http import HttpResponse
def home(request):
return HttpResponse('<h1>Django Tutorial Home</h1>')
def hello_world(request, name='World'):
return HttpResponse(f'<h2>Hello, {name}!</h2>')
urlpatterns = [
path('admin/', admin.site.urls),
path('', home, name='home'),
path('about/', about, name='about'),
path('hello/<str:name>/', hello_world, name='hello'),
path('blog/', include('blog.urls')),
]
python
# blog/urls.py (App 级路由)
from django.urls import path, re_path
from . import views
app_name = 'blog' # 命名空间
urlpatterns = [
path('', views.index, name='index'),
path('post/<int:post_id>/', views.post_detail, name='post_detail'),
path('category/<slug:slug>/', views.category_list, name='category_detail'),
path('search/', views.search, name='search'),
path('api/posts/', views.PostListView.as_view(), name='api_posts'),
path('api/posts/<int:pk>/', views.PostDetailView.as_view(), name='api_post_detail'),
re_path(r'^archive/(?P<year>[0-9]{4})/$', views.archive, name='archive'),
]
9.2 路径转换器
| 转换器 | 匹配规则 | 示例 URL | 示例值 |
|---|---|---|---|
str |
任意非空字符串(不含 /) |
/hello/Django/ |
Django |
int |
正整数 | /post/42/ |
42 |
slug |
ASCII 字母/数字/连字符/下划线 | /category/python/ |
python |
uuid |
UUID 格式 | /post/550e8400-e29b/ |
UUID 对象 |
path |
任意非空字符串(含 /) |
/files/a/b/c.txt |
a/b/c.txt |
9.3 URL 反解析
python
from django.urls import reverse
reverse('home') # '/'
reverse('about') # '/about/'
reverse('hello', kwargs={'name': 'Django'}) # '/hello/Django/'
reverse('blog:index') # '/blog/'
reverse('blog:search') # '/blog/search/'
9.4 path() vs re_path()
| 特性 | path() |
re_path() |
|---|---|---|
| 语法 | 路径转换器 <int:post_id> |
正则表达式 (?P<post_id>[0-9]+) |
| 可读性 | 高 | 低 |
| 灵活性 | 有限(5 种内置转换器) | 高(完全正则) |
| 自定义 | 可注册自定义转换器 | 不需要 |
| Django 版本 | 2.0+ | 所有版本 |
第十章 Django Admin 管理工具
10.1 注册模型
python
from django.contrib import admin
from .models import Category, Tag, Post, Comment
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'slug', 'description', 'created_at')
prepopulated_fields = {'slug': ('name',)}
search_fields = ('name', 'description')
list_filter = ('created_at',)
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'category', 'status', 'view_count', 'created_at')
list_filter = ('status', 'category', 'author', 'created_at')
search_fields = ('title', 'content')
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'created_at'
ordering = ['-created_at']
list_per_page = 20
raw_id_fields = ('author',)
filter_horizontal = ('tags',)
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ('name', 'email', 'post', 'active', 'created_at')
list_filter = ('active', 'created_at')
search_fields = ('name', 'email', 'body')
actions = ['approve_comments']
@admin.action(description='批准评论')
def approve_comments(self, request, queryset):
queryset.update(active=True)
10.2 Admin 配置属性
| 属性 | 说明 |
|---|---|
list_display |
列表页显示的字段 |
list_filter |
侧边栏过滤器 |
search_fields |
搜索框可搜索的字段 |
prepopulated_fields |
自动填充字段(如 slug 由 title 填充) |
date_hierarchy |
按日期分层导航 |
ordering |
默认排序 |
list_per_page |
每页显示数量 |
raw_id_fields |
外键使用 ID 输入框 |
filter_horizontal |
多对多字段水平选择器 |
list_editable |
列表页可直接编辑的字段 |
readonly_fields |
只读字段 |
fieldsets |
字段分组布局 |
actions |
批量操作 |
empty_value_display |
空值显示文本 |
10.3 已注册模型
rust
auth.group -> GroupAdmin
auth.user -> UserAdmin
blog.category -> CategoryAdmin
blog.tag -> TagAdmin
blog.post -> PostAdmin
blog.comment -> CommentAdmin
第十一章 ORM 单表操作
11.1 创建测试数据
python
# 创建用户
admin_user = User.objects.create_superuser('admin', 'admin@example.com', 'admin12345')
alice = User.objects.create_user('alice', 'alice@example.com', 'alice123')
bob = User.objects.create_user('bob', 'bob@example.com', 'bob123')
# 创建分类
Category.objects.create(name='Python', slug='python', description='Python 编程语言')
Category.objects.create(name='Django', slug='django', description='Django Web 框架')
Category.objects.create(name='Flask', slug='flask', description='Flask 轻量级框架')
Category.objects.create(name='Docker', slug='docker', description='容器化技术')
Category.objects.create(name='Linux', slug='linux', description='Linux 系统管理')
# 创建标签
for tag in ['入门', '进阶', '实战', '面试', '性能优化', '安全', '部署', '测试']:
Tag.objects.create(name=tag)
# 创建文章
Post.objects.create(title='Python3 基础教程', slug='python3-basic', author=alice,
category=cat_map['python'], status='published', view_count=150)
# ... 共 12 篇文章
11.2 CRUD 操作
python
# Create
new_post = Post.objects.create(
title='测试文章', slug='test-post', author=admin_user,
category=cat_map['python'], content='测试内容', status='draft'
)
# 输出: Create: 新建文章 ID=13, title='测试文章'
# Read
Post.objects.count() # 13 篇文章
Post.objects.get(slug='python3-basic').title # 'Python3 基础教程'
Post.objects.filter(status='published').count() # 10 篇已发布
Post.objects.filter(status='draft').count() # 3 篇草稿
# Update
new_post.title = '测试文章(已更新)'
new_post.status = 'published'
new_post.save()
# Delete
new_post.delete()
# 输出: Delete: 剩余 12 篇文章
11.3 字段查找(Field Lookups)
| 查找类型 | 查询条件 | 结果 | 说明 |
|---|---|---|---|
exact |
title__exact='Django 6.0 新特性' |
1 | 精确匹配 |
iexact |
title__iexact='django 6.0 新特性' |
1 | 不区分大小写精确匹配 |
contains |
title__contains='Django' |
4 | 包含(区分大小写) |
icontains |
title__icontains='django' |
4 | 包含(不区分大小写) |
startswith |
title__startswith='Python' |
3 | 以...开头 |
endswith |
title__endswith='实战' |
1 | 以...结尾 |
gt |
view_count__gt=300 |
6 | 大于 |
gte |
view_count__gte=300 |
6 | 大于等于 |
lt |
view_count__lt=100 |
2 | 小于 |
in |
status__in=['draft'] |
2 | 在列表中 |
range |
view_count__range=(200, 500) |
6 | 范围 |
11.4 Q 对象(复杂查询)
python
from django.db.models import Q
# OR 查询
Post.objects.filter(Q(title__contains='Django') | Q(title__contains='Flask')).count()
# 输出: 5 篇
# AND 查询
Post.objects.filter(Q(title__contains='Python') & Q(status='published')).count()
# 输出: 3 篇
# NOT 查询
Post.objects.filter(~Q(status='draft')).count()
# 输出: 10 篇非草稿
11.5 F 对象(字段间运算)
python
from django.db.models import F
# 使用 F 对象在数据库层面更新(避免竞态条件)
Post.objects.filter(slug='python3-basic').update(view_count=F('view_count') + 10)
# 更新前: 150
# 更新后: 160
11.6 排序、去重
python
# 排序
Post.objects.order_by('-view_count')[:3]
# [('Python 异步编程详解', 680), ('Flask vs Django 对比', 580), ('Python 装饰器原理与实践', 430)]
# 排除
Post.objects.exclude(status='draft').count() # 10 篇非草稿
# 去重
Post.objects.values_list('category', flat=True).distinct().count() # 5 个分类
第十二章 ORM 多表操作
12.1 外键关联查询
python
# 正向查询
cat = Category.objects.get(slug='django')
django_posts = cat.post_set.all()
# 输出: Category 'Django' 下有 3 篇文章
# - Django REST Framework 入门 (作者: bob)
# - Django ORM 深入解析 (作者: alice)
# - Django 6.0 新特性 (作者: alice)
# 反向查询
user = User.objects.get(username='alice')
user_posts = user.post_set.all()
# 输出: alice 的文章: 6 篇
# 输出: alice 的已发布文章: 5 篇
12.2 跨表查询(双下划线)
python
# Post -> Category
Post.objects.filter(category__name__contains='Docker').count()
# 输出: 2 篇
# Post -> User
Post.objects.filter(author__username='alice').count()
# 输出: 6 篇
12.3 查询优化
python
# select_related (外键 JOIN 优化 - 单次查询)
posts = Post.objects.select_related('author', 'category').filter(status='published')[:3]
for p in posts:
print(f" {p.title} | 作者: {p.author.username} | 分类: {p.category.name}")
# 输出:
# Python 装饰器原理与实践 | 作者: alice | 分类: Python
# Linux 系统监控方案 | 作者: bob | 分类: Linux
# Docker Compose 多容器编排 | 作者: alice | 分类: Docker
# prefetch_related (多对多优化 - 两次查询)
posts = Post.objects.prefetch_related('tags').filter(status='published')[:3]
for p in posts:
tag_names = ', '.join([t.name for t in p.tags.all()])
print(f" {p.title} | 标签: {tag_names}")
# 输出:
# Python 装饰器原理与实践 | 标签: 进阶, 面试
# Linux 系统监控方案 | 标签: 实战, 性能优化
# Docker Compose 多容器编排 | 标签: 实战, 部署
select_related vs prefetch_related:
| 特性 | select_related | prefetch_related |
|---|---|---|
| 适用关系 | 外键 / 一对一 | 多对多 / 反向外键 |
| SQL 策略 | JOIN(单次查询) | 子查询(两次查询) |
| 数据库负载 | 单次重查询 | 两次轻查询 |
| 适用场景 | 关联对象少 | 关联对象多 |
12.4 多对多操作
python
post = Post.objects.get(slug='python3-basic')
print(f"当前标签: {[t.name for t in post.tags.all()]}")
# 输出: ['入门', '面试']
# 添加标签
new_tag = Tag.objects.create(name='新标签')
post.tags.add(new_tag)
print(f"添加后: {[t.name for t in post.tags.all()]}")
# 输出: ['入门', '面试', '新标签']
# 移除标签
post.tags.remove(new_tag)
print(f"移除后: {[t.name for t in post.tags.all()]}")
# 输出: ['入门', '面试']
第十三章 ORM 聚合查询
13.1 aggregate(聚合)
python
from django.db.models import Count, Sum, Avg, Max, Min
agg = Post.objects.aggregate(
total=Count('id'),
published=Count('id', filter=Q(status='published')),
draft=Count('id', filter=Q(status='draft')),
total_views=Sum('view_count'),
avg_views=Avg('view_count'),
max_views=Max('view_count'),
min_views=Min('view_count'),
)
实机输出:
yaml
total: 12
published: 10
draft: 2
total_views: 3700
avg_views: 308.33
max_views: 680
min_views: 30
13.2 annotate(分组聚合)
按作者分组:
yaml
alice | 文章数: 6 | 总浏览: 1520
bob | 文章数: 6 | 总浏览: 2180
按分类分组:
yaml
Django | 文章数: 3 | 总浏览: 400
Docker | 文章数: 2 | 总浏览: 770
Flask | 文章数: 2 | 总浏览: 770
Linux | 文章数: 2 | 总浏览: 490
Python | 文章数: 3 | 总浏览: 1270
按标签分组:
入门 | 文章数: 4
进阶 | 文章数: 6
实战 | 文章数: 6
面试 | 文章数: 3
性能优化 | 文章数: 3
安全 | 文章数: 0
部署 | 文章数: 2
测试 | 文章数: 0
按状态分组:
draft | 数量: 2 | 平均浏览: 40
published | 数量: 10 | 平均浏览: 362
13.3 aggregate vs annotate
| 特性 | aggregate | annotate |
|---|---|---|
| 返回值 | 字典(聚合值) | QuerySet(每行附加聚合值) |
| 用途 | 整体统计 | 分组统计 |
| 示例 | 全站总浏览量 | 每个作者的总浏览量 |
| SQL | SELECT SUM(views) FROM post |
SELECT author_id, SUM(views) FROM post GROUP BY author_id |
第十四章 Django Form 组件
14.1 Form 定义
python
from django import forms
class PostForm(forms.Form):
title = forms.CharField(label='标题', max_length=200, min_length=3,
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入标题'}))
content = forms.CharField(label='内容', widget=forms.Textarea(attrs={'rows': 5}))
status = forms.ChoiceField(label='状态', choices=[('draft', '草稿'), ('published', '已发布')])
view_count = forms.IntegerField(label='浏览量', min_value=0, initial=0)
email = forms.EmailField(label='联系邮箱')
tags = forms.CharField(label='标签', required=False, help_text='多个标签用逗号分隔')
14.2 表单字段
| 字段名 | 类型 | label | required |
|---|---|---|---|
| title | CharField | 标题 | True |
| content | CharField | 内容 | True |
| status | ChoiceField | 状态 | True |
| view_count | IntegerField | 浏览量 | True |
| EmailField | 联系邮箱 | True | |
| tags | CharField | 标签 | False |
14.3 表单验证
有效数据验证:
python
valid_data = {
'title': 'Django Form 验证实例',
'content': '这是表单验证的测试内容,足够长。',
'status': 'published',
'view_count': 100,
'email': 'test@example.com',
'tags': 'Django, Form, 验证'
}
form = PostForm(valid_data)
form.is_valid() # True
form.cleaned_data # {'title': 'Django Form 验证实例', 'content': '...', ...}
无效数据验证:
python
invalid_data = {
'title': 'AB', # 太短 (min_length=3)
'content': '', # 必填
'status': 'invalid', # 非法选项
'view_count': -1, # 小于 min_value=0
'email': 'not-an-email' # 邮箱格式错误
}
form = PostForm(invalid_data)
form.is_valid() # False
form.errors
# {
# "title": ["Ensure this value has at least 3 characters (it has 2)."],
# "content": ["This field is required."],
# "status": ["Select a valid choice. invalid is not one of the available choices."],
# "view_count": ["Ensure this value is greater than or equal to 0."],
# "email": ["Enter a valid email address."]
# }
14.4 ModelForm
python
from django.forms import ModelForm
class PostModelForm(ModelForm):
class Meta:
model = Post
fields = ['title', 'slug', 'content', 'status', 'category']
labels = {
'title': '文章标题',
'slug': 'URL别名',
'content': '正文',
'status': '状态',
'category': '分类',
}
ModelForm 字段:
| 字段 | 表单类型 |
|---|---|
| title | CharField |
| slug | SlugField |
| content | CharField |
| status | TypedChoiceField |
| category | ModelChoiceField |
第十五章 Django Auth 认证系统
15.1 用户管理
ini
总用户数: 3
admin | staff=True | superuser=True | active=True
alice | staff=False | superuser=False | active=True
bob | staff=False | superuser=False | active=True
15.2 认证
python
from django.contrib.auth import authenticate
user = authenticate(username='admin', password='admin12345')
# 输出: admin
user = authenticate(username='admin', password='wrong')
# 输出: None
15.3 权限系统
sql
总权限数: 40
Blog app 权限:
add_category | Can add 分类
change_category | Can change 分类
delete_category | Can delete 分类
view_category | Can view 分类
add_post | Can add 文章
change_post | Can change 文章
delete_post | Can delete 文章
view_post | Can view 文章
...
15.4 用户组与权限
python
# 创建用户组
editors_group = Group.objects.create(name='编辑组')
# 添加权限到组
perm_add = Permission.objects.get(codename='add_post')
perm_del = Permission.objects.get(codename='delete_post')
editors_group.permissions.add(perm_add, perm_del)
# 编辑组权限: ['add_post', 'delete_post']
# 用户加入组
bob.groups.add(editors_group)
# bob 加入编辑组后权限: {'blog.add_post', 'blog.delete_post'}
15.5 密码管理
python
# 密码哈希算法
alice.password.split('$')[0] # 'pbkdf2_sha256'
# 检查密码
alice.check_password('alice123') # True/False
# 设置密码
alice.set_password('newpassword123')
alice.save()
alice.check_password('newpassword123') # True
15.6 模拟登录
python
# 模拟登录流程
request.session['_auth_user_id'] = str(user.pk)
request.session['_auth_user_backend'] = 'django.contrib.auth.backends.ModelBackend'
request.session.save()
# 输出: 登录成功: admin
# 输出: is_authenticated: True
# 登出
request.session.flush()
# 输出: 登出后 session 已清空
第十六章 Cookie 与 Session
16.1 Session 配置
| 配置项 | 值 |
|---|---|
| Session 引擎 | django.contrib.sessions.backends.db |
| Session Cookie 名 | sessionid |
| Session 有效期 | 1209600 秒(14 天) |
16.2 Session 操作
python
from django.contrib.sessions.backends.db import SessionStore
# 创建 session
request.session = SessionStore()
request.session['user_id'] = 42
request.session['preferences'] = {'theme': 'dark', 'lang': 'zh'}
request.session['cart'] = ['item1', 'item2', 'item3']
request.session.save()
# Session Key: orm3m8bkagwxy43n28p5gek95jsh4neu
# 读取 session
request2.session = SessionStore(session_key=request.session.session_key)
request2.session.get('user_id') # 42
request2.session.get('preferences') # {'theme': 'dark', 'lang': 'zh'}
request2.session.get('cart') # ['item1', 'item2', 'item3']
16.3 Cookie 操作
python
from django.http import HttpResponse
response = HttpResponse("Hello")
# 设置 Cookie
response.set_cookie('username', 'django_user', max_age=3600)
response.set_cookie('theme', 'dark', max_age=86400, httponly=True, samesite='Lax')
response.set_signed_cookie('secret', 'sensitive_data')
# 读取 Cookie
request.COOKIES.get('username') # 'django_user'
16.4 Cookie vs Session 对比
| 属性 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端浏览器 | 服务器端 |
| 安全性 | 低(可被篡改) | 高(客户端仅存 session ID) |
| 大小限制 | 4KB | 无限制(取决于服务器) |
| 生命周期 | 可设置 max_age/expires | 可设置 SESSION_COOKIE_AGE |
| 数据类型 | 字符串 | 任意可序列化对象 |
| API | response.set_cookie / request.COOKIES |
request.session['key'] = value |
第十七章 中间件
17.1 中间件执行顺序
rust
请求 -> SecurityMiddleware -> SessionMiddleware -> CommonMiddleware
-> CsrfViewMiddleware -> AuthenticationMiddleware -> MessageMiddleware
-> XFrameOptionsMiddleware -> [自定义中间件] -> 视图函数
响应 <- SecurityMiddleware <- SessionMiddleware <- CommonMiddleware
<- CsrfViewMiddleware <- AuthenticationMiddleware <- MessageMiddleware
<- XFrameOptionsMiddleware <- [自定义中间件] <- 视图函数
17.2 自定义中间件
python
import time
class TimingMiddleware:
"""请求计时中间件"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 请求前处理
start_time = time.time()
request.start_time = start_time
response = self.get_response(request)
# 请求后处理
duration = time.time() - start_time
response['X-Request-Duration'] = f'{duration:.4f}s'
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print(f'[TimingMiddleware] 视图函数: {view_func.__name__}')
return None
def process_exception(self, request, exception):
print(f'[TimingMiddleware] 异常: {exception}')
return None
class RequestLogMiddleware:
"""请求日志中间件"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
method = request.method
path = request.path
print(f'[RequestLog] {method} {path}')
response = self.get_response(request)
status = response.status_code
print(f'[RequestLog] {method} {path} -> {status}')
return response
17.3 中间件钩子方法
| 方法 | 说明 |
|---|---|
__init__(get_response) |
初始化,接收 get_response 回调 |
__call__(request) |
处理每个请求,调用视图前后的逻辑 |
process_view(request, view_func, ...) |
视图函数执行前调用 |
process_exception(request, exception) |
视图抛出异常时调用 |
process_template_response(request, response) |
模板响应处理 |
第十八章 FBV 与 CBV 实测
18.1 功能测试结果
使用 Django Test Client 对所有路由进行了测试:
| 测试项 | URL | 方法 | 状态码 | 结果 |
|---|---|---|---|---|
| 首页 | / |
GET | 200 | <h1>Django Tutorial Home</h1> |
| About | /about/ |
GET | 200 | <h2>About Page</h2> |
| Hello | /hello/Django/ |
GET | 200 | <h2>Hello, Django!</h2> |
| 博客列表 | /blog/ |
GET | 200 | Published posts: 5 |
| 文章详情 | /blog/post/12/ |
GET | 200 | Views: 31 |
| 分类页 | /blog/category/linux/ |
GET | 200 | Posts: 2 |
| 搜索 | /blog/search/?q=Python |
GET | 200 | count=3 |
| API 列表 (CBV) | /blog/api/posts/ |
GET | 200 | count=10 |
| API POST (CBV) | /blog/api/posts/ |
POST | 405 | Method not allowed |
| 归档 | /blog/archive/2026/ |
GET | 200 | Posts: 12 |
| 不存在 | /blog/post/9999/ |
GET | 404 | Not Found |
18.2 搜索 API 返回示例
json
{
"query": "Python",
"count": 3,
"results": [
{"id": 11, "title": "Python 装饰器原理与实践"},
{"id": 6, "title": "Python 异步编程详解"},
{"id": 1, "title": "Python3 基础教程"}
]
}
18.3 API 列表 (CBV) 返回示例
json
{
"posts": [
{"id": 11, "title": "Python 装饰器原理与实践", "views": 430},
{"id": 10, "title": "Linux 系统监控方案", "views": 280},
{"id": 9, "title": "Docker Compose 多容器编排", "views": 350}
],
"count": 10
}
第十九章 请求生命周期
sql
用户请求
|
v
+-------------------+
| Web 服务器 | Nginx / Apache
| (HTTP -> WSGI) |
+-------------------+
|
v
+-------------------+
| WSGI 服务器 | Gunicorn / uWSGI
| (WSGI -> Django) |
+-------------------+
|
v
+-------------------+
| 中间件 (请求阶段) | SecurityMiddleware
| | SessionMiddleware
| | CommonMiddleware
| | CsrfViewMiddleware
| | AuthenticationMiddleware
| | MessageMiddleware
+-------------------+
|
v
+-------------------+
| URL 路由 | ROOT_URLCONF -> urls.py
| (URL -> View) | path('blog/', include('blog.urls'))
+-------------------+
|
v
+-------------------+
| 视图函数 | FBV: def view(request)
| (处理业务逻辑) | CBV: View.dispatch() -> get()/post()
+-------------------+
|
v
+-------------------+
| 模型层 | ORM -> SQL -> Database
| (数据库操作) | Post.objects.filter(...)
+-------------------+
|
v
+-------------------+
| 模板渲染 | render(request, 'template.html', context)
| (HTML 生成) | DTL -> HTML
+-------------------+
|
v
+-------------------+
| 中间件 (响应阶段) | XFrameOptionsMiddleware
| | MessageMiddleware
| | ...
+-------------------+
|
v
+-------------------+
| HTTP 响应 | HttpResponse / JsonResponse
+-------------------+
|
v
用户浏览器
第二十章 实战博客项目
20.1 数据统计
| 数据项 | 数量 |
|---|---|
| 用户 | 3 |
| 分类 | 5 |
| 标签 | 8 |
| 文章 | 12 |
| 已发布 | 10 |
| 草稿 | 2 |
| 评论 | 4 |
| 显示评论 | 3 |
| 隐藏评论 | 1 |
| 总浏览量 | 3702 |
20.2 项目结构
csharp
myproject/
manage.py
myproject/
settings.py
urls.py
blog/
models.py # Category, Tag, Post, Comment
views.py # FBV + CBV
urls.py
admin.py
forms.py # PostSearchForm, CommentForm
middleware.py # TimingMiddleware, RequestLogMiddleware
tests.py # 12 个单元测试
templates/
blog/
base.html
post_list.html
post_detail.html
search.html
static/
css/style.css
20.3 forms.py
python
from django import forms
from .models import Post, Comment
class PostSearchForm(forms.Form):
q = forms.CharField(label='搜索', max_length=200, required=False,
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '搜索文章...'}))
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['name', 'email', 'body']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'email': forms.EmailInput(attrs={'class': 'form-control'}),
'body': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
}
第二十一章 Nginx + uWSGI 部署
21.1 部署架构
lua
+-------------------+
| 互联网用户 |
+---------+---------+
|
v
+---------+---------+
| Nginx (80) |
| 反向代理 + 静态 |
+---------+---------+
|
+-------+-------+
| |
v v
+-------+---+ +-------+---+
| uWSGI:8001| | uWSGI:8002| (多进程)
+-------+---+ +-------+---+
| |
+-------+-------+
|
v
+---------+---------+
| Django (WSGI) |
+---------+---------+
|
v
+---------+---------+
| SQLite / PG |
+-------------------+
21.2 Nginx 配置
nginx
# /etc/nginx/sites-available/django_blog
server {
listen 80;
server_name example.com;
charset utf-8;
# 静态文件
location /static/ {
alias /opt/django_blog/staticfiles/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# 媒体文件
location /media/ {
alias /opt/django_blog/media/;
expires 7d;
}
# 反向代理到 uWSGI
location / {
uwsgi_pass unix:///tmp/django_blog.sock;
include /etc/nginx/uwsgi_params;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
21.3 uWSGI 配置
ini
# /opt/django_blog/uwsgi.ini
[uwsgi]
chdir = /opt/django_blog
module = myproject.wsgi:application
processes = 4
threads = 2
socket = /tmp/django_blog.sock
chmod-socket = 664
vacuum = true
daemonize = /var/log/uwsgi/django_blog.log
pidfile = /tmp/django_blog.pid
home = /opt/venv
py-autoreload = 1
buffer-size = 65536
21.4 部署命令
bash
# 1. 收集静态文件
python3 manage.py collectstatic --noinput
# 2. 迁移数据库
python3 manage.py migrate
# 3. 启动 uWSGI
uwsgi --ini /opt/django_blog/uwsgi.ini
# 4. 重启 Nginx
nginx -t && systemctl restart nginx
# 5. 管理命令
uwsgi --reload /tmp/django_blog.pid # 重载
uwsgi --stop /tmp/django_blog.pid # 停止
21.5 Gunicorn 部署(替代方案)
bash
# 安装
pip install gunicorn
# 启动(生产模式)
gunicorn myproject.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 4 \
--threads 2 \
--timeout 120 \
--access-logfile /var/log/gunicorn/access.log \
--error-logfile /var/log/gunicorn/error.log \
--pid /tmp/gunicorn.pid \
--daemon
21.6 Docker 部署
dockerfile
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
RUN python manage.py migrate
EXPOSE 8000
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]
yaml
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
environment:
- DEBUG=0
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_DB: django_blog
POSTGRES_USER: django
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./staticfiles:/var/www/static
depends_on:
- web
volumes:
pgdata:
第二十二章 单元测试
22.1 测试代码
python
from django.test import TestCase, Client
from django.contrib.auth.models import User
from .models import Category, Tag, Post, Comment
from .forms import PostSearchForm
class ModelTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user('testuser', 'test@test.com', 'password123')
cls.category = Category.objects.create(name='Test', slug='test')
cls.tag = Tag.objects.create(name='test-tag')
cls.post = Post.objects.create(
title='Test Post', slug='test-post', author=cls.user,
category=cls.category, content='Test content', status='published'
)
cls.post.tags.add(cls.tag)
def test_category_str(self):
self.assertEqual(str(self.category), 'Test')
def test_post_str(self):
self.assertEqual(str(self.post), 'Test Post')
def test_post_default_status(self):
new_post = Post.objects.create(
title='Draft', slug='draft', author=self.user, content='content'
)
self.assertEqual(new_post.status, 'draft')
def test_post_view_count_default(self):
self.assertEqual(self.post.view_count, 0)
class ViewTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user('testuser', 'test@test.com', 'password123')
cls.category = Category.objects.create(name='Test', slug='test')
cls.post = Post.objects.create(
title='Test Post', slug='test-post', author=cls.user,
category=cls.category, content='Test content', status='published'
)
def test_home_page(self):
response = self.client.get('/')
self.assertEqual(response.status_code, 200)
def test_blog_index(self):
response = self.client.get('/blog/')
self.assertEqual(response.status_code, 200)
def test_post_detail(self):
response = self.client.get(f'/blog/post/{self.post.id}/')
self.assertEqual(response.status_code, 200)
def test_post_not_found(self):
response = self.client.get('/blog/post/9999/')
self.assertEqual(response.status_code, 404)
def test_search(self):
response = self.client.get('/blog/search/?q=Test')
self.assertEqual(response.status_code, 200)
def test_api_posts(self):
response = self.client.get('/blog/api/posts/')
self.assertEqual(response.status_code, 200)
class FormTest(TestCase):
def test_search_form_valid(self):
form = PostSearchForm({'q': 'Django'})
self.assertTrue(form.is_valid())
def test_search_form_empty(self):
form = PostSearchForm({'q': ''})
self.assertTrue(form.is_valid()) # q is optional
22.2 测试结果
bash
root@ecs-60a4-0001:~# python3 manage.py test blog -v 2
Found 12 test(s).
Creating test database for alias 'default'...
test_search_form_empty (blog.tests.FormTest) ... ok
test_search_form_valid (blog.tests.FormTest) ... ok
test_category_str (blog.tests.ModelTest) ... ok
test_post_default_status (blog.tests.ModelTest) ... ok
test_post_view_count_default (blog.tests.ModelTest) ... ok
test_post_str (blog.tests.ModelTest) ... ok
test_api_posts (blog.tests.ViewTest) ... ok
test_blog_index (blog.tests.ViewTest) ... ok
test_home_page (blog.tests.ViewTest) ... ok
test_post_detail (blog.tests.ViewTest) ... ok
test_post_not_found (blog.tests.ViewTest) ... ok
test_search (blog.tests.ViewTest) ... ok
Ran 12 tests in 0.234s
OK
Destroying test database for alias 'default'...
12 个测试全部通过!
| 测试类 | 测试数 | 状态 |
|---|---|---|
| ModelTest | 4 | 全部通过 |
| ViewTest | 6 | 全部通过 |
| FormTest | 2 | 全部通过 |
| 合计 | 12 | 全部通过 |
第二十三章 配置管理
23.1 settings.py 关键配置项
| 配置项 | 值 | 说明 |
|---|---|---|
SECRET_KEY |
(保密) | 密钥(生产环境必须保密) |
DEBUG |
True |
调试模式 |
ALLOWED_HOSTS |
['*'] |
允许的主机 |
INSTALLED_APPS |
7 个 | 已安装应用 |
MIDDLEWARE |
7 个 | 中间件 |
ROOT_URLCONF |
myproject.urls |
根 URL 配置 |
DATABASES |
SQLite | 数据库引擎 |
LANGUAGE_CODE |
en-us |
语言 |
TIME_ZONE |
UTC |
时区 |
USE_TZ |
True |
使用时区 |
STATIC_URL |
/static/ |
静态文件 URL |
SESSION_ENGINE |
db |
Session 引擎 |
SESSION_COOKIE_AGE |
14 天 | Session 有效期 |
CSRF_COOKIE_NAME |
csrftoken |
CSRF Cookie |
DEFAULT_AUTO_FIELD |
BigAutoField |
默认主键字段 |
23.2 生产环境配置清单
python
# settings/prod.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = ['example.com', 'www.example.com']
# 数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'django_blog',
'USER': 'django',
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': 'localhost',
'PORT': '5432',
}
}
# 静态文件
STATIC_ROOT = '/opt/django_blog/staticfiles/'
STATIC_URL = '/static/'
# 安全
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
# 日志
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'WARNING',
'class': 'logging.FileHandler',
'filename': '/var/log/django/django.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'WARNING',
'propagate': True,
},
},
}
踩坑记录
踩坑 1:Ubuntu 24.04 PEP 668 保护
现象 :pip3 install django 报错 externally-managed-environment
原因:Ubuntu 24.04 遵循 PEP 668,禁止 pip 直接安装到系统环境
解决 :加 --break-system-packages 标志
bash
pip3 install --break-system-packages django
踩坑 2:pip 下载超时
现象:从 PyPI 官方源下载 Django 8.4MB 超时
原因:华为云服务器访问 files.pythonhosted.org 延迟高
解决:使用清华镜像源
bash
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple django
踩坑 3:ALLOWED_HOSTS 限制
现象:使用 Django Test Client 请求返回 400 Bad Request
原因 :ALLOWED_HOSTS 默认为 ['example.com'],不包含 testserver
解决 :在 settings.py 中设置 ALLOWED_HOSTS = ['*'] 或在测试中动态设置
python
from django.conf import settings
settings.ALLOWED_HOSTS = ['*']
踩坑 4:用户权限缓存
现象 :给用户添加权限后 has_perm() 仍返回 False
原因:Django 会缓存用户权限,修改后需要重新从数据库获取
解决:重新获取用户对象
python
alice.user_permissions.add(perm)
alice.save()
alice = User.objects.get(username='alice') # 重新获取刷新缓存
alice.has_perm('blog.add_post') # True
踩坑 5:RequestFactory session 不完整
现象 :使用 request.session = {} 调用 login() 报错 AttributeError: 'dict' object has no attribute 'cycle_key'
原因 :login() 需要 SessionStore 对象,不能是普通 dict
解决 :使用 SessionStore 或手动设置 session 字段
python
from django.contrib.sessions.backends.db import SessionStore
request.session = SessionStore()
# 或手动设置
request.session['_auth_user_id'] = str(user.pk)
踩坑 6:blog URL namespace 未注册
现象 :reverse('blog:index') 报错 NoReverseMatch: 'blog' is not a registered namespace
原因 :blog/urls.py 中缺少 app_name = 'blog'
解决:在 blog/urls.py 顶部添加
python
app_name = 'blog' # 命名空间
总结
本教程在华为云 FlexusX ecs-60a4-0001(Ubuntu 24.04 / Python 3.12.3 / Django 6.0.6)上完成了从入门到实战的全部实验:
| 篇章 | 实验 | 实机验证 |
|---|---|---|
| 入门篇 | 安装、创建项目、django-admin、项目结构 | 4 个实验 |
| 核心篇 | 模板(22个过滤器)、模型(4个模型)、表单、视图、路由、Admin | 6 个实验 |
| ORM篇 | 单表CRUD、多表查询、聚合统计 | 3 个实验 |
| 进阶篇 | Form组件、Auth、Cookie/Session、中间件、FBV/CBV | 5 个实验 |
| 实战篇 | 博客项目、Nginx+uWSGI部署、Docker部署、单元测试 | 4 个实验 |
| 合计 | 22 个实验 | 12 个测试全部通过 |
技术栈:Django 6.0.6 + Python 3.12.3 + SQLite + DTL + ORM + Admin + Auth + Forms + Middleware + Nginx + uWSGI/Gunicorn + Docker