Python 框架 Django 从入门到精通

Python 框架 Django 从入门到精通

目录


一、Django 简介

1.1 什么是 Django

Django 是一个基于 Python 的高级 Web 框架,遵循 DRY(Don't Repeat Yourself)原则,鼓励快速开发和简洁实用的设计。它由劳伦斯出版集团开发,最初用于管理其新闻网站的内容,于 2005 年开源。

1.2 核心特性

特性 说明
ORM 对象关系映射,用 Python 类操作数据库
Admin 开箱即用的后台管理系统
URL 路由 灵活的 URL 配置系统
模板引擎 内置模板系统,支持继承和标签
Middleware 请求/响应钩子机制
国际化 内置多语言支持
安全性 自动防范 CSRF、XSS、SQL 注入等攻击

1.3 MVT 架构

Django 采用 MVT(Model-View-Template)架构:

复制代码
┌─────────┐     ┌─────────┐     ┌───────────┐     ┌─────────┐
│  用户   │────>│  URL    │────>│  View     │────>│ Model   │
│ 浏览器  │<────│ Router  │<────│ 业务逻辑  │<────│ 数据访问 │
└─────────┘     └─────────┘     └─────┬─────┘     └─────────┘
                                     │
                                     v
                              ┌───────────┐
                              │ Template  │
                              │  HTML渲染  │
                              └───────────┘
  • Model:数据模型,定义数据库表结构
  • View:业务逻辑,处理请求并返回响应
  • Template:前端模板,渲染 HTML 页面

二、环境搭建

2.1 创建虚拟环境

bash 复制代码
# 创建虚拟环境
python -m venv myenv

# 激活(macOS / Linux)
source myenv/bin/activate

# 激活(Windows)
myenv\Scripts\activate

# 退出虚拟环境
deactivate

2.2 安装 Django

bash 复制代码
pip install django==5.1

# 验证安装
python -m django --version

2.3 项目与应用的关系

复制代码
myproject/                  # 项目容器
├── manage.py               # 命令行工具
├── myproject/              # 项目配置目录
│   ├── __init__.py
│   ├── settings.py         # 项目配置
│   ├── urls.py             # 根 URL 配置
│   ├── asgi.py             # ASGI 入口
│   └── wsgi.py             # WSGI 入口
├── app1/                   # 应用 1
│   ├── models.py
│   ├── views.py
│   ├── urls.py
│   └── ...
└── app2/                   # 应用 2
    └── ...

一个 Django 项目可以包含多个应用,每个应用负责一个独立的功能模块。


三、项目创建与结构解析

3.1 创建项目

bash 复制代码
django-admin startproject myproject
cd myproject

3.2 创建应用

bash 复制代码
python manage.py startapp blog

3.3 注册应用

settings.py 中注册:

python 复制代码
# myproject/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',  # 注册自定义应用
]

3.4 常用管理命令

bash 复制代码
# 启动开发服务器
python manage.py runserver

# 指定端口
python manage.py runserver 0.0.0.0:8000

# 数据库迁移
python manage.py makemigrations
python manage.py migrate

# 创建超级管理员
python manage.py createsuperuser

# 启动 Shell
python manage.py shell

# 收集静态文件
python manage.py collectstatic

# 查看已注册的 SQL
python manage.py sqlmigrate blog 0001

四、URL 路由系统

4.1 基本路由配置

python 复制代码
# blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('articles/', views.article_list, name='article_list'),
    path('articles/<int:id>/', views.article_detail, name='article_detail'),
    path('articles/<int:year>/<int:month>/', views.article_archive, name='article_archive'),
]

4.2 根路由分发

python 复制代码
# myproject/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')),
    path('api/', include('api.urls')),
]

4.3 路径转换器

python 复制代码
urlpatterns = [
    path('articles/<int:pk>/', views.detail),           # 整数
    path('articles/<slug:title>/', views.detail),        # slug 字符串
    path('articles/<uuid:id>/', views.detail),           # UUID
    path('files/<path:filepath>/', views.download),     # 路径(含 /)
]

4.4 使用 re_path 正则匹配

python 复制代码
from django.urls import re_path

urlpatterns = [
    re_path(r'^articles/(?P<year>\d{4})/$', views.year_archive),
    re_path(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', views.month_archive),
]

4.5 反向解析 URL

在模板中:

html 复制代码
<a href="{% url 'article_detail' id=article.id %}">查看详情</a>

在视图/模型中:

python 复制代码
from django.urls import reverse
from django.shortcuts import redirect

def create_article(request):
    article = Article.objects.create(title="test")
    return redirect(reverse('article_detail', args=[article.id]))

# 或简写
    return redirect('article_detail', pk=article.id)

4.6 include 的 namespace

python 复制代码
# myproject/urls.py
urlpatterns = [
    path('blog/', include('blog.urls', namespace='blog')),
]

# 模板中使用
<a href="{% url 'blog:article_detail' pk=1 %}">详情</a>

五、视图层(Views)

5.1 函数视图(FBV)

python 复制代码
from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponse, JsonResponse, Http404
from .models import Article

def article_list(request):
    articles = Article.objects.all().order_by('-created_at')
    return render(request, 'blog/article_list.html', {'articles': articles})

def article_detail(request, pk):
    article = get_object_or_404(Article, pk=pk)
    return render(request, 'blog/article_detail.html', {'article': article})

def create_article(request):
    if request.method == 'POST':
        title = request.POST.get('title')
        content = request.POST.get('content')
        Article.objects.create(title=title, content=content)
        return redirect('article_list')
    return render(request, 'blog/article_form.html')

5.2 请求对象(HttpRequest)

python 复制代码
def demo(request):
    # 请求方法
    request.method          # 'GET', 'POST', 'PUT', 'DELETE'

    # GET 参数
    request.GET.get('page', 1)

    # POST 数据(表单)
    request.POST.get('username')

    # JSON 请求体
    import json
    data = json.loads(request.body)

    # 文件上传
    file = request.FILES.get('avatar')

    # 请求头
    request.META.get('HTTP_USER_AGENT')

    # Cookies
    request.COOKIES.get('session_id')

    # 用户信息
    request.user            # 当前登录用户(需要中间件)

    # 完整 URL
    request.get_full_path()

5.3 响应对象

python 复制代码
# HTML 响应
from django.shortcuts import render
return render(request, 'template.html', context={'key': 'value'})

# JSON 响应
from django.http import JsonResponse
return JsonResponse({'status': 'ok', 'data': list})

# 重定向
from django.shortcuts import redirect
return redirect('/some/url/')
return redirect('named_url', arg1=value1)

# 文件下载
from django.http import FileResponse
return FileResponse(open('file.pdf', 'rb'), as_attachment=True)

# 自定义响应头
from django.http import HttpResponse
response = HttpResponse('content')
response['X-Custom-Header'] = 'value'
return response

5.4 类视图(CBV)

python 复制代码
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import Article
from .forms import ArticleForm

class ArticleListView(ListView):
    model = Article
    template_name = 'blog/article_list.html'
    context_object_name = 'articles'
    paginate_by = 10
    ordering = ['-created_at']

class ArticleDetailView(DetailView):
    model = Article
    template_name = 'blog/article_detail.html'
    context_object_name = 'article'

class ArticleCreateView(CreateView):
    model = Article
    form_class = ArticleForm
    template_name = 'blog/article_form.html'
    success_url = reverse_lazy('article_list')

class ArticleUpdateView(UpdateView):
    model = Article
    form_class = ArticleForm
    template_name = 'blog/article_form.html'

class ArticleDeleteView(DeleteView):
    model = Article
    success_url = reverse_lazy('article_list')

CBV 路由配置:

python 复制代码
urlpatterns = [
    path('articles/', ArticleListView.as_view(), name='article_list'),
    path('articles/<int:pk>/', ArticleDetailView.as_view(), name='article_detail'),
    path('articles/create/', ArticleCreateView.as_view(), name='article_create'),
    path('articles/<int:pk>/edit/', ArticleUpdateView.as_view(), name='article_update'),
    path('articles/<int:pk>/delete/', ArticleDeleteView.as_view(), name='article_delete'),
]

5.5 CBV Mixin 扩展

python 复制代码
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import UpdateView

class ArticleUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Article
    form_class = ArticleForm

    def test_func(self):
        """只有文章作者才能编辑"""
        return self.get_object().author == self.request.user

    def form_valid(self, form):
        """自动设置作者"""
        form.instance.author = self.request.user
        return super().form_valid(form)

5.6 装饰器

python 复制代码
from django.contrib.auth.decorators import login_required, permission_required
from django.views.decorators.http import require_http_methods, require_POST
from django.views.decorators.csrf import csrf_exempt

@login_required(login_url='/login/')
def dashboard(request):
    ...

@permission_required('blog.change_article', raise_exception=True)
def edit_article(request, pk):
    ...

@require_POST
def like_article(request, pk):
    ...

@csrf_exempt  # 豁免 CSRF 验证(用于 API)
def api_endpoint(request):
    ...

六、模板层(Templates)

6.1 模板配置

python 复制代码
# settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],     # 模板目录
        'APP_DIRS': True,                      # 自动搜索各 app 的 templates 目录
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

6.2 模板继承

html 复制代码
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}My Site{% endblock %}</title>
    {% load static %}
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
    {% block extra_css %}{% endblock %}
</head>
<body>
    <header>{% include 'header.html' %}</header>

    <main>
        {% block content %}{% endblock %}
    </main>

    <footer>{% include 'footer.html' %}</footer>

    {% block extra_js %}{% endblock %}
</body>
</html>
html 复制代码
<!-- blog/templates/blog/article_list.html -->
{% extends 'base.html' %}

{% block title %}文章列表 - My Blog{% endblock %}

{% block content %}
<h1>文章列表</h1>
<ul>
    {% for article in articles %}
    <li>
        <a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
        <span>{{ article.created_at|date:"Y-m-d" }}</span>
    </li>
    {% empty %}
    <li>暂无文章</li>
    {% endfor %}
</ul>
{% endblock %}

6.3 常用模板标签和过滤器

html 复制代码
<!-- 变量输出 -->
{{ article.title }}

<!-- 过滤器 -->
{{ content|truncatewords:30 }}          <!-- 截断为 30 个单词 -->
{{ price|floatformat:2 }}              <!-- 保留两位小数 -->
{{ date|date:"Y年m月d日" }}            <!-- 日期格式化 -->
{{ name|default:"匿名" }}              <!-- 默认值 -->
{{ content|safe }}                      <!-- 渲染 HTML(不转义) -->
{{ items|length }}                      <!-- 长度 -->
{{ list|join:", " }}                   <!-- 用逗号连接 -->
{{ value|upper }}                       <!-- 转大写 -->
{{ content|linebreaks }}                <!-- 换行转 <br> -->
{{ value|filesizeformat }}              <!-- 文件大小格式化 -->

<!-- 逻辑标签 -->
{% if user.is_authenticated %}
    <p>欢迎, {{ user.username }}</p>
{% else %}
    <p>请 <a href="{% url 'login' %}">登录</a></p>
{% endif %}

{% for item in items %}
    {{ forloop.counter }}. {{ item.name }}
{% empty %}
    暂无数据
{% endfor %}

{% for i in "xxxx" %}
    {{ i }}
{% endfor %}

<!-- URL 反向解析 -->
{% url 'article_detail' pk=article.id %}

<!-- 静态文件 -->
{% load static %}
<link href="{% static 'css/style.css' %}" rel="stylesheet">
<img src="{% static 'images/logo.png' %}">

<!-- 注释 -->
{# 单行注释 #}
{% comment %}
多行注释
{% endcomment %}

<!-- 自定义标签/过滤器需要先 load -->
{% load my_tags %}
{{ value|my_filter }}
{% my_tag arg1 arg2 %}

6.4 自定义模板过滤器

python 复制代码
# blog/templatetags/__init__.py        # 空文件
# blog/templatetags/blog_extras.py

from django import template

register = template.Library()

@register.filter
def truncate_chars(value, max_length):
    if len(value) > max_length:
        return value[:max_length] + '...'
    return value

@register.filter(name='add_class')
def add_css_class(value, css_class):
    return value.as_widget(attrs={'class': css_class})

@register.simple_tag
def get_recent_articles(count=5):
    from blog.models import Article
    return Article.objects.order_by('-created_at')[:count]

模板中使用:

html 复制代码
{% load blog_extras %}

{{ article.content|truncate_chars:100 }}
{{ form.username|add_class:"form-control" }}
{% get_recent_articles 10 as recent %}

七、模型层(Models)与 ORM

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, unique=True)
    description = models.TextField('描述', blank=True, default='')
    created_at = models.DateTimeField('创建时间', auto_now_add=True)

    class Meta:
        verbose_name = '分类'
        verbose_name_plural = '分类'
        ordering = ['name']

    def __str__(self):
        return self.name


class Article(models.Model):
    STATUS_CHOICES = [
        ('draft', '草稿'),
        ('published', '已发布'),
        ('archived', '已归档'),
    ]

    title = models.CharField('标题', max_length=200)
    content = models.TextField('内容')
    summary = models.CharField('摘要', max_length=500, blank=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='作者')
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
    tags = models.ManyToManyField('Tag', blank=True, verbose_name='标签')
    status = models.CharField('状态', max_length=20, 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)
    is_deleted = models.BooleanField('已删除', default=False)

    class Meta:
        verbose_name = '文章'
        verbose_name_plural = '文章'
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['-created_at']),
            models.Index(fields=['author', 'status']),
        ]

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        from django.urls import reverse
        return reverse('article_detail', args=[self.id])

7.2 字段类型大全

python 复制代码
# 字符串
CharField(max_length=N)           # 短字符串
TextField()                       # 长文本

# 数字
IntegerField()                     # 整数
BigIntegerField()                  # 大整数
FloatField()                      # 浮点数
DecimalField(max_digits=N, decimal_places=M)  # 精确小数
PositiveIntegerField()            # 正整数
PositiveSmallIntegerField()       # 小正整数

# 布尔
BooleanField()                    # 布尔值
NullBooleanField()                 # 可为空的布尔值(Django 4+ 已废弃,用 BooleanField(null=True))

# 日期时间
DateField()                       # 日期
DateTimeField()                   # 日期时间
TimeField()                       # 时间
DurationField()                   # 时间间隔

# 文件
FileField(upload_to='uploads/')   # 文件
ImageField(upload_to='images/')   # 图片(需要 Pillow)

# 关系字段
ForeignKey(Model, on_delete=...)   # 多对一
OneToOneField(Model, on_delete=...)  # 一对一
ManyToManyField(Model)             # 多对多

# 特殊
UUIDField()                       # UUID
SlugField()                       # URL 友好字符串
GenericIPAddressField()            # IP 地址
JSONField()                       # JSON 数据
BinaryField()                     # 二进制数据

# 常用参数
null=True                         # 数据库允许 NULL
blank=True                        # 表单验证允许为空
default=N                         # 默认值
unique=True                       # 唯一约束
db_index=True                     # 数据库索引
choices=[...]                     # 选项
verbose_name='中文'                # 字段人类可读名称
help_text='帮助文本'               # 表单帮助文本
auto_now=True                     # 每次保存自动更新
auto_now_add=True                 # 创建时自动设置

7.3 关系字段

python 复制代码
# 一对多:ForeignKey
class Article(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)

# on_delete 选项
CASCADE     # 级联删除
SET_NULL    # 设为 NULL(需要 null=True)
SET_DEFAULT # 设为默认值(需要 default)
PROTECT     # 阻止删除,抛异常
DO_NOTHING  # 不做任何操作

# 反向查询
user.articles.all()             # 默认: model_name_set
user.article_set.all()

# 自定义反向查询名称
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='articles')

# 多对多:ManyToManyField
class Article(models.Model):
    tags = models.ManyToManyField('Tag', blank=True)

article.tags.add(tag)
article.tags.remove(tag)
article.tags.clear()
article.tags.set([tag1, tag2])

# 多对多带额外字段
class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()

class Person(models.Model):
    groups = models.ManyToManyField(Group, through='Membership')

7.4 ORM 查询

python 复制代码
# 基础查询
Article.objects.all()                         # 所有记录
Article.objects.filter(status='published')     # 过滤
Article.objects.exclude(status='draft')        # 排除
Article.objects.get(pk=1)                      # 获取单个(不存在抛异常)
Article.objects.get_or_create(title='test')    # 获取或创建

# 链式查询
Article.objects.filter(status='published').filter(author__username='admin')

# 字段查找
Article.objects.filter(title__contains='Django')
Article.objects.filter(title__icontains='django')      # 不区分大小写
Article.objects.filter(title__startswith='Django')
Article.objects.filter(title__endswith='教程')
Article.objects.filter(view_count__gte=100)            # 大于等于
Article.objects.filter(view_count__lt=100)             # 小于
Article.objects.filter(created_at__year=2024)
Article.objects.filter(created_at__date__gte='2024-01-01')
Article.objects.filter(title__in=['Django', 'Flask'])
Article.objects.filter(title__isnull=True)

# Q 对象(复杂查询)
from django.db.models import Q

Article.objects.filter(Q(title__contains='Django') | Q(title__contains='Flask'))
Article.objects.filter(Q(status='published') & ~Q(author__username='spam'))

# F 对象(字段间比较)
from django.db.models import F

Article.objects.filter(view_count__gt=F('comment_count') * 10)
Article.objects.update(view_count=F('view_count') + 1)

# 排序
Article.objects.order_by('-created_at')
Article.objects.order_by('created_at', '-title')

# 切片(LIMIT / OFFSET)
Article.objects.all()[:10]           # 前 10 条
Article.objects.all()[10:20]         # 11-20 条

# 聚合
from django.db.models import Count, Sum, Avg, Max, Min

Article.objects.aggregate(avg_views=Avg('view_count'))
Article.objects.annotate(comment_count=Count('comment'))

# 分组统计
from django.db.models import Count

Category.objects.annotate(article_count=Count('article'))

# 去重
Article.objects.filter(tags__name='Python').distinct()

# 日期查询
from django.utils import timezone
Article.objects.filter(created_at__gte=timezone.now() - timedelta(days=7))

# 选择字段
Article.objects.values('id', 'title')
Article.objects.values_list('id', 'title')
Article.objects.values_list('id', flat=True)   # 只返回单值列表

# exists
Article.objects.filter(title='test').exists()  # 是否存在

# 批量操作
Article.objects.filter(status='draft').delete()
Article.objects.filter(status='draft').update(status='archived')
Article.objects.bulk_create([...])              # 批量创建

7.5 执行原生 SQL

python 复制代码
# Manager.raw() ------ 返回 Model 实例
for article in Article.objects.raw('SELECT * FROM blog_article WHERE status = %s', ['published']):
    print(article.title)

# connection.cursor() ------ 执行任意 SQL
from django.db import connection

with connection.cursor() as cursor:
    cursor.execute("SELECT COUNT(*) FROM blog_article WHERE status = %s", ['published'])
    count = cursor.fetchone()[0]

7.6 数据库迁移

bash 复制代码
# 生成迁移文件
python manage.py makemigrations

# 执行迁移
python manage.py migrate

# 查看迁移状态
python manage.py showmigrations

# 回滚到指定迁移
python manage.py migrate blog 0002

# 查看迁移 SQL
python manage.py sqlmigrate blog 0003

自定义迁移操作:

python 复制代码
# migrations/0004_add_initial_data.py
from django.db import migrations

def create_initial_categories(apps, schema_editor):
    Category = apps.get_model('blog', 'Category')
    Category.objects.bulk_create([
        Category(name='Python'),
        Category(name='Django'),
        Category(name='数据库'),
    ])

class Migration(migrations.Migration):
    dependencies = [
        ('blog', '0003_auto_20240101_0000'),
    ]
    operations = [
        migrations.RunPython(create_initial_categories),
    ]

八、Admin 后台管理

8.1 注册模型

python 复制代码
# blog/admin.py
from django.contrib import admin
from .models import Article, Category, Tag

admin.site.register(Category)
admin.site.register(Tag)

8.2 自定义 Admin 配置

python 复制代码
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ['id', 'title', 'author', 'status', 'view_count', 'created_at']
    list_display_links = ['id', 'title']
    list_filter = ['status', 'category', 'created_at']
    search_fields = ['title', 'content']
    list_editable = ['status']
    list_per_page = 20
    ordering = ['-created_at']
    date_hierarchy = 'created_at'
    actions = ['make_published', 'make_draft']
    filter_horizontal = ['tags']

    fieldsets = (
        ('基本信息', {
            'fields': ('title', 'author', 'category', 'tags')
        }),
        ('内容', {
            'fields': ('content', 'summary'),
            'classes': ('wide',)
        }),
        ('设置', {
            'fields': ('status',)
        }),
    )

    readonly_fields = ['view_count', 'created_at', 'updated_at']

    @admin.display(description='是否热门')
    def is_hot(self, obj):
        return obj.view_count > 1000
    is_hot.boolean = True

    @admin.action(description='发布选中文章')
    def make_published(self, request, queryset):
        updated = queryset.update(status='published')
        self.message_user(request, f'{updated} 篇文章已发布')

    @admin.action(description='转为草稿')
    def make_draft(self, request, queryset):
        updated = queryset.update(status='draft')
        self.message_user(request, f'{updated} 篇文章已转为草稿')

九、表单处理

9.1 ModelForm

python 复制代码
# blog/forms.py
from django import forms
from .models import Article, Comment

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'category', 'tags', 'content', 'summary']
        widgets = {
            'content': forms.Textarea(attrs={'class': 'editor', 'rows': 10}),
            'tags': forms.CheckboxSelectMultiple(),
        }

    def clean_title(self):
        title = self.cleaned_data['title']
        if len(title) < 5:
            raise forms.ValidationError('标题至少 5 个字符')
        return title

9.2 在视图中使用表单

python 复制代码
from django.shortcuts import render, redirect
from .forms import ArticleForm

def create_article(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            article = form.save(commit=False)
            article.author = request.user
            article.save()
            form.save_m2m()  # 保存多对多关系
            return redirect(article)
    else:
        form = ArticleForm()
    return render(request, 'blog/article_form.html', {'form': form})

9.3 模板中渲染表单

html 复制代码
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}

    {# 手动渲染每个字段 #}
    <div>
        {{ form.title.label_tag }}
        {{ form.title }}
        {% if form.title.errors %}
            <span class="error">{{ form.title.errors }}</span>
        {% endif %}
    </div>

    <button type="submit">保存</button>
</form>

9.4 纯 Django Form(不关联模型)

python 复制代码
class ContactForm(forms.Form):
    name = forms.CharField(max_length=100, label='姓名')
    email = forms.EmailField(label='邮箱')
    message = forms.CharField(widget=forms.Textarea, label='留言')

    def clean_email(self):
        email = self.cleaned_data['email']
        if not email.endswith('@company.com'):
            raise forms.ValidationError('请使用公司邮箱')
        return email

十、中间件(Middleware)

10.1 自定义中间件

python 复制代码
# middleware.py
import time
import logging

logger = logging.getLogger(__name__)

class RequestTimingMiddleware:
    """记录请求耗时"""

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        start_time = time.time()
        response = self.get_response(request)
        duration = time.time() - start_time
        logger.info(f'{request.method} {request.path} - {duration:.3f}s')
        return response


class ApiKeyMiddleware:
    """API Key 验证"""

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if request.path.startswith('/api/'):
            api_key = request.META.get('HTTP_X_API_KEY')
            if not api_key or api_key != 'expected-key':
                from django.http import JsonResponse
                return JsonResponse({'error': 'Invalid API key'}, status=401)
        return self.get_response(request)

10.2 注册中间件

python 复制代码
# 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',
    'django.contrib.messages.middleware.MessageMiddleware',
    'middleware.RequestTimingMiddleware',
    'middleware.ApiKeyMiddleware',
]

十一、REST API 开发(DRF)

11.1 安装 DRF

bash 复制代码
pip install djangorestframework

11.2 序列化器(Serializer)

python 复制代码
# blog/serializers.py
from rest_framework import serializers
from .models import Article, Category

class CategorySerializer(serializers.ModelSerializer):
    article_count = serializers.IntegerField(read_only=True)

    class Meta:
        model = Category
        fields = ['id', 'name', 'description', 'article_count']


class ArticleListSerializer(serializers.ModelSerializer):
    category_name = serializers.CharField(source='category.name', read_only=True)
    author_name = serializers.CharField(source='author.username', read_only=True)

    class Meta:
        model = Article
        fields = ['id', 'title', 'author_name', 'category_name', 'status',
                  'view_count', 'created_at']


class ArticleDetailSerializer(serializers.ModelSerializer):
    category = CategorySerializer(read_only=True)
    tags = serializers.StringRelatedField(many=True, read_only=True)
    author_name = serializers.CharField(source='author.username', read_only=True)

    class Meta:
        model = Article
        fields = '__all__'

11.3 视图集(ViewSet)

python 复制代码
# blog/api_views.py
from rest_framework import viewsets, status, permissions
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .models import Article
from .serializers import ArticleListSerializer, ArticleDetailSerializer

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.select_related('author', 'category').prefetch_related('tags')
    permission_classes = [IsAuthenticatedOrReadOnly]

    def get_serializer_class(self):
        if self.action == 'list':
            return ArticleListSerializer
        return ArticleDetailSerializer

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

    @action(detail=False, methods=['get'])
    def published(self, request):
        articles = self.queryset.filter(status='published')
        serializer = self.get_serializer(articles, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def publish(self, request, pk=None):
        article = self.get_object()
        article.status = 'published'
        article.save()
        return Response({'status': 'published'})

11.4 路由配置

python 复制代码
# myproject/urls.py
from rest_framework.routers import DefaultRouter
from blog.api_views import ArticleViewSet

router = DefaultRouter()
router.register(r'articles', ArticleViewSet, basename='article')

urlpatterns = [
    path('api/', include(router.urls)),
    path('api-auth/', include('rest_framework.urls')),
]

11.5 DRF 分页与过滤

python 复制代码
# settings.py
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
    'DEFAULT_FILTER_BACKENDS': [
        'rest_framework.filters.SearchFilter',
        'rest_framework.filters.OrderingFilter',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ],
}

# 视图中启用搜索和排序
class ArticleViewSet(viewsets.ModelViewSet):
    search_fields = ['title', 'content']
    ordering_fields = ['created_at', 'view_count']
    ordering = ['-created_at']

十二、认证与权限

12.1 用户认证

python 复制代码
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required

def login_view(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = authenticate(request, username=username, password=password)
        if user is not None:
            if user.is_active:
                login(request, user)
                return redirect('dashboard')
        return render(request, 'login.html', {'error': '用户名或密码错误'})
    return render(request, 'login.html')

@login_required
def dashboard(request):
    return render(request, 'dashboard.html')

def logout_view(request):
    logout(request)
    return redirect('login')

12.2 自定义认证后端

python 复制代码
# auth/backends.py
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q

class EmailOrUsernameBackend(ModelBackend):
    """支持邮箱或用户名登录"""

    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get('username')
        if password is None:
            password = kwargs.get('password')

        UserModel = get_user_model()
        try:
            user = UserModel.objects.get(Q(username=username) | Q(email=username))
            if user.check_password(password) and self.user_can_authenticate(user):
                return user
        except UserModel.DoesNotExist:
            return None
python 复制代码
# settings.py
AUTHENTICATION_BACKENDS = [
    'auth.backends.EmailOrUsernameBackend',
    'django.contrib.auth.backends.ModelBackend',
]

12.3 Token 认证

bash 复制代码
pip install rest_framework.authtoken
python 复制代码
# 自动创建 Token
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token

@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)

# 客户端请求时携带 Header: Authorization: Token xxxxxxxx

十三、缓存机制

13.1 缓存配置

python 复制代码
# settings.py

# 方式 1:本地内存缓存(开发用)
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}

# 方式 2:Redis 缓存(生产用)
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
    }
}

13.2 缓存使用

python 复制代码
from django.core.cache import cache

# 基础用法
cache.set('key', 'value', timeout=60)
data = cache.get('key')
cache.delete('key')
cache.set_many({'key1': 'val1', 'key2': 'val2'})
data = cache.get_many(['key1', 'key2'])

# 视图缓存装饰器
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)  # 缓存 15 分钟
def article_list(request):
    ...

# 模板片段缓存
{% load cache %}
{% cache 500 sidebar %}
    {% get_recent_articles as recent %}
    {% for article in recent %}
        <li>{{ article.title }}</li>
    {% endfor %}
{% endcache %}

十四、信号(Signals)

14.1 内置信号

python 复制代码
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from .models import Article

@receiver(post_save, sender=Article)
def on_article_created(sender, instance, created, **kwargs):
    if created:
        from .tasks import send_notification
        send_notification.delay(instance.id)
        # 清除相关缓存
        from django.core.cache import cache
        cache.delete('article_list')

@receiver(pre_delete, sender=Article)
def on_article_delete(sender, instance, **kwargs):
    # 删除前清理关联数据
    instance.tags.clear()

14.2 自定义信号

python 复制代码
# signals.py
from django.dispatch import Signal

article_viewed = Signal()  # 文章被浏览

# 在视图或模型中发送信号
article_viewed.send(sender=Article, article=article, user=request.user)

# 接收信号
@receiver(article_viewed, sender=Article)
def track_article_view(sender, article, user, **kwargs):
    Article.objects.filter(pk=article.pk).update(view_count=F('view_count') + 1)

十五、异步任务(Celery 集成)

15.1 安装配置

bash 复制代码
pip install celery redis
python 复制代码
# myproject/celery.py
import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

app = Celery('myproject')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
python 复制代码
# settings.py
CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Shanghai'

15.2 定义任务

python 复制代码
# blog/tasks.py
from celery import shared_task
from django.core.mail import send_mail

@shared_task
def send_welcome_email(user_id):
    from django.contrib.auth.models import User
    user = User.objects.get(id=user_id)
    send_mail(
        '欢迎加入',
        f'你好 {user.username},感谢注册!',
        'noreply@example.com',
        [user.email],
    )

@shared_task
def generate_article_summary(article_id):
    from .models import Article
    article = Article.objects.get(id=article_id)
    summary = article.content[:200] + '...'
    article.summary = summary
    article.save()

# 调用任务
send_welcome_email.delay(user.id)
generate_article_summary.apply_async(args=[article.id], countdown=60)  # 60秒后执行

15.3 启动 Celery

bash 复制代码
# 启动 Worker
celery -A myproject worker -l info

# 启动 Beat(定时任务调度器)
celery -A myproject beat -l info

15.4 定时任务

python 复制代码
# myproject/celery.py
from celery.schedules import crontab

app.conf.beat_schedule = {
    'cleanup-expired-sessions': {
        'task': 'myproject.tasks.cleanup_expired_sessions',
        'schedule': crontab(hour=3, minute=0),  # 每天凌晨 3 点
    },
    'send-daily-report': {
        'task': 'myproject.tasks.send_daily_report',
        'schedule': crontab(hour=8, minute=0, day_of_week='mon-fri'),
    },
}

十六、部署与优化

16.1 关闭 Debug 模式

python 复制代码
# settings.py
DEBUG = False

ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']

SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

16.2 静态文件收集

python 复制代码
# settings.py
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'

STATICFILES_DIRS = [
    BASE_DIR / 'static',
]
bash 复制代码
python manage.py collectstatic

16.3 使用 Gunicorn 部署

bash 复制代码
pip install gunicorn

gunicorn myproject.wsgi:application \
    --bind 0.0.0.0:8000 \
    --workers 4 \
    --worker-class gthread \
    --threads 2 \
    --timeout 120 \
    --access-logfile - \
    --error-logfile -

16.4 使用 Uvicorn + ASGI

bash 复制代码
pip install uvicorn

uvicorn myproject.asgi:application --host 0.0.0.0 --port 8000 --workers 4

16.5 Nginx 配置

nginx 复制代码
server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name yourdomain.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location /static/ {
        alias /path/to/staticfiles/;
        expires 30d;
    }

    location /media/ {
        alias /path/to/media/;
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        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;
    }
}

16.6 性能优化清单

复制代码
1. 数据库优化
   - 使用 select_related() 减少一对多查询的 SQL 次数
   - 使用 prefetch_related() 减少多对多查询的 SQL 次数
   - 添加合适的数据库索引
   - 使用 only() / defer() 限制查询字段
   - 使用 iterator() 处理大数据集,减少内存

2. 缓存策略
   - 视图缓存:@cache_page
   - 模板片段缓存:{% cache %}
   - 数据查询缓存:cache.set/get
   - 使用 Redis 作为缓存后端

3. 查询优化
   - 避免在循环中执行查询(N+1 问题)
   - 使用 bulk_create 批量插入
   - 使用 update() 批量更新
   - 使用 count() 或 exists() 代替 len()

4. 模板优化
   - 使用 {% load cached %} 缓存模板加载
   - 使用 compressed 模板加载器
   - 避免在模板中执行复杂逻辑

5. 部署优化
   - 开启 gzip 压缩
   - 配置 CDN 托管静态文件
   - 使用多个 Worker 进程
   - 设置合适的 Worker 超时时间

十七、最佳实践与常见陷阱

17.1 项目结构推荐

复制代码
myproject/
├── manage.py
├── myproject/
│   ├── settings/
│   │   ├── __init__.py
│   │   ├── base.py           # 公共配置
│   │   ├── development.py    # 开发环境
│   │   ├── testing.py        # 测试环境
│   │   └── production.py     # 生产环境
│   ├── urls.py
│   └── wsgi.py
├── apps/
│   ├── __init__.py
│   ├── blog/                 # 博客应用
│   ├── users/                # 用户应用
│   └── common/               # 公共工具
├── config/
│   ├── celery.py
│   └── middleware.py
├── templates/
│   ├── base.html
│   └── blog/
├── static/
│   ├── css/
│   ├── js/
│   └── images/
├── tests/
│   ├── test_blog/
│   └── test_users/
└── requirements/
    ├── base.txt
    ├── dev.txt
    └── prod.txt

17.2 settings 分环境管理

python 复制代码
# settings/base.py
import os
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
INSTALLED_APPS = [...]
DATABASES = {...}

# settings/development.py
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['*']

# settings/production.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
bash 复制代码
# 启动时指定
DJANGO_SETTINGS_MODULE=myproject.settings.production python manage.py runserver

17.3 常见陷阱

python 复制代码
# 1. N+1 查询问题
# 错误:循环中执行查询
for article in Article.objects.all():
    print(article.category.name)  # 每次循环都会查询数据库

# 正确:使用 select_related
for article in Article.objects.select_related('category').all():
    print(article.category.name)  # 只执行 1 次 SQL

# 2. 修改查询集不会立即执行
qs = Article.objects.all()
qs = qs.filter(status='published')  # 不会查数据库
list(qs)  # 此时才执行查询

# 3. 时区问题
from django.utils import timezone
now = timezone.now()  # 始终使用 aware datetime

# 4. 不要在视图外直接调用 ORM
# models.py 中不要导入 views.py 中的内容,避免循环引用

# 5. 表单中 CSRF 保护
# POST 请求模板中必须包含 {% csrf_token %}

# 6. 静态文件在 DEBUG=False 时需要 collectstatic

# 7. migration 冲突
# 多人开发时,生成 migration 后及时提交
# 如果冲突,删除多余文件,重新生成

17.4 编写测试

python 复制代码
# tests/test_blog.py
from django.test import TestCase, Client
from django.urls import reverse
from .models import Article

class ArticleModelTest(TestCase):
    def setUp(self):
        self.article = Article.objects.create(
            title='测试文章',
            content='测试内容',
        )

    def test_article_creation(self):
        self.assertEqual(self.article.title, '测试文章')
        self.assertIsNotNone(self.article.created_at)

    def test_article_str(self):
        self.assertEqual(str(self.article), '测试文章')


class ArticleViewTest(TestCase):
    def setUp(self):
        self.client = Client()
        Article.objects.create(title='文章1', status='published')
        Article.objects.create(title='文章2', status='draft')

    def test_article_list(self):
        response = self.client.get('/blog/articles/')
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, '文章1')

    def test_article_detail(self):
        article = Article.objects.first()
        response = self.client.get(f'/blog/articles/{article.id}/')
        self.assertEqual(response.status_code, 200)

    def test_create_article_requires_login(self):
        response = self.client.get('/blog/articles/create/')
        self.assertEqual(response.status_code, 302)  # 重定向到登录页


# 使用 pytest-django
# pip install pytest-django
# conftest.py
import pytest
from django.contrib.auth.models import User

@pytest.fixture
def user(db):
    return User.objects.create_user(username='test', password='pass123')

@pytest.fixture
def auth_client(client, user):
    client.force_login(user)
    return client

附录

A. 常用第三方包

包名 用途
djangorestframework REST API 框架
django-filter 查询过滤
django-cors-headers 跨域配置
django-crispy-forms 表单样式美化
django-debug-toolbar 调试工具栏
django-extensions 扩展命令(shell_plus、show_urls 等)
django-guardian 对象级别权限
django-allauth 社交登录集成
celery 异步任务队列
pillow 图片处理
psycopg2-binary PostgreSQL 驱动
mysqlclient MySQL 驱动
redis Redis 缓存客户端
drf-spectacular OpenAPI 文档生成

B. 学习资源


本文档覆盖了 Django 从基础到进阶的核心知识点,建议结合官方文档和实际项目练习,加深理解。

相关推荐
云边有个稻草人2 小时前
MySQL 监控实战:mysql_exporter 部署与远程监控实现
数据库·mysql
LSL666_2 小时前
MybatisPlus——代码生成器
数据库·mybatisplus
小小AK2 小时前
吉客云与MySQL数据集成方案
数据库·mysql
weixin_457760002 小时前
KenLM简介及安装使用
python·kenlm
B站_计算机毕业设计之家2 小时前
计算机毕业设计:汽车数据可视化与后台管理平台 Django框架 requests爬虫 可视化 车辆 数据分析 大数据 机器学习(建议收藏)✅
python·算法·机器学习·信息可视化·django·汽车·课程设计
ai安歌2 小时前
学生管理系统——Django学生管理系统架构设计与实现:从零构建现代化Web应用
前端·python·django
子夜四时歌3 小时前
Python详细安装与环境搭建
开发语言·python
Jinkxs3 小时前
SkyWalking - Python 应用追踪:基于 skywalking-python 的埋点
开发语言·python·skywalking
大头博士先生3 小时前
【3月考】二级Python最新真题及满分代码合集(基本操作题部分)
开发语言·python