1 介绍
1.1 概述
01.Django定义
a.框架本质
Django是一个高级Python Web框架,遵循MTV(Model-Template-View)架构模式
由Adrian Holovaty和Simon Willison于2003年在Lawrence Journal-World报社开发
2005年以BSD许可证开源发布,现由Django软件基金会维护
遵循"batteries included"理念,提供构建Web应用所需的大部分功能
b.核心特性
完整的ORM系统,支持多种数据库后端
自动管理后台,快速构建CRUD界面
强大的模板系统,支持模板继承和复用
完善的用户认证和权限管理系统
内置安全防护机制,防止常见Web攻击
国际化支持,支持多语言开发
丰富的中间件系统,可扩展性强
c.适用场景
适合构建复杂的企业级Web应用系统
内容管理系统(CMS)和电商平台
API后端服务,配合前端框架使用
需要快速原型的项目开发
团队协作开发,需要标准化框架
02.技术架构
a.MTV架构模式
Model(模型):数据访问层,处理与数据库相关的操作
Template(模板):表现层,处理用户界面显示逻辑
View(视图):业务逻辑层,处理用户请求和业务逻辑
URLconf(URL配置):URL分发机制,将请求映射到视图函数
b.WSGI兼容性
实现WSGI(Web Server Gateway Interface)标准
支持多种Web服务器:Gunicorn、uWSGI、mod_wsgi
开发服务器:内置轻量级开发服务器
生产部署:支持高性能的ASGI和WSGI服务器
c.组件化设计
松耦合的应用架构,支持模块化开发
可重用的应用组件,便于团队协作
插件式架构,支持第三方扩展
清晰的层次分离,便于维护和测试
1.2 核心概念
01.项目与应用
a.项目结构
myproject/ # 项目根目录
├── manage.py # 项目管理脚本
├── myproject/ # 项目配置包
│ ├── __init__.py
│ ├── settings.py # 项目配置文件
│ ├── urls.py # 项目URL配置
│ ├── wsgi.py # WSGI配置
│ └── asgi.py # ASGI配置
└── apps/ # 应用目录
b.应用概念
应用是项目的功能模块,负责特定业务逻辑
一个项目可以包含多个应用
应用可以重用到不同项目中
每个应用有独立的models.py、views.py、urls.py等
c.项目与应用关系
项目是应用的容器,提供全局配置
应用是具体的功能实现模块
通过settings.py的INSTALLED_APPS注册应用
应用间可以相互依赖和协作
02.模型系统
a.ORM概念
对象关系映射(Object-Relational Mapping)
将Python对象映射到数据库表
提供面向对象的数据库操作接口
隐藏底层数据库差异,支持多种数据库
b.模型定义
每个模型类继承自django.db.models.Model
类属性对应数据库表的字段
支持各种数据类型:CharField、IntegerField、DateTimeField等
字段选项:null、blank、default、unique等
c.数据库迁移
makemigrations:检测模型变更,生成迁移文件
migrate:应用迁移到数据库,更新表结构
支持数据库版本控制和回滚
迁移文件记录了数据库结构的变更历史
03.视图系统
a.函数视图
a.功能说明
最简单的视图形式,接收HttpRequest对象作为参数,执行业务逻辑后返回HttpResponse对象作为响应,适合处理简单的请求逻辑。
b.代码示例
---
from django.http import HttpResponse
def hello_view(request):
return HttpResponse("Hello, Django!")
---
b.类视图
a.功能说明
基于类的视图提供更好的代码组织结构,支持HTTP方法分发(get、post、put、delete),支持Mixin模式组合多种功能,适合复杂业务逻辑。
b.代码示例
---
from django.views import View
class HelloView(View):
def get(self, request):
return HttpResponse("Hello, Class View!")
---
c.通用视图
Django提供的预定义视图类
ListView、DetailView、CreateView、UpdateView、DeleteView
快速实现常见的CRUD操作
可通过继承和Mixin进行定制
04.模板系统
a.模板语言
变量显示:{{ variable }}
标签:{% tag %}执行逻辑操作
过滤器:{{ value|filter }}
模板继承:{% extends "base.html" %}
块定义:{% block content %}{% endblock %}
b.模板继承
基础模板定义页面结构
子模板继承基础模板并重写特定块
支持多级继承和块嵌套
使用{{ block.super }}调用父块内容
c.静态文件处理
STATIC_URL:静态文件URL前缀
STATICFILES_DIRS:静态文件存储目录
{% load static %}加载静态文件标签
{% static 'css/style.css' %}生成静态文件URL
1.3 发展历程
01.Django诞生背景
a.起源故事
2003年诞生于美国堪萨斯州的Lawrence Journal-World报社
开发者:Adrian Holovaty和Simon Willison
初始目标:快速开发新闻网站,提高新闻发布效率
解决传统Web开发的重复性和低效性问题
b.早期发展
2005年7月以BSD许可证开源发布
获得了Python社区的广泛关注和支持
2008年成立Django软件基金会,独立运作
版本迭代快速,功能不断完善
c.社区成长
全球活跃的开发者社区
丰富的第三方应用和插件生态
完善的文档和教程体系
定期举办Django Under the Hood大会
02.版本演进历程
a.Django 1.0时代(2008)
2008年9月发布1.0正式版本
确立了核心架构和主要功能
引入Admin后台,成为框架标志性功能
建立稳定的API和向后兼容性承诺
b.Django 1.1-1.8系列(2009-2015)
持续添加新功能:模型验证、聚合、多数据库支持
改进Admin界面和用户体验
增强安全性和性能
建立成熟的第三方包生态
c.Django 2.0时代(2017-2019)
移除Python 2支持,专注Python 3发展
引入路径转换器,简化URL配置
改进移动端支持和响应式设计
性能优化和开发者体验提升
03.现代Django发展
a.Django 3.0系列(2019-2021)
支持异步视图和中间件(async/await)
引入MariaDB官方支持
改进数据库查询性能和安全性
优化开发服务器,支持HTTP/2
b.Django 4.0系列(2021-2023)
要求Python 3.8+,移除旧版本支持
引入基于Redis的缓存后端
改进表单渲染和安全性
增强PostgreSQL支持和数据库功能
c.Django 5.0系列(2023-现在)
继续强化异步支持和性能
改进Admin界面和用户体验
增强安全特性和错误处理
优化开发者工具和调试体验
04.技术影响和地位
a.行业地位
Python生态系统中最成熟的Web框架
企业级应用开发的首选框架之一
大型互联网公司的技术栈重要组成部分
教育和培训领域的标准框架
b.技术创新
引入了"batteries included"的设计理念
推动了Python Web开发的发展
Admin后台成为其他框架模仿的对象
ORM系统影响了后续的数据库抽象层设计
c.开源贡献
成功的开源项目管理典范
活跃的社区贡献和维护模式
良好的文档和代码质量标准
为Python生态系统的繁荣做出重要贡献
1.4 设计哲学
01.DRY原则
a.Don't Repeat Yourself
避免代码重复,提高代码复用性
一次定义,多处使用,减少维护成本
Django的Admin后台是DRY原则的最佳实践
自动生成代码,减少手工编写重复代码
b.模板系统复用
模板继承机制避免HTML代码重复
模板包含(include)复用页面片段
自定义标签和过滤器复用模板逻辑
模板上下文处理器复用全局数据
c.ORM设计思想
定义一次模型,自动生成数据库操作
迁移系统自动处理数据库结构变更
通用视图减少重复的CRUD代码
Mixin模式复用视图功能
02.显式优于隐式
a.代码可读性
明确的函数和类命名,见名知意
清晰的参数传递和返回值
避免隐式转换和魔法方法
详细的错误信息和调试提示
b.配置显式化
settings.py中所有配置都是显式的
数据库配置、中间件、应用注册等
环境变量使用django-environ包管理
生产环境和开发环境配置分离
c.URL配置
明确的URL路由映射
路径参数和查询参数分离
URL命名和反向解析避免硬编码
包含机制组织大型应用的URL结构
03.快速开发
a.开发效率优先
内置常用功能,减少第三方依赖
Admin后台自动生成管理界面
开发服务器自动重载和错误提示
脚手架工具快速生成项目结构
b.约定优于配置
遵循命名约定减少配置工作
模型和数据库表的自动映射
模板文件位置和命名约定
静态文件组织结构约定
c.开发者友好
详细的错误页面和调试信息
完善的文档和教程体系
丰富的示例代码和最佳实践
活跃的社区支持和技术交流
04.安全性优先
a.内置安全防护
CSRF保护:防止跨站请求伪造攻击
XSS防护:自动转义用户输入数据
SQL注入防护:ORM提供参数化查询
点击劫持保护:X-Frame-Options头设置
b.安全默认设置
生产环境安全配置建议
密码哈希存储,使用PBKDF2算法
会话安全配置,支持安全的cookie设置
HTTPS重定向和安全头设置
c.安全最佳实践
定期发布安全更新和补丁
安全漏洞报告和响应机制
安全文档和最佳实践指南
与安全社区合作,及时发现和修复问题
1.5 优势与特点
01.开发效率优势
a.快速开发
内置完整的功能组件,无需重复开发
Admin后台自动生成CRUD管理界面
开发服务器支持自动重载和调试
脚手架工具快速创建项目和应用结构
示例代码:
```python
# 快速创建项目和应用
django-admin startproject myproject
python manage.py startapp myapp
python manage.py createsuperuser # 创建管理员用户
```
b.代码复用
丰富的内置应用:auth、admin、sessions等
强大的第三方包生态系统
应用模块化设计,便于代码重用
模板系统和通用视图减少重复代码
c.开发工具
django-admin和manage.py管理命令
开发服务器支持调试和性能分析
数据库迁移工具简化数据库管理
自动化测试框架和代码生成工具
02.功能完整性
a.核心功能
完整的ORM系统,支持多种数据库
强大的模板系统和前端集成
用户认证和权限管理系统
表单处理和数据验证机制
文件上传和静态文件管理
b.高级功能
国际化和本地化支持
缓存系统和性能优化
中间件系统和请求处理管道
信号系统和事件处理
邮件发送和后台任务处理
c.扩展性
插件式架构,支持自定义扩展
丰富的第三方包和应用
API设计支持前后端分离
微服务架构集成能力
03.安全特性
a.内置安全防护
CSRF保护:防止跨站请求伪造
XSS防护:自动HTML转义
SQL注入防护:参数化查询
点击劫持保护:安全头设置
b.数据安全
密码哈希存储,支持多种算法
会话安全配置,防止会话劫持
文件上传安全验证和处理
数据库连接安全配置
c.生产安全
安全的默认配置建议
环境变量管理敏感信息
HTTPS强制和安全头设置
定期安全更新和漏洞修复
04.社区和生态
a.活跃社区
全球数百万开发者使用
定期的技术会议和交流活动
丰富的在线教程和文档资源
活跃的开源贡献和社区维护
b.第三方生态
Django REST Framework构建API
Django CMS构建内容管理系统
Celery后台任务处理
Django Channels实时通信
c.企业支持
大型互联网公司的生产使用
专业的技术支持和咨询服务
完善的培训和教育体系
长期维护和支持承诺
1.6 应用场景
01.企业级Web应用
a.业务管理系统
Django适合构建复杂的企业级应用系统
提供完整的用户认证、权限管理、数据校验功能
支持多种数据库,满足企业数据存储需求
内置Admin后台,快速构建管理系统界面
示例应用:ERP系统、CRM系统、OA系统
b.数据密集型应用
强大的ORM适合处理复杂的数据关系
支持大数据量的查询和操作优化
数据库迁移系统简化数据结构变更
完整的事务支持保证数据一致性
c.多租户系统
支持多用户和多组织的数据隔离
灵活的权限系统和角色管理
可扩展的架构支持业务增长
安全的配置和部署方案
02.内容管理系统
a.CMS开发
Django最初就是为CMS开发而设计的
强大的模板系统便于页面布局和内容展示
Admin后台提供便捷的内容管理界面
支持多媒体文件管理和版本控制
示例:新闻网站、博客系统、企业官网
b.媒体平台
支持复杂的媒体内容关系和分类
灵活的模板系统适应不同页面布局
文件上传和处理能力
多语言支持和国际化功能
c.电商平台
支持复杂的商品目录和订单管理系统
内置用户认证和支付接口集成能力
良好的安全性保障交易数据安全
可扩展的架构支持业务快速增长
03.API后端服务
a.Django REST Framework
提供强大的API开发能力
支持多种序列化格式(JSON、XML等)
完整的认证和权限控制系统
自动生成API文档,便于前后端协作
-----------------------------------------------------------------------------------------------------
from rest_framework import viewsets, serializers
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email']
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
b.微服务架构
Django可作为微服务中的API网关
支持异步处理和实时通信
与消息队列集成,支持分布式系统
容器化部署和云原生支持
c.移动应用后端
提供RESTful API支持移动应用
用户认证和推送通知功能
数据同步和离线支持
性能优化和缓存策略
04.教育和科研
a.在线教育平台
适合构建在线学习系统
支持课程管理、用户权限、内容展示
可扩展的架构支持教育业务需求
良好的国际化支持多语言教学
示例:Moodle、edX等平台的部分功能
b.科研数据管理
强大的数据建模和处理能力
支持复杂的数据关系和查询
数据可视化和报表功能
科学计算和分析工具集成
c.学术网站
论文管理和发布系统
学术会议管理平台
研究项目管理系统
学术资源分享平台
1.7 版本演进
01.Django 1.x时代(2005-2017)
a.早期版本(1.0-1.4)
2008年发布1.0版本,确立核心架构
引入Admin后台,成为框架标志性功能
建立ORM系统,提供数据库抽象层
引入模板系统,分离逻辑和显示
b.稳定发展期(1.5-1.8)
持续添加新功能:模型验证、聚合查询
改进Admin界面和用户体验
增强安全性和性能优化
建立成熟的第三方包生态
-----------------------------------------------------------------------------------------------------
# Django 1.8 语法示例
from django.db import models
from django.contrib.auth.models import User
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add=True)
c.长期支持版本
Django 1.8 LTS:提供3年长期支持
专注于稳定性和安全性修复
企业级应用推荐使用LTS版本
平滑升级路径和向后兼容
02.Django 2.x时代(2017-2019)
a.重大变革(2.0-2.1)
移除Python 2支持,专注Python 3发展
引入路径转换器,简化URL配置
改进Admin界面,提升用户体验
增强移动端支持,响应式设计
b.功能增强(2.2-2.2)
Window表达式支持复杂SQL查询
数据库函数和聚合功能增强
改进的错误处理和调试信息
性能优化和开发者体验提升
c.安全更新
定期发布安全补丁和更新
修复已知安全漏洞和问题
改进安全配置和默认设置
加强安全文档和最佳实践
03.Django 3.x时代(2019-2021)
a.异步支持(3.0-3.1)
支持异步视图和中间件
引入ASGI接口,支持WebSocket
异步数据库查询和文件操作
提升并发处理能力和性能
-----------------------------------------------------------------------------------------------------
import asyncio
from django.http import JsonResponse
from django.views import View
class AsyncView(View):
async def get(self, request):
await asyncio.sleep(1) # 模拟异步操作
return JsonResponse({'status': 'ok'})
b.数据库增强
引入MariaDB官方支持
改进数据库查询性能
支持更多的数据库特性
优化迁移系统和数据操作
c.开发体验
改进错误信息和调试功能
优化开发服务器和性能
增强测试框架和工具
改进文档和示例代码
04.Django 4.x时代(2021-2023)
a.版本升级要求
移除对Python 3.7的支持,要求Python 3.8+
弃用旧功能和配置选项
清理过时的代码和API
为未来版本奠定基础
b.新功能特性
引入基于Redis的缓存后端
改进表单渲染和安全性
增强PostgreSQL支持和特性
优化开发服务器,支持HTTP/2
c.性能和安全
持续的性能优化和改进
增强安全特性和防护机制
改进错误处理和日志记录
优化缓存和数据库操作
05.Django 5.x时代(2023-现在)
a.当前发展(5.0-5.1)
继续强化异步支持和性能
改进Admin界面和用户体验
增强安全特性和错误处理
优化开发者工具和调试体验
b.未来规划
更好的云原生支持
增强的API开发能力
改进的性能和扩展性
更完善的开发工具和生态
c.社区发展
活跃的开源社区贡献
丰富的第三方包和插件
完善的文档和教程体系
全球范围的技术交流和合作
2 基础架构
2.1 MTV架构模式
01.MTV架构详解
a.架构组成
Model(模型):数据访问层,定义数据结构和业务逻辑
Template(模板):表现层,处理用户界面显示
View(视图):业务逻辑层,处理请求和响应
Controller(控制器):URL路由系统,分发请求到视图
b.与传统MVC对比
MTV是MVC模式的变种,更适合Web开发
Django的View对应MVC的Controller
Django的Template对应MVC的View
Django的Model与MVC的Model基本一致
更好地体现了关注点分离原则
c.架构优势
清晰的层次分离,便于维护和测试
高度模块化,支持团队协作开发
松耦合设计,组件可独立替换和扩展
符合软件工程的最佳实践原则
02.Model层详解
a.数据模型定义
a.功能说明
每个模型类继承自django.db.models.Model,类属性对应数据库表的字段,
支持各种数据类型(CharField、TextField、ForeignKey等)和关系类型,
提供丰富的字段选项(max_length、null、default等)和验证机制。
b.代码示例
---
from django.db import models
class BlogPost(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']
---
b.ORM功能特性
对象关系映射,Python对象到数据库表的映射
强大的查询API,支持复杂的数据查询
数据库迁移系统,自动处理结构变更
事务支持,保证数据一致性
多数据库支持,可在不同数据库间切换
c.数据验证和约束
字段级别的数据验证
模型级别的完整性约束
自定义验证器和清理方法
表单集和模型表单集成
03.Template层详解
a.模板语言
Django模板语言(DTL)设计简单安全
变量显示:{{ variable }}
模板标签:{% tag %}执行逻辑
过滤器:{{ value|filter }}
注释:{# comment #}模板注释
b.模板继承机制
a.功能说明
基础模板定义页面结构和公共部分,子模板通过{% extends %}继承基础模板并重写特定块,
支持多级继承和块嵌套,使用{{ block.super }}可调用父块内容,实现模板复用。
b.代码示例
---
<!-- base.html -->
<html>
<head>
<title>{% block title %}My Site{% endblock %}</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
<!-- blog_post.html -->
{% extends "base.html" %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}<h1>{{ post.title }}</h1>{% endblock %}
---
c.模板上下文处理
上下文处理器添加全局变量
视图函数传递局部上下文
模板标签和过滤器扩展功能
安全的模板渲染机制
04.View层详解
a.视图函数
接收HttpRequest对象作为参数
执行业务逻辑和数据处理
返回HttpResponse对象作为响应
支持多种响应类型:HTML、JSON、文件等
b.类视图
a.功能说明
基于类的视图提供更好的代码组织结构,支持HTTP方法分发处理(get、post、put、delete),
Mixin模式组合多种功能,通用视图快速实现CRUD操作,适合复杂业务场景。
b.代码示例
---
from django.views import View
from django.shortcuts import render
class BlogPostDetailView(View):
def get(self, request, pk):
post = get_object_or_404(BlogPost, pk=pk)
return render(request, 'blog/detail.html', {'post': post})
def post(self, request):
# 处理POST请求
return HttpResponse("Created")
---
c.通用视图
ListView:列表展示视图
DetailView:详情展示视图
CreateView:创建对象视图
UpdateView:更新对象视图
DeleteView:删除对象视图
2.2 项目结构详解
01.项目目录结构
a.标准项目结构
myproject/ # 项目根目录
├── manage.py # 项目管理脚本
├── myproject/ # 项目配置包
│ ├── __init__.py # Python包初始化文件
│ ├── settings.py # 项目配置文件
│ ├── urls.py # 项目URL配置
│ ├── wsgi.py # WSGI应用配置
│ └── asgi.py # ASGI应用配置
└── apps/ # 应用目录
b.应用目录结构
apps/
├── __init__.py # 应用包初始化
├── models.py # 数据模型定义
├── views.py # 视图函数/类
├── urls.py # 应用URL配置
├── forms.py # 表单定义
├── admin.py # Admin后台配置
├── apps.py # 应用配置
├── tests.py # 测试用例
├── migrations/ # 数据库迁移文件
│ ├── 0001_initial.py
│ └── ...
├── templates/ # 模板文件
│ └── app_name/
│ ├── base.html
│ └── index.html
└── static/ # 静态文件
└── app_name/
├── css/
├── js/
└── images/
c.settings.py核心配置
---
import os
from pathlib import Path
# 项目根目录
BASE_DIR = Path(__file__).resolve().parent.parent
# 调试模式
DEBUG = True
# 允许的主机
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
# 应用注册
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'apps.blog', # 自定义应用
'apps.users', # 自定义应用
]
# 中间件配置
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',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# 数据库配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'myproject',
'USER': 'username',
'PASSWORD': 'password',
'HOST': 'localhost',
'PORT': '5432',
}
}
# 国际化配置
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = True
# 静态文件配置
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
# 媒体文件配置
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# 安全设置
SECRET_KEY = 'django-insecure-secret-key'
---
02.manage.py管理脚本
a.常用命令
startproject:创建新项目
startapp:创建新应用
runserver:启动开发服务器
shell:启动Django shell
makemigrations:创建数据库迁移
migrate:应用数据库迁移
createsuperuser:创建超级用户
collectstatic:收集静态文件
test:运行测试用例
b.命令示例
---
# 创建项目
django-admin startproject myproject
# 创建应用
python manage.py startapp blog
# 启动开发服务器
python manage.py runserver 0.0.0.0:8000
# 数据库操作
python manage.py makemigrations
python manage.py migrate
# 创建超级用户
python manage.py createsuperuser
# 收集静态文件
python manage.py collectstatic
# 运行测试
python manage.py test
---
c.自定义管理命令
a.功能说明
在应用目录下创建management/commands/目录,继承BaseCommand类创建自定义命令,可添加参数解析和业务逻辑处理,实现项目特定的管理任务。
b.代码示例
---
# blog/management/commands/cleanup_posts.py
from django.core.management.base import BaseCommand
from apps.blog.models import BlogPost
class Command(BaseCommand):
help = '清理旧博客文章'
def add_arguments(self, parser):
parser.add_argument('--days', type=int, default=30)
def handle(self, *args, **options):
deleted_count = BlogPost.objects.filter(
is_published=False
).delete()[0]
self.stdout.write(f'成功删除 {deleted_count} 篇')
---
03.应用配置
a.apps.py配置类
a.功能说明
定义应用名称和标签,配置应用行为和选项,实现应用级别的初始化逻辑,可在ready方法中注册信号等启动任务。
b.代码示例
---
from django.apps import AppConfig
class BlogConfig(AppConfig):
name = 'apps.blog'
verbose_name = '博客应用'
def ready(self):
import apps.blog.signals
---
b.__init__.py配置
定义应用包的初始化逻辑
设置默认应用配置类
导入必要的模块和函数
c.应用间依赖
定义应用间的依赖关系
处理循环依赖问题
实现应用间的通信和协作
使用信号机制解耦应用
2.3 请求处理流程
01.完整请求周期
a.请求到达
用户通过浏览器发送HTTP请求
Web服务器(如Nginx、Apache)接收请求
通过WSGI接口将请求传递给Django应用
Django开始处理HTTP请求
b.URL路由匹配
a.功能说明
Django的URLconf根据URL路径匹配对应的视图函数,按照配置的顺序进行URL模式匹配,支持正则表达式和路径转换器,提取URL参数并传递给视图函数。
b.代码示例
---
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('blog/', views.blog_list),
path('blog/<int:post_id>/', views.blog_detail),
]
# views.py
def blog_detail(request, post_id):
post = get_object_or_404(BlogPost, pk=post_id)
return render(request, 'blog/detail.html', {'post': post})
---
c.中间件处理
中间件按照MIDDLEWARE配置的顺序执行
每个中间件的process_request方法依次执行
如果中间件返回响应,后续处理停止
视图函数执行前的预处理
02.视图函数执行
a.视图函数调用
Django调用匹配到的视图函数
传递HttpRequest对象和URL参数
视图函数执行业务逻辑处理
可以访问请求的所有信息:GET、POST、FILES、user等
b.业务逻辑处理
a.功能说明
在视图函数中执行数据库操作(查询、创建、更新、删除),进行业务规则验证和处理,实现数据转换和计算,调用其他服务和API。
b.代码示例
---
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from .forms import BlogPostForm
@login_required
def create_post(request):
if request.method == 'POST':
form = BlogPostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect('blog:detail', pk=post.pk)
else:
form = BlogPostForm()
return render(request, 'blog/create.html', {'form': form})
---
c.数据库操作
使用ORM进行数据库查询和操作
支持事务处理保证数据一致性
数据验证和错误处理
性能优化和缓存策略
03.响应生成
a.HttpResponse对象
a.功能说明
视图函数返回HttpResponse对象,包含响应内容、状态码、响应头,支持多种内容类型(HTML、JSON、XML、文件等)。
b.代码示例
---
from django.http import JsonResponse, HttpResponseRedirect
def json_response(request):
data = {'status': 'success', 'data': {'id': 1}}
return JsonResponse(data)
def redirect_response(request):
return HttpResponseRedirect('/blog/')
---
b.模板渲染
视图函数使用模板引擎生成HTML内容
传递上下文数据到模板
模板系统处理变量替换和逻辑执行
生成最终的HTML响应内容
c.中间件后处理
中间件按照逆序执行process_response方法
可以修改响应内容和响应头
处理响应的后处理逻辑
设置缓存头、安全头等
04.响应返回
a.WSGI接口
Django通过WSGI接口返回HTTP响应
Web服务器接收响应并返回给用户
支持多种WSGI服务器:Gunicorn、uWSGI等
异步支持:ASGI接口处理异步请求
b.日志记录
记录请求和响应信息
错误和异常日志记录
性能监控和统计分析
安全事件记录和报警
c.性能优化
数据库查询优化
缓存策略实施
静态文件优化
代码性能分析和改进
2.4 WSGI接口
01.WSGI标准详解
a.WSGI定义
Web Server Gateway Interface的缩写
Python Web应用与Web服务器之间的标准接口
定义了服务器如何与应用程序通信的规范
实现了Web服务器与应用程序的解耦
b.WSGI协议规范
应用程序必须是可调用对象
接受两个参数:environ字典和start_response函数
返回可迭代的响应体
environ包含请求环境和CGI变量
start_response设置响应状态和头部
c.Django WSGI实现
a.功能说明
django.core.wsgi模块提供WSGI适配器,自动处理请求解析和响应生成,支持同步和异步处理模式,兼容标准的WSGI服务器。
b.代码示例
---
# myproject/wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
application = get_wsgi_application()
---
02.开发服务器
a.runserver命令
Django内置的开发服务器
支持自动重载和调试功能
适合开发和测试环境
不适合生产环境使用
b.开发服务器特性
自动检测文件变化并重启
详细的错误页面和调试信息
支持HTTPS开发(runserver_plus)
集成静态文件服务
c.开发服务器配置
命令行选项:
---
python manage.py runserver 0.0.0.0:8000
python manage.py runserver --settings=myproject.settings_dev
python manage.py runserver --noreload
python manage.py runserver --verbosity=2
---
d.开发限制
性能限制:不适合高并发
安全性限制:不适合生产环境
稳定性限制:可能存在bug
功能限制:缺少生产级特性
03.生产WSGI服务器
a.Gunicorn服务器
a.功能说明
成熟的Python WSGI服务器,支持多进程和多线程,轻量级且性能优秀,广泛应用于生产环境。
b.代码示例
---
# 安装和启动
pip install gunicorn
gunicorn myproject.wsgi:application --bind 0.0.0.0:8000
# gunicorn.conf.py
bind = "0.0.0.0:8000"
workers = 4
timeout = 30
loglevel = "info"
---
b.uWSGI服务器
a.功能说明
功能强大的应用服务器,支持WSGI和ASGI协议,丰富的配置选项和插件,高性能和稳定性。
b.代码示例
---
# uwsgi.ini
[uwsgi]
socket = 127.0.0.1:8000
chdir = /path/to/myproject
wsgi-file = myproject/wsgi.py
master = true
processes = 4
threads = 2
---
c.mod_wsgi集成
a.功能说明
Apache服务器的WSGI模块,直接集成到Apache服务器,支持嵌入式和守护进程模式,适合已有Apache基础设施。
b.代码示例
---
# /etc/apache2/sites-available/myproject.conf
<VirtualHost *:80>
ServerName myproject.com
Alias /static /path/to/myproject/static
WSGIDaemonProcess myproject python-path=/path/to/myproject
WSGIProcessGroup myproject
WSGIScriptAlias / /path/to/myproject/wsgi.py
</VirtualHost>
---
04.ASGI异步支持
a.ASGI接口
异步服务器网关接口
支持WebSocket和HTTP/2
异步处理提升并发性能
Django 3.0+原生支持ASGI
b.ASGI服务器
a.功能说明
Daphne是Django官方ASGI服务器,Uvicorn是基于Starlette的高性能ASGI服务器,Hypercorn支持HTTP/2和WebSocket。
b.代码示例
---
# 安装和启动
pip install daphne uvicorn
daphne myproject.asgi:application -b 0.0.0.0 -p 8000
uvicorn myproject.asgi:application --host 0.0.0.0 --port 8000 --workers 4
---
c.异步视图
a.功能说明
Django 3.1+支持异步视图函数,使用async/await语法,支持异步数据库查询和文件操作。
b.代码示例
---
from django.http import JsonResponse
from asgiref.sync import sync_to_async
async def async_api_view(request):
data = await sync_to_async(get_data)()
return JsonResponse({'data': data})
def get_data():
return {'id': 1, 'name': '异步数据'}
---
05.部署最佳实践
a.服务器选择
Gunicorn:适合传统WSGI应用
uWSGI:功能丰富,性能优秀
Daphne:Django官方ASGI服务器
Uvicorn:高性能ASGI服务器
b.性能优化
合理配置worker进程数
使用进程和线程混合模式
启用预加载应用
配置合理的超时和连接限制
c.监控和日志
配置详细的访问日志和错误日志
使用系统监控工具监控服务器状态
设置报警和自动重启机制
收集性能指标和统计数据
2.5 配置系统
01.配置文件结构
a.settings.py核心配置
a.功能说明
项目的主要配置文件
包含数据库、中间件、应用注册等设置
支持模块化配置和条件配置
分离开发和生产环境配置
b.配置文件组织
---
# settings.py
import os
from pathlib import Path
import json
# 基础配置
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get('SECRET_KEY', 'django-insecure-key')
# 环境检测
ENVIRONMENT = os.environ.get('DJANGO_ENV', 'development')
# 导入环境特定配置
if ENVIRONMENT == 'production':
from .production import *
elif ENVIRONMENT == 'testing':
from .testing import *
else:
from .development import *
# 自定义配置加载
try:
with open(BASE_DIR / 'config' / 'custom.json') as f:
custom_config = json.load(f)
locals().update(custom_config)
except FileNotFoundError:
pass
---
b.环境特定配置
开发环境:development.py
测试环境:testing.py
生产环境:production.py
本地环境:local.py(不纳入版本控制)
c.配置模块化
按功能模块组织配置
数据库配置、缓存配置、日志配置等
使用配置类管理复杂配置
支持配置继承和覆盖
02.核心配置项
a.基础配置
DEBUG:调试模式开关
ALLOWED_HOSTS:允许访问的主机列表
SECRET_KEY:安全密钥
ROOT_URLCONF:根URL配置模块
WSGI_APPLICATION:WSGI应用
b.应用配置
a.功能说明
INSTALLED_APPS注册应用列表,包含Django内置应用和自定义应用,支持应用名称和配置类。
b.代码示例
---
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'rest_framework',
'apps.users',
'apps.blog',
]
---
c.中间件配置
a.功能说明
MIDDLEWARE中间件列表按执行顺序排列,支持字符串和类的引用方式。
b.代码示例
---
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'apps.core.middleware.RequestLogMiddleware',
]
---
d.数据库配置
a.功能说明
DATABASES数据库连接配置,支持多种数据库后端,支持读写分离和分片。
b.代码示例
---
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'myproject',
'USER': 'postgres',
'HOST': 'localhost',
'PORT': '5432',
}
}
---
03.安全配置
a.安全中间件
SecurityMiddleware:安全相关中间件
SECURE_BROWSER_XSS_FILTER
SECURE_CONTENT_TYPE_NOSNIFF
SECURE_HSTS_SECONDS
SECURE_HSTS_INCLUDE_SUBDOMAINS
SECURE_HSTS_PRELOAD
b.认证安全
a.功能说明
配置密码哈希器、会话安全设置和CSRF保护,确保应用安全性。
b.代码示例
---
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
]
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = True
---
c.权限配置
AUTH_USER_MODEL:自定义用户模型
AUTH_PASSWORD_VALIDATORS:密码验证器
LOGIN_URL:登录页面URL
LOGIN_REDIRECT_URL:登录后重定向URL
04.静态文件和媒体文件
a.静态文件配置
STATIC_URL:静态文件URL前缀
STATIC_ROOT:静态文件收集目录
STATICFILES_DIRS:静态文件搜索目录
STATICFILES_FINDERS:静态文件查找器
b.媒体文件配置
MEDIA_URL:媒体文件URL前缀
MEDIA_ROOT:媒体文件存储目录
文件上传处理配置
c.文件存储后端
a.功能说明
Django-storages支持云存储,可自定义存储类,支持文件压缩和处理。
b.代码示例
---
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
FILE_UPLOAD_MAX_MEMORY_SIZE = 5242880 # 5MB
# AWS S3存储
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_STORAGE_BUCKET_NAME = 'mybucket'
---
05.缓存配置
a.缓存后端
Redis缓存:高性能内存缓存
Memcached缓存:分布式缓存系统
数据库缓存:使用数据库表缓存
文件系统缓存:本地文件缓存
b.缓存配置示例
---
# Redis缓存配置
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'CONNECTION_POOL_KWARGS': {
'max_connections': 50,
'retry_on_timeout': True,
}
},
'KEY_PREFIX': 'myproject',
'TIMEOUT': 300, # 5分钟
}
}
# 多级缓存配置
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
},
'session': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'cache_table',
},
'static': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/tmp/django_cache',
}
}
# 缓存配置
CACHE_MIDDLEWARE_ALIAS = 'default'
CACHE_MIDDLEWARE_SECONDS = 600
CACHE_MIDDLEWARE_KEY_PREFIX = ''
---
c.会话缓存
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'session'
SESSION_COOKIE_AGE = 3600 # 1小时
2.6 环境管理
01.虚拟环境
a.venv虚拟环境
a.功能说明
Python 3.3+内置虚拟环境,轻量级无需额外安装,创建和使用简单。
b.代码示例
---
# 创建虚拟环境
python -m venv myproject-env
# 激活虚拟环境
source myproject-env/bin/activate # Linux/macOS
myproject-env\Scripts\activate # Windows
# 安装依赖
pip install -r requirements.txt
---
b.virtualenv虚拟环境
a.功能说明
功能更丰富的虚拟环境工具,支持Python版本切换和不同的创建参数。
b.代码示例
---
pip install virtualenv
virtualenv -p python3.9 myproject-env
source myproject-env/bin/activate
---
c.conda环境
a.功能说明
Anaconda的数据科学平台,集成包管理和环境管理,支持复杂的依赖关系。
b.代码示例
---
conda create -n myproject python=3.9
conda activate myproject
conda install django psycopg2
conda env export > environment.yml
---
02.依赖管理
a.requirements.txt
a.功能说明
标准的Python依赖管理文件,支持指定版本和安装选项,与pip工具完美集成。
b.代码示例
---
Django>=4.0,<5.0
psycopg2-binary==2.9.3
redis==4.3.1
gunicorn==20.1.0
---
b.Pipenv工具
a.功能说明
现代Python依赖管理工具,集成虚拟环境和依赖管理,支持Pipfile和Pipfile.lock。
b.代码示例
---
pip install pipenv
pipenv install django==4.0.6
pipenv install pytest --dev
pipenv shell
pipenv run python manage.py runserver
---
c.Poetry工具
a.功能说明
现代化的Python项目管理工具,集成依赖管理、构建和发布,使用pyproject.toml配置文件。
b.代码示例
---
# pyproject.toml
[tool.poetry]
name = "myproject"
version = "1.0.0"
[tool.poetry.dependencies]
python = "^3.8"
django = "^4.0"
---
03.环境变量管理
a.环境变量配置
a.功能说明
使用.env文件管理敏感信息,支持不同环境的配置,集成django-environ包。
b.代码示例
---
# settings.py
import environ
env = environ.Env(DEBUG=(bool, False))
environ.Env.read_env(BASE_DIR / '.env')
SECRET_KEY = env('SECRET_KEY')
DEBUG = env('DEBUG')
---
b..env文件示例
---
# .env文件
SECRET_KEY=your-secret-key-here
DEBUG=True
DATABASE_URL=postgresql://user:password@localhost:5432/myproject
REDIS_URL=redis://localhost:6379/0
EMAIL_URL=smtp://user:[email protected]:587
---
c.环境配置分离
开发环境:.env.development
测试环境:.env.testing
生产环境:.env.production
本地环境:.env.local
04.配置管理最佳实践
a.配置原则
敏感信息不提交到版本控制
使用环境变量管理配置
分离不同环境的配置
使用配置管理工具
b.部署配置
Docker容器化配置
Kubernetes配置管理
CI/CD管道配置
云平台配置管理
c.监控和日志
配置监控工具
设置日志记录
配置报警机制
性能监控配置
3 模型与ORM
3.1 模型定义与字段类型
01.模型类基础
a.模型类定义
a.功能说明
Django模型类通过继承models.Model实现ORM映射,每个类属性对应数据库表字段,
系统自动生成主键id,支持Meta类配置表名、排序等元数据,可重写save等方法实现自定义逻辑。
b.代码示例
---
from django.db import models
class BlogPost(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']
---
b.模型元数据
a.功能说明
Meta类用于配置模型元数据,包括自定义表名、默认排序规则、显示名称、联合唯一约束、索引优化和数据完整性约束,提供灵活的数据库层面控制能力。
b.代码示例
---
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
class Meta:
db_table = 'inventory_products'
ordering = ['name']
constraints = [
models.CheckConstraint(check=models.Q(price__gte=0), name='price_positive')
]
---
c.模型方法
a.功能说明
模型方法包括实例方法处理单个对象业务逻辑、类方法执行批量操作、属性方法提供动态计算值、魔法方法定制对象行为,实现业务逻辑封装和代码复用。
b.代码示例
---
class Order(models.Model):
status = models.CharField(max_length=20, default='pending')
def can_cancel(self):
return self.status in ['pending', 'processing']
@property
def status_display(self):
return dict(self.STATUS_CHOICES).get(self.status)
@classmethod
def get_pending_orders(cls):
return cls.objects.filter(status='pending')
---
02.基础字段类型
a.字符串字段
a.功能说明
字符串字段提供多种类型满足不同需求,CharField存储定长文本、TextField存储长文本、EmailField和URLField自动验证格式、
SlugField生成URL友好标识,支持配置长度、验证和显示选项。
b.代码示例
---
class Article(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
content = models.TextField()
email = models.EmailField()
---
b.数值字段
a.功能说明
数值字段支持整数、大整数、小整数、浮点数和精确小数类型,IntegerField存储整数、FloatField存储浮点数、
DecimalField精确存储货币等小数值,可配置默认值和空值选项。
b.代码示例
---
class Product(models.Model):
stock = models.IntegerField(default=0)
weight = models.FloatField(null=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
---
c.日期时间字段
a.功能说明
日期时间字段支持日期、时间、日期时间和时间间隔类型,DateField存储日期、TimeField存储时间、
DateTimeField存储完整时间戳,支持auto_now和auto_now_add自动更新时间。
b.代码示例
---
class Event(models.Model):
start_date = models.DateField()
start_datetime = models.DateTimeField()
created_at = models.DateTimeField(auto_now_add=True)
---
03.关系字段类型
a.外键字段
a.功能说明
ForeignKey实现一对多关系,必须指定on_delete删除策略,支持related_name配置反向关系名称,
可设置null和blank允许空值,提供CASCADE、SET_NULL、PROTECT等多种删除行为。
b.代码示例
---
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
bio = models.TextField()
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey()
co_author = models.ForeignKey()
publisher = models.ForeignKey()
---
b.多对多字段
a.功能说明
ManyToManyField实现多对多关系,支持through参数指定自定义中间表存储额外字段,
symmetrical控制关系对称性,blank允许空关系,适用于标签、分类等多对多场景。
b.代码示例
---
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
color = models.CharField(max_length=20, default='blue')
def __str__(self):
return self.name
class Category(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
tags = models.ManyToManyField()
categories = models.ManyToManyField()
related_articles = models.ManyToManyField()
class Student(models.Model):
name = models.CharField(max_length=100)
class Course(models.Model):
name = models.CharField(max_length=100)
students = models.ManyToManyField()
class Enrollment(models.Model):
student = models.ForeignKey(on_delete=models.CASCADE)
course = models.ForeignKey(on_delete=models.CASCADE)
enrollment_date = models.DateField(auto_now_add=True)
grade = models.CharField(max_length=2)
class Meta:
unique_together = ['student', 'course']
---
c.一对一字段
a.功能说明
ForeignKey实现一对多关系,必须指定on_delete删除策略,支持related_name配置反向关系名称,
可设置null和blank允许空值,提供CASCADE、SET_NULL、PROTECT等多种删除行为。
b.代码示例
---
class User(models.Model):
username = models.CharField(max_length=50, unique=True)
email = models.EmailField(unique=True)
class UserProfile(models.Model):
user = models.OneToOneField()
avatar = models.ImageField()
bio = models.TextField()
birth_date = models.DateField()
phone = models.CharField()
from django.contrib.auth.models import User
class ExtendedUser(models.Model):
user = models.OneToOneField()
company = models.CharField()
department = models.CharField()
---
04.字段选项和约束
a.通用字段选项
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
class Employee(models.Model):
name = models.CharField()
gender = models.CharField()
email = models.EmailField()
phone = models.CharField()
salary = models.DecimalField()
hire_date = models.DateField()
---
b.数据库约束
a.功能说明
ForeignKey实现一对多关系,必须指定on_delete删除策略,支持related_name配置反向关系名称,
可设置null和blank允许空值,提供CASCADE、SET_NULL、PROTECT等多种删除行为。
b.代码示例
---
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.IntegerField(default=0)
category = models.CharField(max_length=50)
is_active = models.BooleanField()
class Meta:
constraints = [
models.CheckConstraint(check=models.Q(price__gte=0), name='price_positive')
]
---
c.自定义字段
a.功能说明
自定义字段通过继承models.Field或现有字段类型实现特殊需求,重写db_type指定数据库类型、from_db_value转换数据库值、
get_prep_value准备保存值、validate实现自定义验证逻辑。
b.代码示例
---
from django.db import models
from django.core.exceptions import ValidationError
import re
class ColorField(models.CharField):
def __init__(self, *args, **kwargs):
def from_db_value(self, value, expression, connection):
return value
return value
def get_prep_value(self, value):
return value
return value
class Product(models.Model):
name = models.CharField(max_length=100)
---
3.2 模型关系
01.一对多关系
a.外键定义
a.功能说明
ForeignKey实现一对多关系,必须指定on_delete删除策略,支持related_name配置反向关系名称,
可设置null和blank允许空值,提供CASCADE、SET_NULL、PROTECT等多种删除行为。
b.代码示例
---
class Department(models.Model):
name = models.CharField(max_length=100, unique=True)
description = models.TextField()
manager = models.ForeignKey()
def __str__(self):
return self.name
class Employee(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField(unique=True)
gender = models.CharField(max_length=1)
birth_date = models.DateField(null=True)
hire_date = models.DateField()
salary = models.DecimalField(max_digits=10, decimal_places=2)
department = models.ForeignKey()
supervisor = models.ForeignKey()
class Meta:
ordering = ['name']
def __str__(self):
return f'{self.name} - {self.department.name}'
def get_subordinates_count(self):
return self.subordinates.count()
return False
return True
return False
---
b.级联删除选项
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
class User(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField(unique=True)
class Profile(models.Model):
user = models.OneToOneField()
avatar = models.ImageField()
class Blog(models.Model):
author = models.ForeignKey()
title = models.CharField(max_length=200)
content = models.TextField()
class Comment(models.Model):
blog = models.ForeignKey()
author = models.ForeignKey()
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Category(models.Model):
name = models.CharField(max_length=100)
parent = models.ForeignKey()
---
c.反向关系查询
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db.models import Count
from django.db.models import Q
---
02.多对多关系
a.基本多对多关系
a.功能说明
ManyToManyField实现多对多关系,支持through参数指定自定义中间表存储额外字段,
symmetrical控制关系对称性,blank允许空关系,适用于标签、分类等多对多场景。
b.代码示例
---
class Student(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField(unique=True)
enrollment_date = models.DateField(auto_now_add=True)
def __str__(self):
return self.name
class Course(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
credits = models.IntegerField(default=3)
start_date = models.DateField()
def __str__(self):
return self.name
class Enrollment(models.Model):
student = models.ForeignKey(on_delete=models.CASCADE)
course = models.ForeignKey(on_delete=models.CASCADE)
enrollment_date = models.DateField(auto_now_add=True)
grade = models.CharField(max_length=2)
is_completed = models.BooleanField()
class Meta:
unique_together = ['student', 'course']
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
tags = models.ManyToManyField()
authors = models.ManyToManyField()
return self.title
---
b.中间表多对多关系
a.功能说明
ManyToManyField实现多对多关系,支持through参数指定自定义中间表存储额外字段,
symmetrical控制关系对称性,blank允许空关系,适用于标签、分类等多对多场景。
b.代码示例
---
class Student(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField(unique=True)
class Course(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
class Enrollment(models.Model):
student = models.ForeignKey(on_delete=models.CASCADE)
course = models.ForeignKey(on_delete=models.CASCADE)
enrollment_date = models.DateField(auto_now_add=True)
status = models.CharField()
grade = models.CharField()
feedback = models.TextField()
class Meta:
unique_together = ['student', 'course']
ordering = ['-enrollment_date']
def __str__(self):
return f'{self.student.name} - {self.course.name}'
def is_passing(self):
return self.grade in ['A', 'B', 'C']
class Course(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
students = models.ManyToManyField()
def get_enrolled_students(self):
return self.students.filter(
return self.students.filter(
return self.students.filter(
---
c.多对多关系操作
a.功能说明
ManyToManyField实现多对多关系,支持through参数指定自定义中间表存储额外字段,
symmetrical控制关系对称性,blank允许空关系,适用于标签、分类等多对多场景。
b.代码示例
---
from django.db.models import Q
from django.db.models import Count
---
03.一对一关系
a.基本一对一关系
a.功能说明
ForeignKey实现一对多关系,必须指定on_delete删除策略,支持related_name配置反向关系名称,
可设置null和blank允许空值,提供CASCADE、SET_NULL、PROTECT等多种删除行为。
b.代码示例
---
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField()
avatar = models.ImageField()
bio = models.TextField()
birth_date = models.DateField()
phone = models.CharField()
address = models.TextField()
website = models.URLField()
github_username = models.CharField()
def __str__(self):
return f'{self.user.username}的个人资料'
def get_age(self):
from datetime import date
return today.year - self.birth_date.year - (
return None
def get_github_url(self):
return f'https://github.com/{self.github_username}'
return None
return profile
return user.profile
return None
---
b.模型扩展
a.功能说明
Django提供内置User模型用于用户管理,支持自定义用户模型配置扩展字段,认证后端负责验证用户身份和权限,可配置多个认证后端实现灵活的认证策略。
b.代码示例
---
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
phone = models.CharField()
avatar = models.ImageField()
birth_date = models.DateField()
website = models.URLField()
bio = models.TextField()
is_verified = models.BooleanField()
verification_token = models.CharField()
def __str__(self):
return self.username
def get_full_name(self):
return full_name or self.username
class UserPreference(models.Model):
user = models.OneToOneField()
theme = models.CharField()
language = models.CharField()
timezone = models.CharField()
email_notifications = models.BooleanField()
push_notifications = models.BooleanField()
def __str__(self):
return f'{self.user.username}的偏好设置'
---
c.关系查询优化
a.功能说明
ForeignKey实现一对多关系,必须指定on_delete删除策略,支持related_name配置反向关系名称,
可设置null和blank允许空值,提供CASCADE、SET_NULL、PROTECT等多种删除行为。
b.代码示例
---
def get_users_with_details():
return CustomUser.objects.select_related(
def user_list_view(request):
return JsonResponse({'users': user_data})
---
3.3 数据库迁移系统
01.迁移基础概念
a.迁移文件作用
记录数据库结构的变更历史
实现数据库版本控制
支持团队协作开发
提供可重复的数据库操作
b.迁移文件结构
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
from django.db import migrations, models
class Migration(migrations.Migration):
---
c.迁移命令详解
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
略
02.自动迁移生成
a.字段添加
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
description = models.TextField()
sku = models.CharField(max_length=50, unique=True)
weight = models.FloatField(null=True)
is_digital = models.BooleanField()
tags = models.TextField()
---
b.字段修改
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=12, decimal_places=2)
description = models.TextField()
sku = models.CharField(max_length=100)
---
c.关系字段变更
a.功能说明
ForeignKey实现一对多关系,必须指定on_delete删除策略,支持related_name配置反向关系名称,
可设置null和blank允许空值,提供CASCADE、SET_NULL、PROTECT等多种删除行为。
b.代码示例
---
class Product(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey()
---
03.自定义迁移操作
a.数据迁移
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db import migrations
from django.db.models import F
def populate_product_skus(apps, schema_editor):
def reverse_populate_product_skus(apps, schema_editor):
class Migration(migrations.Migration):
def validate_product_data(apps, schema_editor):
class Migration(migrations.Migration):
---
b.SQL迁移
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db import migrations
class Migration(migrations.Migration):
---
c.条件迁移
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db import migrations
def create_postgresql_view(apps, schema_editor):
def create_mysql_view(apps, schema_editor):
def create_sqlite_view(apps, schema_editor):
class Migration(migrations.Migration):
---
04.迁移最佳实践
a.迁移管理策略
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
略
b.迁移测试
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.test import TestCase
from django.core.management import call_command
from store.models import Product, Category
class MigrationTestCase(TestCase):
def setUp(self):
def test_0002_add_product_stock(self):
def test_0006_populate_product_skus(self):
---
3.4 QuerySet查询API
01.基础查询操作
a.查询方法概览
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from store.models import Product, Category
---
b.查询过滤器
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from datetime import date, timedelta
from django.utils import timezone
---
c.查询链式调用
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db.models import Count, Avg, Max, Min, Sum
def search_products(query_text, min_price=None, max_price=None, category=None):
return queryset.order_by('-created_at')
---
02.复杂查询技巧
a.Q对象使用
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db.models import Q
def build_filter_query(criteria):
return query
from django.db.models import Count
---
b.F对象使用
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db.models import F
from datetime import timedelta
from django.utils import timezone
def update_product_prices():
---
c.聚合查询
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db.models import Avg, Count, Max, Min, Sum, StdDev, Variance
from store.models import Product
from django.db.models import Count
from django.db.models import IntegerField
from django.db.models.functions import Cast
from django.db.models import Window
from django.db.models.functions import RowNumber
---
03.查询优化技巧
a.select_related优化
a.功能说明
ForeignKey实现一对多关系,必须指定on_delete删除策略,支持related_name配置反向关系名称,
可设置null和blank允许空值,提供CASCADE、SET_NULL、PROTECT等多种删除行为。
b.代码示例
---
def get_products_with_details():
return Product.objects.select_related(
import time
from django.db import connection
def benchmark_queries():
---
b.prefetch_related优化
a.功能说明
ForeignKey实现一对多关系,必须指定on_delete删除策略,支持related_name配置反向关系名称,
可设置null和blank允许空值,提供CASCADE、SET_NULL、PROTECT等多种删除行为。
b.代码示例
---
from django.db.models import Prefetch
from django.db.models import Prefetch
def get_products_with_custom_relations():
return Product.objects.prefetch_related(
---
c.only和defer优化
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
def get_product_list(fields=None, exclude_fields=None):
return queryset
---
d.批量操作优化
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db import transaction
def create_products_bulk(product_data):
return created_products
def update_prices_bulk(updates):
def delete_inactive_products():
return deleted_count
import time
from django.db import connection
return queryset.iterator()
return queryset.defer(*large_fields)
return queryset.select_related('category')
return queryset
---
3.5 高级查询技巧
01.子查询
a.使用子查询进行复杂筛选
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db.models import Subquery, OuterRef
from store.models import Product, Category, Order, OrderItem
from django.db.models import Avg
from django.db.models import OuterRef, Subquery
from django.db.models import Exists
from django.db.models import OuterRef, Max, Subquery
def get_top_selling_products(limit=10):
from django.db.models import Subquery, OuterRef, Sum
from django.db.models import F
return products_with_rank
def get_cheaper_products():
from django.db.models import Subquery, OuterRef, Avg
return cheaper_products
---
b.Window函数
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db.models import Window, F, RowNumber
from django.db.models.functions import Lag, Lead
from django.db.models.functions import DenseRank, Rank
from django.db.models.functions import Avg
def analyze_product_trends():
return Product.objects.annotate(
def get_paginated_analysis(page=1, page_size=20):
return Product.objects.annotate(
---
c.CTE(通用表表达式)
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db.models import With
def get_category_statistics():
from django.db.models import Sum, Max, Min, With
return Category.objects.annotate(
def get_category_tree():
return Category.objects.filter(
def perform_complex_analysis():
return Product.objects.annotate(
---
02.原生SQL查询
a.raw()方法
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db.models import IntegerField
def get_products_by_price_range(min_price, max_price):
---
b.extra()方法
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
def get_products_with_markup():
return Product.objects.extra(
def get_inventory_status():
return Product.objects.extra(
---
c.cursor()方法
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db import connection
def execute_custom_query(sql, params=None):
return dict_results
def build_product_query(filters=None):
return sql, params
def call_stored_procedure(procedure_name, params=None):
return cursor.fetchall()
---
03.查询性能分析
a.查询计划分析
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
from django.db import connection
def analyze_query(queryset):
return plan
def monitor_query_performance(queryset, description=""):
import time
from django.db import connection
return result, execution_time, query_count
def get_product_analysis():
from store.models import Product
return {
return slow_queries, optimization_tips
---
b.查询优化策略
a.功能说明
该功能提供完整的实现示例,展示了核心配置和使用方法,包含必要的字段定义和业务逻辑处理。
b.代码示例
---
class QueryOptimizer:
@staticmethod
def optimize_queryset(queryset, context=None):
return queryset
@staticmethod
def _basic_optimization(queryset):
return queryset[:50]
return queryset
@staticmethod
def _optimize_for_list(queryset):
return queryset.iterator()
return queryset
return queryset
class ProductService:
return QueryOptimizer.optimize_queryset(
return QueryOptimizer.optimize_queryset(
return QueryOptimizer.optimize_queryset(
return func(optimized_queryset, *args[1:], **kwargs)
return func(*args, **kwargs)
return wrapper
return Product.objects.all()
class QueryBuilder:
return queryset
return queryset
return QueryOptimizer.optimize_queryset(queryset)
---
4 视图与路由
4.1 URL路由配置
01.URLconf基础
a.URL模式定义
a.功能说明
urlpatterns列表定义URL路由规则,path()函数处理简单路径转换,
re_path()函数处理正则表达式匹配,include()函数包含其他URL配置模块。
Django的URL配置通过urlpatterns列表集中管理所有路由规则,支持路径参数、正则表达式匹配和模块化配置。
b.代码示例
---
# urls.py
from django.contrib import admin
from django.urls import path, include, re_path
from apps.blog import views as blog_views
urlpatterns = [
# 管理后台
path('admin/', admin.site.urls),
# 首页
path('', blog_views.home, name='home'),
# 博客应用
path('blog/', blog_views.post_list, name='blog_list'),
path('blog/<int:year>/', blog_views.post_year, name='post_year'),
path('blog/<int:year>/<int:month>/', blog_views.post_month, name='post_month'),
path('blog/<int:year>/<int:month>/<slug:slug>/',
blog_views.post_detail, name='post_detail'),
# 正则表达式URL
re_path(r'^articles/(?P<year>\d{4})/$', blog_views.article_year),
re_path(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$',
blog_views.article_month),
# 包含其他URL配置
path('users/', include('apps.users.urls')),
path('api/', include('apps.api.urls')),
]
---
b.路径转换器
a.功能说明
Django内置路径转换器包括str(匹配非空字符串)、int(匹配0或正整数)、slug(匹配字母数字连字符下划线)、uuid(匹配UUID格式)、
path(匹配包含路径分隔符的字符串)。可通过定义包含regex、to_python()和to_url()方法的类来创建自定义转换器,
使用register_converter()注册后即可在URL模式中使用。
b.代码示例
---
# converters.py
from django.urls.converters import register_converter
class YearConverter:
regex = r'\d{4}'
def to_python(self, value):
return int(value)
def to_url(self, value):
return str(value)
class PositiveIntConverter:
regex = r'[1-9]\d*'
def to_python(self, value):
return int(value)
def to_url(self, value):
return str(value)
# 注册转换器
register_converter(YearConverter, 'yyyy')
register_converter(PositiveIntConverter, 'positive')
# urls.py中使用
urlpatterns = [
path('archive/<yyyy:year>/', views.archive_year),
path('post/<positive:post_id>/', views.post_detail),
]
---
c.命名URL和反向解析
a.功能说明
使用name参数为URL模式命名,通过reverse()函数或{% url %}模板标签根据名称生成URL,
命名空间机制避免不同应用间的URL名称冲突。反向解析使代码与URL结构解耦,修改URL模式时无需更改视图和模板中的硬编码路径。
b.代码示例
---
# urls.py
urlpatterns = [
path('blog/<int:post_id>/', blog_views.post_detail, name='post_detail'),
path('blog/<int:post_id>/edit/', blog_views.post_edit, name='post_edit'),
path('blog/<int:post_id>/delete/', blog_views.post_delete, name='post_delete'),
]
# views.py中使用reverse()
from django.urls import reverse
from django.shortcuts import redirect
def post_create(request):
if request.method == 'POST':
# 创建文章逻辑
post = create_post_from_form(request.POST)
return redirect('post_detail', post_id=post.id)
def post_delete(request, post_id):
post = get_object_or_404(Post, pk=post_id)
post.delete()
return redirect('post_list')
# 模板中使用{% url %}
"""
<a href="{% url 'post_detail' post.id %}">{{ post.title }}</a>
<a href="{% url 'post_edit' post.id %}">编辑</a>
<a href="{% url 'post_delete' post.id %}">删除</a>
"""
---
02.高级URL配置
a.命名空间
a.功能说明
应用命名空间通过app_name变量定义,实例命名空间通过include()的namespace参数指定,支持嵌套命名空间构建复杂URL结构。
命名空间使用冒号分隔(如'blog:detail'),在多应用项目中有效避免URL名称冲突,提高代码可维护性。
b.代码示例
---
# blog/urls.py
app_name = 'blog'
urlpatterns = [
path('', views.post_list, name='list'),
path('<int:pk>/', views.post_detail, name='detail'),
path('create/', views.post_create, name='create'),
]
# project/urls.py
from django.urls import path, include
urlpatterns = [
path('blog/', include('blog.urls', namespace='blog')),
path('news/', include('news.urls', namespace='news')),
]
# 视图中使用命名空间URL
from django.urls import reverse
def some_view(request):
blog_url = reverse('blog:list') # /blog/
news_url = reverse('news:detail', args=[1]) # /news/1/
# 模板中使用命名空间URL
"""
<a href="{% url 'blog:detail' post.pk %}">{{ post.title }}</a>
<a href="{% url 'news:list' %}">所有新闻</a>
"""
---
b.URL参数传递
a.功能说明
URL参数通过路径转换器或正则表达式命名组捕获并传递给视图函数,可使用字典向视图传递额外参数,
查询参数通过request.GET获取。参数传递支持位置参数和关键字参数两种方式,视图函数通过参数名接收URL中捕获的值。
b.代码示例
---
# urls.py
urlpatterns = [
# 路径转换器参数
path('user/<int:user_id>/', views.user_profile),
path('post/<slug:slug>/', views.post_detail),
# 正则表达式参数
re_path(r'^category/(?P<category_name>\w+)/$', views.category_posts),
# 额外参数
path('archive/', views.archive, {'show_all': False}),
]
# views.py
def user_profile(request, user_id):
user = get_object_or_404(User, pk=user_id)
return render(request, 'users/profile.html', {'user': user})
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
return render(request, 'blog/detail.html', {'post': post})
def category_posts(request, category_name):
posts = Post.objects.filter(category__name=category_name)
return render(request, 'blog/category.html', {
'category_name': category_name,
'posts': posts
})
def archive(request, show_all):
if show_all:
posts = Post.objects.all()
else:
posts = Post.objects.filter(published=True)
return render(request, 'blog/archive.html', {'posts': posts})
# 查询参数处理
def search_posts(request):
query = request.GET.get('q', '')
category = request.GET.get('category', '')
posts = Post.objects.all()
if query:
posts = posts.filter(title__icontains=query)
if category:
posts = posts.filter(category__name=category)
return render(request, 'blog/search.html', {
'posts': posts,
'query': query,
'category': category
})
---
c.动态URL生成
a.功能说明
通过reverse()函数和QueryDict类程序化构建URL,支持参数验证、URL格式化和复杂查询参数处理。
动态URL生成常用于分页、搜索过滤等场景,可根据业务逻辑灵活组装URL参数,保持代码的可维护性和灵活性。
b.代码示例
---
# utils.py
from django.urls import reverse
django.http import QueryDict
def build_url_with_params(url_name, page=1, **kwargs):
"""构建带查询参数的URL"""
base_url = reverse(url_name, kwargs=kwargs)
if page > 1:
query_dict = QueryDict(mutable=True)
query_dict['page'] = page
return f"{base_url}?{query_dict.urlencode()}"
return base_url
def build_search_url(query, category=None, page=1):
"""构建搜索URL"""
params = {'q': query}
if category:
params['category'] = category
if page > 1:
params['page'] = page
query_string = '&'.join([f"{k}={v}" for k, v in params.items()])
return f"/search/?{query_string}"
# 视图中使用
from django.core.paginator import Paginator
def post_list(request):
posts = Post.objects.all()
paginator = Paginator(posts, 10)
page = request.GET.get('page', 1)
posts_page = paginator.get_page(page)
# 构建分页URL
pagination_urls = []
for page_num in paginator.page_range:
url = build_url_with_params('post_list', page=page_num)
pagination_urls.append({
'number': page_num,
'url': url,
'is_current': page_num == posts_page.number
})
return render(request, 'blog/list.html', {
'posts': posts_page,
'pagination_urls': pagination_urls
})
---
03.错误处理和重定向
a.自定义错误页面
a.功能说明
通过handler404、handler500、handler403、handler400变量指定自定义错误处理视图,
分别处理页面未找到、服务器错误、权限错误和请求错误。
自定义错误页面提供友好的用户体验,可包含导航链接、错误说明和品牌元素。
b.代码示例
---
# urls.py
from django.urls import path
from . import views
handler404 = 'blog.views.custom_404'
handler500 = 'blog.views.custom_500'
handler403 = 'blog.views.custom_403'
handler400 = 'blog.views.custom_400'
# views.py
from django.shortcuts import render
from django.http import HttpResponseNotFound, HttpResponseServerError
def custom_404(request, exception):
return render(request, 'errors/404.html', status=404)
def custom_500(request):
return render(request, 'errors/500.html', status=500)
def custom_403(request, exception):
return render(request, 'errors/403.html', status=403)
def custom_400(request, exception):
return render(request, 'errors/400.html', status=400)
# 模板示例 templates/errors/404.html
"""
<!DOCTYPE html>
<html>
<head>
<title>页面未找到 - 404</title>
</head>
<body>
<h1>页面未找到</h1>
<p>抱歉,您访问的页面不存在。</p>
<p><a href="{% url 'home' %}">返回首页</a></p>
</body>
</html>
"""
---
b.URL重定向
a.功能说明
使用redirect()函数实现临时重定向,permanent参数设为True实现永久重定向,
RedirectView类视图提供声明式重定向配置。重定向支持基于条件的动态跳转,可根据用户状态、权限或业务逻辑选择目标URL。
b.代码示例
---
from django.shortcuts import redirect, get_object_or_404
from django.urls import reverse
from django.views.generic import RedirectView
# 函数视图重定向
def old_post_detail(request, post_id):
"""旧的文章详情页,重定向到新的URL结构"""
post = get_object_or_404(Post, pk=post_id)
return redirect('post_detail', slug=post.slug, permanent=True)
def login_redirect(request):
"""根据用户类型重定向到不同页面"""
if request.user.is_authenticated:
if request.user.is_staff:
return redirect('admin:index')
else:
return redirect('user_dashboard')
else:
return redirect('login')
# 类视图重定向
class PostRedirectView(RedirectView):
permanent = False
query_string = True
pattern_name = 'post_detail'
def get_redirect_url(self, *args, **kwargs):
post = get_object_or_404(Post, pk=kwargs['post_id'])
kwargs.update({'slug': post.slug})
return super().get_redirect_url(*args, **kwargs)
# URL配置
urlpatterns = [
# 旧URL重定向到新URL
path('old-post/<int:post_id>/', RedirectView.as_view(
url='/post/%(post_id)s/',
permanent=True
)),
# 基于模式的URL重定向
path('blog/post/<int:post_id>/', RedirectView.as_view(
pattern_name='post_detail',
permanent=True
)),
]
---
c.中间件级别的URL处理
a.功能说明
通过自定义中间件实现URL重写、请求拦截和响应后处理,在请求到达视图前或响应返回前执行全局URL处理逻辑。
中间件可处理URL尾部斜杠、多语言路径前缀、域名路由等跨应用的URL需求。
b.代码示例
---
# middleware.py
from django.http import HttpResponsePermanentRedirect
from django.urls import resolve
class URLTrailingSlashMiddleware:
"""处理URL尾部斜杠的中间件"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 移除末尾斜杠(Django默认处理)
if request.path.endswith('/') and len(request.path) > 1:
new_path = request.path.rstrip('/')
if not self.is_static_file(new_path):
return HttpResponsePermanentRedirect(new_path)
return self.get_response(request)
def is_static_file(self, path):
"""检查是否为静态文件"""
static_extensions = ['.css', '.js', '.png', '.jpg', '.gif']
return any(path.endswith(ext) for ext in static_extensions)
class LocaleURLMiddleware:
"""本地化URL中间件"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 从URL中提取语言代码
path_parts = request.path.split('/')
if len(path_parts) > 1 and path_parts[1] in ['en', 'zh', 'fr']:
request.LANGUAGE_CODE = path_parts[1]
# 移除语言前缀继续处理
request.path_info = '/' + '/'.join(path_parts[2:])
response = self.get_response(request)
return response
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'myapp.middleware.LocaleURLMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'myapp.middleware.URLTrailingSlashMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
]
---
4.2 函数视图
01.基础函数视图
a.视图函数定义
a.功能说明
视图函数接收HttpRequest对象作为第一个参数,返回HttpResponse对象,负责处理HTTP方法和请求参数。
视图函数是Django处理请求的核心,通过render()渲染模板、redirect()重定向、JsonResponse()返回JSON等方式生成响应。
b.代码示例
---
from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponse, JsonResponse, Http404
from .models import Post, Category
from .forms import PostForm
def home_view(request):
"""首页视图"""
latest_posts = Post.objects.order_by('-created_at')[:5]
categories = Category.objects.all()
context = {
'latest_posts': latest_posts,
'categories': categories,
'page_title': '欢迎来到我的博客'
}
return render(request, 'blog/home.html', context)
def post_list_view(request):
"""文章列表视图"""
posts = Post.objects.filter(published=True)
# 处理分类过滤
category_id = request.GET.get('category')
if category_id:
posts = posts.filter(category_id=category_id)
# 处理搜索
search_query = request.GET.get('q')
if search_query:
posts = posts.filter(title__icontains=search_query)
context = {
'posts': posts,
'category_id': category_id,
'search_query': search_query
}
return render(request, 'blog/post_list.html', context)
def post_detail_view(request, post_id):
"""文章详情视图"""
try:
post = Post.objects.get(pk=post_id, published=True)
except Post.DoesNotExist:
raise Http404("文章不存在")
# 增加浏览次数
post.views_count += 1
post.save(update_fields=['views_count'])
# 获取相关文章
related_posts = Post.objects.filter(
category=post.category
).exclude(pk=post.pk)[:3]
context = {
'post': post,
'related_posts': related_posts
}
return render(request, 'blog/post_detail.html', context)
---
b.HTTP方法处理
a.功能说明
通过request.method检查请求方法类型,使用require_http_methods、require_GET、require_POST等装饰器限制允许的HTTP方法。
不同HTTP方法对应不同的业务逻辑,GET用于查询数据,POST用于提交表单,PUT/DELETE用于RESTful API操作。
b.代码示例
---
from django.views.decorators.http import require_http_methods, require_GET, require_POST
from django.contrib.auth.decorators import login_required
@require_GET
def post_create_view(request):
"""创建文章表单页面"""
if not request.user.is_authenticated:
return redirect('login')
form = PostForm()
return render(request, 'blog/post_create.html', {'form': form})
@require_http_methods(["GET", "POST"])
@login_required
def post_edit_view(request, post_id):
"""编辑文章视图"""
post = get_object_or_404(Post, pk=post_id, author=request.user)
if request.method == 'GET':
form = PostForm(instance=post)
return render(request, 'blog/post_edit.html', {
'form': form,
'post': post
})
elif request.method == 'POST':
form = PostForm(request.POST, request.FILES, instance=post)
if form.is_valid():
post = form.save(commit=False)
post.updated_at = timezone.now()
post.save()
return redirect('post_detail', post_id=post.id)
else:
return render(request, 'blog/post_edit.html', {
'form': form,
'post': post
})
@require_POST
def post_delete_view(request, post_id):
"""删除文章视图"""
post = get_object_or_404(Post, pk=post_id, author=request.user)
# CSRF保护
if not request.POST.get('confirm'):
return render(request, 'blog/post_delete_confirm.html', {
'post': post
})
post.delete()
return redirect('post_list')
def api_like_post_view(request, post_id):
"""点赞文章API视图"""
if request.method == 'POST':
post = get_object_or_404(Post, pk=post_id)
if request.user.is_authenticated:
# 处理用户点赞逻辑
if post.likes.filter(pk=request.user.pk).exists():
post.likes.remove(request.user)
liked = False
else:
post.likes.add(request.user)
liked = True
return JsonResponse({
'success': True,
'liked': liked,
'likes_count': post.likes.count()
})
else:
return JsonResponse({
'success': False,
'error': '需要登录'
}, status=401)
# 其他方法不支持
return JsonResponse({
'success': False,
'error': '不支持的请求方法'
}, status=405)
---
c.请求参数处理
a.功能说明
GET参数从request.GET获取,POST参数从request.POST获取,文件上传从request.FILES获取,请求元数据从request.META获取。
参数处理包括数据验证、类型转换、默认值设置,支持复杂的查询过滤、分页和排序逻辑。
b.代码示例
---
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.db.models import Q
import json
def advanced_search_view(request):
"""高级搜索视图"""
# GET参数处理
query = request.GET.get('q', '').strip()
category_id = request.GET.get('category')
date_from = request.GET.get('date_from')
date_to = request.GET.get('date_to')
sort_by = request.GET.get('sort', 'latest')
page = request.GET.get('page', 1)
# 构建查询
posts = Post.objects.filter(published=True)
if query:
posts = posts.filter(
Q(title__icontains=query) |
Q(content__icontains=query) |
Q(author__username__icontains=query)
)
if category_id:
posts = posts.filter(category_id=category_id)
if date_from:
posts = posts.filter(created_at__date__gte=date_from)
if date_to:
posts = posts.filter(created_at__date__lte=date_to)
# 排序
sort_options = {
'latest': '-created_at',
'oldest': 'created_at',
'popular': '-views_count',
'title': 'title'
}
if sort_by in sort_options:
posts = posts.order_by(sort_options[sort_by])
# 分页处理
paginator = Paginator(posts, 10)
try:
posts_page = paginator.get_page(page)
except PageNotAnInteger:
posts_page = paginator.get_page(1)
except EmptyPage:
posts_page = paginator.get_page(paginator.num_pages)
# 构建搜索上下文
context = {
'posts': posts_page,
'paginator': paginator,
'search_params': {
'q': query,
'category': category_id,
'date_from': date_from,
'date_to': date_to,
'sort': sort_by
}
}
return render(request, 'blog/advanced_search.html', context)
def json_api_view(request):
"""JSON API视图"""
if request.method == 'POST':
try:
# 解析JSON数据
data = json.loads(request.body)
title = data.get('title')
content = data.get('content')
# 验证数据
if not title or not content:
return JsonResponse({
'success': False,
'error': '标题和内容不能为空'
}, status=400)
# 创建文章
post = Post.objects.create(
title=title,
content=content,
author=request.user
)
return JsonResponse({
'success': True,
'post_id': post.id,
'message': '文章创建成功'
})
except json.JSONDecodeError:
return JsonResponse({
'success': False,
'error': '无效的JSON数据'
}, status=400)
# GET请求返回文章列表
posts = Post.objects.filter(published=True)
posts_data = [{
'id': post.id,
'title': post.title,
'author': post.author.username,
'created_at': post.created_at.isoformat()
} for post in posts]
return JsonResponse({
'success': True,
'posts': posts_data
})
---
02.装饰器应用
a.认证和权限装饰器
a.功能说明
login_required装饰器要求用户登录,user_passes_test执行自定义用户测试,permission_required检查用户权限。
认证装饰器在视图执行前验证用户身份和权限,未通过验证时自动重定向到登录页面或返回403错误。
b.代码示例
---
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.mixins import UserPassesTestMixin
from django.contrib.auth.models import User
def is_author(user):
"""检查用户是否为作者角色"""
return user.is_authenticated and user.groups.filter(name='authors').exists()
def is_staff_user(user):
"""检查用户是否为管理员"""
return user.is_authenticated and user.is_staff
@login_required(login_url='/login/')
def user_dashboard_view(request):
"""用户仪表板"""
user_posts = Post.objects.filter(author=request.user)
user_comments = Comment.objects.filter(user=request.user)
context = {
'user_posts': user_posts,
'user_comments': user_comments,
'posts_count': user_posts.count(),
'comments_count': user_comments.count()
}
return render(request, 'users/dashboard.html', context)
@user_passes_test(is_author, login_url='/login/')
@login_required
def author_tools_view(request):
"""作者工具页面"""
published_posts = Post.objects.filter(author=request.user, published=True)
draft_posts = Post.objects.filter(author=request.user, published=False)
context = {
'published_posts': published_posts,
'draft_posts': draft_posts,
'total_views': sum(post.views_count for post in published_posts)
}
return render(request, 'authors/tools.html', context)
@user_passes_test(is_staff_user, login_url='/login/')
def admin_statistics_view(request):
"""管理员统计页面"""
from django.db.models import Count, Sum
stats = {
'total_users': User.objects.count(),
'total_posts': Post.objects.count(),
'total_comments': Comment.objects.count(),
'total_views': Post.objects.aggregate(total=Sum('views_count'))['total'] or 0,
'posts_by_category': Post.objects.values('category__name').annotate(count=Count('id'))
}
return render(request, 'admin/statistics.html', {'stats': stats})
# 自定义装饰器
def post_author_required(view_func):
"""要求用户为文章作者"""
def wrapper(request, *args, **kwargs):
post_id = kwargs.get('post_id')
post = get_object_or_404(Post, pk=post_id)
if post.author != request.user:
return HttpResponseForbidden("您没有权限访问此页面")
return view_func(request, *args, **kwargs)
return wrapper
@post_author_required
def edit_own_post_view(request, post_id):
"""编辑自己的文章"""
post = get_object_or_404(Post, pk=post_id)
# 编辑逻辑
return render(request, 'blog/edit_post.html', {'post': post})
---
b.HTTP方法和缓存装饰器
a.功能说明
require_http_methods限制允许的HTTP方法,cache_page实现页面级缓存,vary_on_headers根据请求头变化缓存不同版本。
缓存装饰器显著提升响应速度,减少数据库查询和计算开销,适用于内容相对稳定的页面。
b.代码示例
---
from django.views.decorators.http import require_GET, require_POST, require_http_methods
from django.views.decorators.cache import cache_page, never_cache, vary_on_cookie
from django.views.decorators.vary import vary_on_headers
from django.utils.decorators import method_decorator
from django.core.cache import cache
import time
@cache_page(60 * 15) # 缓存15分钟
@vary_on_cookie
def popular_posts_view(request):
"""热门文章页面(缓存)"""
popular_posts = Post.objects.filter(
published=True
).order_by('-views_count')[:10]
return render(request, 'blog/popular_posts.html', {
'posts': popular_posts,
'cached_at': time.time()
})
@vary_on_headers('User-Agent')
@cache_page(60 * 5) # 缓存5分钟
def home_page_view(request):
"""首页(根据User-Agent变化缓存)"""
featured_posts = Post.objects.filter(featured=True, published=True)
recent_posts = Post.objects.order_by('-created_at')[:5]
return render(request, 'blog/home.html', {
'featured_posts': featured_posts,
'recent_posts': recent_posts
})
@never_cache # 禁止缓存
def user_profile_view(request):
"""用户资料页(禁止缓存)"""
if not request.user.is_authenticated:
return redirect('login')
return render(request, 'users/profile.html')
@require_GET
def static_info_view(request):
"""静态信息页面(仅GET请求)"""
return render(request, 'info/about.html')
@require_http_methods(["GET", "POST"])
def contact_view(request):
"""联系我们页面"""
if request.method == 'POST':
# 处理联系表单提交
name = request.POST.get('name')
email = request.POST.get('email')
message = request.POST.get('message')
# 发送邮件或保存消息
# ...
return render(request, 'contact/success.html')
return render(request, 'contact/form.html')
# 自定义缓存装饰器
def custom_cache(cache_timeout=300):
"""自定义缓存装饰器"""
def decorator(view_func):
def wrapper(request, *args, **kwargs):
# 生成缓存键
cache_key = f"{view_func.__name__}:{request.path}:{request.GET.urlencode()}"
# 尝试从缓存获取
cached_response = cache.get(cache_key)
if cached_response:
return cached_response
# 执行视图并缓存结果
response = view_func(request, *args, **kwargs)
cache.set(cache_key, response, cache_timeout)
return response
return wrapper
return decorator
@custom_cache(cache_timeout=60 * 10)
def trending_topics_view(request):
"""热门话题页面(自定义缓存)"""
# 计算热门话题的逻辑
topics = calculate_trending_topics()
return render(request, 'blog/trending_topics.html', {
'topics': topics
})
---
c.请求处理装饰器
a.功能说明
gzip_page压缩响应内容减少传输大小,csrf_protect启用CSRF保护,ensure_csrf_cookie确保CSRF cookie存在,
xframe_options_exempt允许页面被iframe嵌入。这些装饰器处理安全、性能和兼容性需求,可灵活组合使用。
b.代码示例
---
from django.views.decorators.gzip import gzip_page
from django.views.decorators.csrf import csrf_protect, ensure_csrf_cookie, csrf_exempt
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.decorators.debug import sensitive_post_parameters, sensitive_variables
import json
@gzip_page
@cache_page(60 * 30) # 压缩并缓存30分钟
def large_content_view(request):
"""大内容页面(启用gzip压缩)"""
# 生成大量内容
large_data = generate_large_data()
return render(request, 'blog/large_content.html', {
'large_data': large_data
})
@csrf_protect
def secure_form_view(request):
"""安全表单页面(CSRF保护)"""
if request.method == 'POST':
# 处理表单数据
title = request.POST.get('title')
content = request.POST.get('content')
# CSRF保护已启用
# 表单处理逻辑
return redirect('form_success')
return render(request, 'forms/secure_form.html')
@ensure_csrf_cookie
def api_csrf_view(request):
"""API视图(确保CSRF cookie)"""
if request.method == 'GET':
# 返回包含CSRF token的响应
return JsonResponse({
'message': 'CSRF cookie已设置'
})
@csrf_exempt
def webhook_view(request):
"""Webhook接收端(免除CSRF保护)"""
if request.method == 'POST':
try:
data = json.loads(request.body)
# 处理webhook数据
process_webhook_data(data)
return JsonResponse({'status': 'success'})
except Exception as e:
return JsonResponse({
'status': 'error',
'message': str(e)
}, status=400)
return JsonResponse({'status': 'ok'})
@xframe_options_exempt
def embed_content_view(request):
"""可嵌入内容页面(免除X-Frame-Options)"""
return render(request, 'embed/content.html')
@sensitive_post_parameters('password', 'credit_card')
def sensitive_form_view(request):
"""敏感表单处理"""
if request.method == 'POST':
password = request.POST.get('password')
credit_card = request.POST.get('credit_card')
# Django将在错误报告中隐藏这些敏感参数
# 处理逻辑
return redirect('success')
return render(request, 'forms/sensitive_form.html')
@sensitive_variables('api_key', 'secret')
def api_call_view(request):
"""API调用视图(敏感变量)"""
api_key = 'super_secret_api_key'
secret = 'top_secret_code'
# 这些变量在错误报告中将被隐藏
result = make_api_call(api_key, secret)
return JsonResponse({'result': result})
---
4.3 类视图(CBV)
01.基础类视图
a.View基类
a.功能说明
Django.views.View是所有类视图的基类,支持HTTP方法自动分发,将GET/POST等请求分发到对应的get()/post()方法处理。
View基类提供dispatch()方法控制请求分发流程,支持装饰器和Mixin扩展功能。
b.代码示例
---
from django.views import View
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.http import JsonResponse
from .models import Post
from .forms import PostForm
class PostCreateView(View):
"""创建文章的类视图"""
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
# 调用父类dispatch方法
return super().dispatch(request, *args, **kwargs)
def get(self, request):
"""处理GET请求,显示创建表单"""
form = PostForm()
return render(request, 'blog/post_create.html', {'form': form})
def post(self, request):
"""处理POST请求,创建文章"""
form = PostForm(request.POST, request.FILES)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect('post_detail', pk=post.pk)
return render(request, 'blog/post_create.html', {'form': form})
class PostUpdateView(View):
"""更新文章的类视图"""
def get_object(self, pk):
"""获取文章对象"""
return get_object_or_404(Post, pk=pk, author=self.request.user)
def get(self, request, pk):
"""显示编辑表单"""
post = self.get_object(pk)
form = PostForm(instance=post)
return render(request, 'blog/post_edit.html', {
'form': form,
'post': post
})
def post(self, request, pk):
"""处理文章更新"""
post = self.get_object(pk)
form = PostForm(request.POST, request.FILES, instance=post)
if form.is_valid():
updated_post = form.save(commit=False)
updated_post.updated_at = timezone.now()
updated_post.save()
return redirect('post_detail', pk=updated_post.pk)
return render(request, 'blog/post_edit.html', {
'form': form,
'post': post
})
class PostDeleteView(View):
"""删除文章的类视图"""
def get_object(self, pk):
"""获取文章对象"""
return get_object_or_404(Post, pk=pk, author=self.request.user)
def get(self, request, pk):
"""显示删除确认页面"""
post = self.get_object(pk)
return render(request, 'blog/post_delete.html', {'post': post})
def post(self, request, pk):
"""处理删除操作"""
post = self.get_object(pk)
post.delete()
return redirect('post_list')
class PostListView(View):
"""文章列表类视图"""
def get_queryset(self):
"""获取查询集"""
return Post.objects.filter(published=True)
def get_context_data(self):
"""获取上下文数据"""
queryset = self.get_queryset()
# 处理搜索查询
search_query = self.request.GET.get('q')
if search_query:
queryset = queryset.filter(title__icontains=search_query)
# 处理分类过滤
category_id = self.request.GET.get('category')
if category_id:
queryset = queryset.filter(category_id=category_id)
return {
'posts': queryset,
'search_query': search_query or '',
'category_id': category_id or ''
}
def get(self, request):
"""处理GET请求"""
context = self.get_context_data()
return render(request, 'blog/post_list.html', context)
---
b.TemplateView
a.功能说明
TemplateView用于渲染模板并返回HTML响应,通过template_name指定模板,通过get_context_data()添加上下文数据。
适用于静态页面、关于页面、联系页面等不需要复杂数据处理的场景,支持缓存和模板继承。
b.代码示例
---
from django.views.generic.base import TemplateView
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
from .models import Post, Category
@method_decorator(cache_page(60 * 15)) # 缓存15分钟
class HomePageView(TemplateView):
"""首页模板视图"""
template_name = 'blog/home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# 添加最新文章
context['latest_posts'] = Post.objects.filter(
published=True
).order_by('-created_at')[:6]
# 添加分类
context['categories'] = Category.objects.all()[:8]
# 添加推荐文章
context['featured_posts'] = Post.objects.filter(
featured=True,
published=True
).order_by('-created_at')[:3]
context['page_title'] = '欢迎来到我的博客'
return context
class AboutView(TemplateView):
"""关于我们页面"""
template_name = 'info/about.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page_title'] = '关于我们'
context['team_members'] = [
{'name': '张三', 'role': '技术总监'},
{'name': '李四', 'role': '产品经理'},
{'name': '王五', 'role': '设计师'},
]
return context
class ContactView(TemplateView):
"""联系我们页面"""
template_name = 'info/contact.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['contact_info'] = {
'email': '[email protected]',
'phone': '+86 123 4567 8900',
'address': '北京市朝阳区xxx街道xxx号'
}
return context
def post(self, request):
"""处理联系表单提交"""
name = request.POST.get('name')
email = request.POST.get('email')
message = request.POST.get('message')
# 验证表单数据
if not all([name, email, message]):
context = self.get_context_data()
context['error'] = '请填写所有必填字段'
context['form_data'] = request.POST
return self.render_to_response(context)
# 发送邮件或保存消息
# send_contact_email(name, email, message)
context = self.get_context_data()
context['success'] = True
return self.render_to_response(context)
class ErrorPageView(TemplateView):
"""错误页面视图"""
def get_template_names(self):
"""根据参数选择模板"""
error_type = self.kwargs.get('error_type', '404')
return f'errors/{error_type}.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['error_type'] = self.kwargs.get('error_type', '404')
return context
# URL配置
urlpatterns = [
path('', HomePageView.as_view(), name='home'),
path('about/', AboutView.as_view(), name='about'),
path('contact/', ContactView.as_view(), name='contact'),
path('error/<str:error_type>/', ErrorPageView.as_view(), name='error_page'),
]
---
c.RedirectView
a.功能说明
RedirectView实现URL重定向功能,permanent属性控制永久或临时重定向,pattern_name指定目标URL模式,url属性设置硬编码URL。
通过get_redirect_url()方法动态生成重定向目标,支持查询参数保留和条件重定向。
b.代码示例
---
from django.views.generic.base import RedirectView
from django.shortcuts import get_object_or_404
from django.urls import reverse
from .models import Post
class PostRedirectView(RedirectView):
"""文章重定向视图"""
permanent = False # 临时重定向
query_string = True # 保留查询参数
pattern_name = 'post_detail' # URL模式名称
def get_redirect_url(self, *args, **kwargs):
"""获取重定向URL"""
# 获取文章对象
post = get_object_or_404(Post, pk=kwargs['post_id'])
# 使用文章的slug作为新URL参数
kwargs['slug'] = post.slug
return super().get_redirect_url(*args, **kwargs)
class CategoryRedirectView(RedirectView):
"""分类重定向视图"""
permanent = True # 永久重定向
def get_redirect_url(self, *args, **kwargs):
"""根据分类名称重定向"""
category_slug = kwargs.get('category_slug')
if category_slug:
return reverse('category_posts', kwargs={'slug': category_slug})
return reverse('home')
class ExternalRedirectView(RedirectView):
"""外部URL重定向视图"""
url = 'https://example.com'
permanent = False
def get_redirect_url(self, *args, **kwargs):
"""可配置的外部重定向"""
target_url = self.request.GET.get('url', self.url)
# 简单的URL验证
if target_url.startswith(('http://', 'https://')):
return target_url
return reverse('home')
class DomainRedirectView(RedirectView):
"""域名重定向视图"""
permanent = False
def get_redirect_url(self, *args, **kwargs):
"""根据域名重定向到不同页面"""
domain = self.request.get_host()
if 'mobile' in domain:
return reverse('mobile_home')
elif 'admin' in domain:
return reverse('admin:index')
else:
return reverse('desktop_home')
# URL配置
urlpatterns = [
# 旧URL重定向到新URL
path('post/<int:post_id>/', PostRedirectView.as_view(), name='post_redirect'),
# 硬编码URL重定向
path('old-blog/', RedirectView.as_view(
url='/blog/',
permanent=True
), name='old_blog_redirect'),
# 外部链接重定向
path('go/', ExternalRedirectView.as_view(), name='external_redirect'),
# 分类重定向
path('category/<str:category_slug>/',
CategoryRedirectView.as_view(),
name='category_redirect'),
]
---
4.4 通用视图
01.列表和详情视图
a.ListView
a.功能说明
ListView显示对象列表,通过model属性指定模型,paginate_by设置分页,ordering定义排序规则。
通过get_queryset()自定义查询逻辑,get_context_data()添加额外上下文,支持搜索、过滤、分类等复杂列表展示需求。
b.代码示例
---
from django.views.generic import ListView
from django.shortcuts import get_object_or_404
from django.db.models import Q
from .models import Post, Category
from .forms import SearchForm
class PostListView(ListView):
"""文章列表视图"""
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10 # 每页10条记录
ordering = '-created_at' # 默认排序
def get_queryset(self):
"""自定义查询集"""
queryset = super().get_queryset().filter(published=True)
# 分类过滤
category_slug = self.kwargs.get('category_slug')
if category_slug:
category = get_object_or_404(Category, slug=category_slug)
queryset = queryset.filter(category=category)
# 搜索过滤
search_query = self.request.GET.get('q')
if search_query:
queryset = queryset.filter(
Q(title__icontains=search_query) |
Q(content__icontains=search_query) |
Q(author__username__icontains=search_query)
)
# 作者过滤
author_id = self.request.GET.get('author')
if author_id:
queryset = queryset.filter(author_id=author_id)
return queryset
def get_context_data(self, **kwargs):
"""添加额外的上下文数据"""
context = super().get_context_data(**kwargs)
# 添加分类列表
context['categories'] = Category.objects.all()
# 添加当前分类
category_slug = self.kwargs.get('category_slug')
if category_slug:
context['current_category'] = get_object_or_404(Category, slug=category_slug)
# 添加搜索表单
context['search_form'] = SearchForm(self.request.GET or None)
# 添加排序选项
context['sort_options'] = [
{'value': 'created_at', 'label': '创建时间'},
{'value': '-created_at', 'label': '最新创建'},
{'value': 'views_count', 'label': '浏览次数'},
{'value': '-views_count', 'label': '热门程度'},
{'value': 'title', 'label': '标题'},
]
# 当前排序
context['current_sort'] = self.request.GET.get('sort', '-created_at')
return context
class CategoryPostListView(ListView):
"""分类文章列表视图"""
model = Post
template_name = 'blog/category_posts.html'
context_object_name = 'posts'
paginate_by = 8
def get_queryset(self):
"""获取特定分类的文章"""
category = get_object_or_404(Category, slug=self.kwargs['slug'])
return Post.objects.filter(category=category, published=True)
def get_context_data(self, **kwargs):
"""添加分类信息到上下文"""
context = super().get_context_data(**kwargs)
category = get_object_or_404(Category, slug=self.kwargs['slug'])
context['category'] = category
context['page_title'] = f'{category.name} - 分类文章'
return context
class AuthorPostListView(ListView):
"""作者文章列表视图"""
model = Post
template_name = 'blog/author_posts.html'
context_object_name = 'posts'
paginate_by = 6
def get_queryset(self):
"""获取特定作者的文章"""
author_id = self.kwargs.get('author_id')
return Post.objects.filter(author_id=author_id, published=True)
def get_context_data(self, **kwargs):
"""添加作者信息到上下文"""
context = super().get_context_data(**kwargs)
from django.contrib.auth.models import User
author = get_object_or_404(User, pk=self.kwargs['author_id'])
context['author'] = author
context['page_title'] = f'{author.username} - 文章列表'
return context
---
b.DetailView
a.功能说明
DetailView显示单个对象详情,通过pk或slug参数查询对象,slug_url_kwarg和slug_field配置slug字段映射。
通过get_object()自定义对象获取逻辑,get_context_data()添加相关数据如评论、相关文章等,适用于文章详情、用户资料等场景。
b.代码示例
---
from django.views.generic import DetailView
from django.shortcuts import get_object_or_404
from django.contrib.auth.models import User
from django.utils import timezone
from .models import Post, Comment
from .forms import CommentForm
class PostDetailView(DetailView):
"""文章详情视图"""
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
slug_url_kwarg = 'slug' # URL参数名称
slug_field = 'slug' # 模型字段名称
pk_url_kwarg = 'pk' # 主键URL参数名称
def get_queryset(self):
"""自定义查询集"""
return Post.objects.filter(published=True)
def get_object(self, queryset=None):
"""获取对象并增加浏览次数"""
if queryset is None:
queryset = self.get_queryset()
# 根据slug或pk获取对象
slug = self.kwargs.get(self.slug_url_kwarg)
pk = self.kwargs.get(self.pk_url_kwarg)
if slug:
obj = get_object_or_404(queryset, **{self.slug_field: slug})
else:
obj = get_object_or_404(queryset, pk=pk)
# 增加浏览次数
obj.views_count += 1
obj.save(update_fields=['views_count'])
return obj
def get_context_data(self, **kwargs):
"""添加额外的上下文数据"""
context = super().get_context_data(**kwargs)
post = context['post']
# 获取相关文章
context['related_posts'] = Post.objects.filter(
category=post.category
).exclude(pk=post.pk).filter(published=True)[:3]
# 获取评论
context['comments'] = post.comments.filter(
approved=True
).order_by('created_at')
# 添加评论表单
if self.request.user.is_authenticated:
context['comment_form'] = CommentForm()
# 获取作者其他文章
context['author_posts'] = Post.objects.filter(
author=post.author,
published=True
).exclude(pk=post.pk)[:5]
return context
class UserProfileView(DetailView):
"""用户资料视图"""
model = User
template_name = 'users/profile.html'
context_object_name = 'user_profile'
pk_url_kwarg = 'user_id'
def get_context_data(self, **kwargs):
"""添加用户相关数据到上下文"""
context = super().get_context_data(**kwargs)
user = context['user_profile']
# 用户统计信息
from django.db.models import Count, Sum
context['user_stats'] = {
'posts_count': Post.objects.filter(author=user, published=True).count(),
'total_views': Post.objects.filter(author=user, published=True).aggregate(
total=Sum('views_count')
)['total'] or 0,
'comments_count': Comment.objects.filter(user=user).count(),
}
# 最新文章
context['latest_posts'] = Post.objects.filter(
author=user,
published=True
).order_by('-created_at')[:6]
return context
class CategoryDetailView(DetailView):
"""分类详情视图"""
model = Category
template_name = 'blog/category_detail.html'
context_object_name = 'category'
slug_url_kwarg = 'slug'
def get_context_data(self, **kwargs):
"""添加分类相关数据到上下文"""
context = super().get_context_data(**kwargs)
category = context['category']
# 分页显示分类下的文章
from django.core.paginator import Paginator
posts = Post.objects.filter(
category=category,
published=True
).order_by('-created_at')
paginator = Paginator(posts, 12)
page = self.request.GET.get('page', 1)
posts_page = paginator.get_page(page)
context['posts_page'] = posts_page
context['posts_count'] = posts.count()
return context
---
c.列表详情混合使用
a.功能说明
通过判断URL参数动态选择显示列表或详情,使用SingleObjectMixin和ListView组合实现混合视图。
混合视图减少代码重复,统一处理列表和详情的公共逻辑,适用于需要在同一页面切换列表和详情的场景。
b.代码示例
---
from django.views.generic import ListView, DetailView
from django.views import View
from django.shortcuts import render, get_object_or_404
class PostListDetailView(View):
"""文章列表详情混合视图"""
def get(self, request, slug=None, pk=None):
"""根据参数决定显示列表还是详情"""
if slug or pk:
# 显示详情
return self.get_detail_view(request, slug, pk)
else:
# 显示列表
return self.get_list_view(request)
def get_detail_view(self, request, slug, pk):
"""获取详情视图内容"""
if slug:
post = get_object_or_404(Post, slug=slug, published=True)
else:
post = get_object_or_404(Post, pk=pk, published=True)
# 增加浏览次数
post.views_count += 1
post.save(update_fields=['views_count'])
# 获取相关数据
related_posts = Post.objects.filter(
category=post.category
).exclude(pk=post.pk).filter(published=True)[:3]
comments = post.comments.filter(approved=True).order_by('created_at')
context = {
'post': post,
'related_posts': related_posts,
'comments': comments,
'view_mode': 'detail'
}
return render(request, 'blog/post_detail.html', context)
def get_list_view(self, request):
"""获取列表视图内容"""
posts = Post.objects.filter(published=True).order_by('-created_at')
# 处理搜索和过滤
search_query = request.GET.get('q')
category_id = request.GET.get('category')
if search_query:
posts = posts.filter(title__icontains=search_query)
if category_id:
posts = posts.filter(category_id=category_id)
# 分页处理
from django.core.paginator import Paginator
paginator = Paginator(posts, 10)
page = request.GET.get('page', 1)
posts_page = paginator.get_page(page)
context = {
'posts_page': posts_page,
'paginator': paginator,
'search_query': search_query or '',
'category_id': category_id or '',
'view_mode': 'list'
}
return render(request, 'blog/post_list.html', context)
# 使用通用视图类实现类似功能
from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
class PostMixinView(SingleObjectMixin, ListView):
"""文章混合视图"""
model = Post
template_name = 'blog/post_mixed.html'
context_object_name = 'posts'
paginate_by = 10
def get(self, request, *args, **kwargs):
# 根据URL参数决定使用单个对象还是列表
if 'slug' in kwargs or 'pk' in kwargs:
self.object_list = None # 不使用对象列表
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
else:
self.object = None
self.object_list = self.get_queryset()
context = self.get_context_data()
return self.render_to_response(context)
def get_queryset(self):
"""获取文章列表查询集"""
return Post.objects.filter(published=True)
def get_object(self, queryset=None):
"""获取单个文章对象"""
if queryset is None:
queryset = self.get_queryset()
slug = self.kwargs.get('slug')
pk = self.kwargs.get('pk')
if slug:
return get_object_or_404(queryset, slug=slug)
else:
return get_object_or_404(queryset, pk=pk)
def get_context_data(self, **kwargs):
"""构建上下文数据"""
context = super().get_context_data(**kwargs)
if hasattr(self, 'object') and self.object:
# 详情视图上下文
post = self.object
context['post'] = post
context['view_mode'] = 'detail'
# 相关文章
context['related_posts'] = Post.objects.filter(
category=post.category
).exclude(pk=post.pk).filter(published=True)[:3]
else:
# 列表视图上下文
context['view_mode'] = 'list'
return context
---
4.5 视图高级功能
01.Mixin模式
a.常用Mixin类
a.功能说明
Django内置Mixin类提供可复用功能,LoginRequiredMixin要求用户登录,PermissionRequiredMixin检查特定权限,UserPassesTestMixin执行自定义测试。
Mixin通过多重继承组合功能,遵循单一职责原则,提高代码复用性和可维护性。
b.代码示例
---
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin, UserPassesTestMixin
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from django.http import HttpResponseForbidden
from .models import Post
class PostCreateView(LoginRequiredMixin, CreateView):
"""创建文章视图(需要登录)"""
model = Post
template_name = 'blog/post_form.html'
fields = ['title', 'content', 'category', 'tags', 'featured_image']
success_url = reverse_lazy('post_list')
def form_valid(self, form):
"""设置作者为当前用户"""
form.instance.author = self.request.user
return super().form_valid(form)
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
"""更新文章视图(需要登录且为作者)"""
model = Post
template_name = 'blog/post_form.html'
fields = ['title', 'content', 'category', 'tags', 'featured_image']
def test_func(self):
"""测试用户是否为文章作者"""
post = self.get_object()
return post.author == self.request.user
def handle_no_permission(self):
"""处理权限不足的情况"""
return HttpResponseForbidden("您没有权限编辑此文章")
def get_success_url(self):
"""更新成功后重定向到文章详情页"""
return reverse_lazy('post_detail', kwargs={'pk': self.object.pk})
class PostDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
"""删除文章视图(需要登录和特定权限)"""
model = Post
template_name = 'blog/post_confirm_delete.html'
success_url = reverse_lazy('post_list')
permission_required = 'blog.delete_post'
def get_queryset(self):
"""只能删除自己创建的文章"""
return Post.objects.filter(author=self.request.user)
# 自定义Mixin类
class AuthorRequiredMixin:
"""要求用户为作者的Mixin"""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
if not hasattr(request.user, 'author_profile'):
return HttpResponseForbidden("您没有作者权限")
return super().dispatch(request, *args, **kwargs)
class PublishedRequiredMixin:
"""文章必须已发布的Mixin"""
def get_queryset(self):
return super().get_queryset().filter(published=True)
class SEOContextMixin:
"""SEO上下文Mixin"""
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# 添加默认SEO信息
context.update({
'meta_title': getattr(self, 'meta_title', '默认标题'),
'meta_description': getattr(self, 'meta_description', '默认描述'),
'meta_keywords': getattr(self, 'meta_keywords', ''),
})
# 如果是详情视图,添加对象的SEO信息
if hasattr(self, 'object') and self.object:
if hasattr(self.object, 'title'):
context['meta_title'] = self.object.title
if hasattr(self.object, 'content'):
content = self.object.content[:150]
context['meta_description'] = content.replace('\n', ' ').strip()
return context
class CacheMixin:
"""缓存Mixin"""
cache_timeout = 300 # 5分钟
def dispatch(self, request, *args, **kwargs):
# 如果是GET请求且用户未登录,使用缓存
if request.method == 'GET' and not request.user.is_authenticated:
cache_key = self.get_cache_key(request)
cached_response = cache.get(cache_key)
if cached_response:
return cached_response
response = super().dispatch(request, *args, **kwargs)
if request.method == 'GET' and not request.user.is_authenticated:
cache.set(cache_key, response, self.cache_timeout)
return response
def get_cache_key(self, request):
"""生成缓存键"""
return f"{self.__class__.__name__}:{request.path}:{request.GET.urlencode()}"
# 使用自定义Mixin
class PostDetailView(SEOContextMixin, PublishedRequiredMixin, DetailView):
"""文章详情视图"""
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
slug_url_kwarg = 'slug'
meta_title = '博客文章详情'
meta_description = '查看精彩的博客文章内容'
class PublicPostListView(CacheMixin, SEOContextMixin, ListView):
"""公开文章列表视图(带缓存)"""
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
cache_timeout = 600 # 10分钟
meta_title = '博客文章列表'
meta_description = '浏览最新的博客文章'
---
b.Mixin组合策略
a.功能说明
多重继承遵循MRO(方法解析顺序)从左到右查找方法,Mixin调用顺序影响功能执行流程,需避免方法名冲突和循环依赖。
合理组合Mixin需考虑功能优先级、依赖关系和super()调用链,确保各Mixin协同工作。
b.代码示例
---
from django.views.generic import View
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
class PostCRUDMixin:
"""文章CRUD操作Mixin"""
def get_success_url(self):
"""根据操作类型返回不同的URL"""
if self.request.POST.get('_save_and_continue'):
return reverse_lazy('post_edit', kwargs={'pk': self.object.pk})
elif self.request.POST.get('_save_and_new'):
return reverse_lazy('post_create')
else:
return reverse_lazy('post_list')
class PostFormValidationMixin:
"""文章表单验证Mixin"""
def form_valid(self, form):
"""表单验证成功时的处理"""
response = super().form_valid(form)
# 发送通知
if self.object.published:
send_post_published_notification(self.object)
# 记录日志
log_user_action(self.request.user, f'创建/更新文章: {self.object.title}')
return response
class PostContextMixin:
"""文章上下文Mixin"""
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# 添加常用上下文
context['categories'] = Category.objects.all()
context['tags'] = Tag.objects.all()
# 如果是编辑视图,添加额外信息
if hasattr(self, 'object') and self.object:
context['edit_mode'] = True
context['post_versions'] = PostVersion.objects.filter(
post=self.object
).order_by('-created_at')[:5]
return context
# 组合使用多个Mixin
class PostCreateView(
LoginRequiredMixin,
PermissionRequiredMixin,
PostCRUDMixin,
PostFormValidationMixin,
PostContextMixin,
CreateView
):
"""创建文章视图(组合多个Mixin)"""
model = Post
template_name = 'blog/post_form.html'
form_class = PostForm
permission_required = 'blog.add_post'
def form_invalid(self, form):
"""表单验证失败时的处理"""
messages.error(self.request, '表单验证失败,请检查输入内容')
return super().form_invalid(form)
class PostUpdateView(
LoginRequiredMixin,
UserPassesTestMixin,
PostCRUDMixin,
PostFormValidationMixin,
PostContextMixin,
UpdateView
):
"""更新文章视图"""
model = Post
template_name = 'blog/post_form.html'
form_class = PostForm
def test_func(self):
"""测试用户权限"""
return self.get_object().author == self.request.user
# 动态Mixin组合
def get_view_class_with_mixins(base_view, *mixins):
"""动态组合Mixin"""
class DynamicView(*mixins, base_view):
pass
return DynamicView
# 根据条件动态选择Mixin
def create_post_view(request):
"""根据用户权限创建不同的视图"""
if request.user.has_perm('blog.moderate_post'):
# 管理员视图,使用所有功能
return AdminPostView.as_view()
elif request.user.has_perm('blog.create_post'):
# 普通作者视图
return AuthorPostView.as_view()
else:
# 游客只读视图
return PublicPostView.as_view()
# 动态Mixin选择
class DynamicMixinView:
"""根据请求动态选择Mixin"""
def get_mixin_classes(self):
"""获取应该使用的Mixin类"""
mixins = []
if self.request.user.is_authenticated:
mixins.append(LoginRequiredMixin)
if self.request.user.is_staff:
mixins.append(StaffRequiredMixin)
if self.request.GET.get('preview'):
mixins.append(PreviewModeMixin)
return mixins
def get_view_class(self):
"""获取最终的视图类"""
base_view = self.base_view_class
mixins = self.get_mixin_classes()
return type(
f'Dynamic{base_view.__name__}',
tuple(mixins) + (base_view,),
{}
)
# 策略模式使用Mixin
class ViewStrategyMixin:
"""视图策略Mixin"""
strategy_map = {}
def get_strategy(self):
"""获取当前策略"""
strategy_key = self.get_strategy_key()
return self.strategy_map.get(strategy_key)
def get_strategy_key(self):
"""获取策略键"""
return self.request.GET.get('strategy', 'default')
def dispatch(self, request, *args, **kwargs):
strategy = self.get_strategy()
if strategy:
# 应用策略特定的Mixin
return strategy.apply(self)
return super().dispatch(request, *args, **kwargs)
---
c.自定义Mixin开发
a.功能说明
自定义Mixin封装可复用的视图逻辑,通过类属性提供配置选项,使用super()保持方法调用链完整。
Mixin应专注单一功能,提供清晰的接口和文档,支持灵活配置和扩展,便于在多个视图中复用。
b.代码示例
---
from django.core.exceptions import ImproperlyConfigured
from django.contrib import messages
from django.utils.timezone import now
import json
class AjaxResponseMixin:
"""AJAX响应Mixin"""
ajax_template_name = None
ajax_context_name = 'data'
def get_ajax_template_names(self):
"""获取AJAX模板名称"""
if self.ajax_template_name:
return [self.ajax_template_name]
# 默认使用原模板名加上_ajax后缀
template_name = self.get_template_names()[0]
name, ext = template_name.rsplit('.', 1)
return [f"{name}_ajax.{ext}"]
def render_to_response(self, context, **response_kwargs):
"""根据请求类型返回不同响应"""
if self.request.headers.get('x-requested-with') == 'XMLHttpRequest':
# AJAX请求,返回JSON或部分模板
if self.request.GET.get('format') == 'json':
data = context.get(self.ajax_context_name, context)
return JsonResponse(data)
else:
template_names = self.get_ajax_template_names()
return self.response_class(
request=self.request,
template=template_names,
context=context,
**response_kwargs
)
else:
# 普通请求,返回完整模板
return super().render_to_response(context, **response_kwargs)
class TimestampMixin:
"""时间戳Mixin"""
add_created_at = True
add_updated_at = True
timestamp_field = 'created_at'
update_timestamp_field = 'updated_at'
def form_valid(self, form):
"""表单验证时添加时间戳"""
response = super().form_valid(form)
if self.add_created_at and hasattr(form.instance, 'created_at'):
form.instance.created_at = now()
if self.add_updated_at and hasattr(form.instance, 'updated_at'):
form.instance.updated_at = now()
return response
class BulkOperationMixin:
"""批量操作Mixin"""
bulk_action_param = 'bulk_action'
bulk_ids_param = 'bulk_ids'
def post(self, request, *args, **kwargs):
"""处理POST请求,检查批量操作"""
bulk_action = request.POST.get(self.bulk_action_param)
bulk_ids = request.POST.getlist(self.bulk_ids_param)
if bulk_action and bulk_ids:
return self.handle_bulk_operation(bulk_action, bulk_ids)
return super().post(request, *args, **kwargs)
def handle_bulk_operation(self, action, ids):
"""处理批量操作"""
try:
queryset = self.get_queryset().filter(pk__in=ids)
count = queryset.count()
if action == 'publish':
queryset.update(published=True)
messages.success(
self.request, f'成功发布 {count} 篇文章'
)
elif action == 'unpublish':
queryset.update(published=False)
messages.success(
self.request, f'成功取消发布 {count} 篇文章'
)
elif action == 'delete':
queryset.delete()
messages.success(
self.request, f'成功删除 {count} 篇文章'
)
else:
messages.error(
self.request, '未知的批量操作'
)
except Exception as e:
messages.error(
self.request, f'批量操作失败: {str(e)}'
)
return self.get(self.request)
class FilterMixin:
"""过滤器Mixin"""
filter_fields = {}
filter_class = None
def get_queryset(self):
"""应用过滤器到查询集"""
queryset = super().get_queryset()
if self.filter_class:
# 使用过滤器类
filter_obj = self.filter_class(
self.request.GET, queryset=queryset
)
return filter_obj.qs
# 使用字段过滤
for field_name, filter_value in self.filter_fields.items():
request_value = self.request.GET.get(field_name)
if request_value:
filter_kwargs = {filter_value: request_value}
queryset = queryset.filter(**filter_kwargs)
return queryset
def get_context_data(self, **kwargs):
"""添加过滤上下文"""
context = super().get_context_data(**kwargs)
# 添加当前过滤参数
context['current_filters'] = {
field: self.request.GET.get(field)
for field in self.filter_fields.keys()
}
return context
# 配置化Mixin
class ConfigurableMixin:
"""可配置Mixin"""
config_options = {}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._config_cache = {}
def get_config(self, key, default=None):
"""获取配置值"""
if key not in self._config_cache:
self._config_cache[key] = self._resolve_config(key, default)
return self._config_cache[key]
def _resolve_config(self, key, default):
"""解析配置值"""
# 1. 检查类属性
if hasattr(self, key):
return getattr(self, key)
# 2. 检查配置选项
if key in self.config_options:
return self.config_options[key]
# 3. 检查设置
from django.conf import settings
settings_key = f"{self.__class__.__name__.upper()}_{key.upper()}"
return getattr(settings, settings_key, default)
# 性能监控Mixin
class PerformanceMonitorMixin:
"""性能监控Mixin"""
monitor_enabled = True
slow_query_threshold = 1000 # 毫秒
def dispatch(self, request, *args, **kwargs):
if not self.monitor_enabled:
return super().dispatch(request, *args, **kwargs)
import time
start_time = time.time()
try:
response = super().dispatch(request, *args, **kwargs)
# 记录性能数据
end_time = time.time()
duration = (end_time - start_time) * 1000 # 转换为毫秒
if duration > self.slow_query_threshold:
# 记录慢请求
logger.warning(
f"Slow request: {request.path} took {duration:.2f}ms"
)
# 添加性能头信息
response['X-Response-Time'] = f"{duration:.2f}ms"
return response
except Exception as e:
# 记录错误信息
logger.error(
f"Error in {self.__class__.__name__}: {str(e)}"
)
raise
---
02.视图装饰器与类视图集成
a.method_decorator
a.功能说明
method_decorator将函数装饰器应用到类视图方法,通过name参数指定装饰目标方法(如'dispatch'、'get'、'post')。
支持装饰单个方法或使用列表装饰多个方法,保持函数装饰器在类视图中的可用性。
b.代码示例
---
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required, user_passes_test
from django.views.decorators.cache import cache_page, never_cache
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.views.generic import View, ListView, CreateView
# 装饰单个方法
class PostView(View):
def get(self, request):
return render(request, 'post_list.html')
@method_decorator(require_POST)
@method_decorator(csrf_exempt)
def post(self, request):
# POST方法免CSRF保护
return JsonResponse({'status': 'success'})
@method_decorator(login_required)
def put(self, request):
# PUT方法需要登录
return JsonResponse({'status': 'updated'})
# 装饰所有方法
@method_decorator([login_required, cache_page(60 * 15)], name='dispatch')
class ProtectedPostView(ListView):
"""需要登录和缓存的视图"""
model = Post
template_name = 'blog/protected_post_list.html'
context_object_name = 'posts'
def get_queryset(self):
return Post.objects.filter(author=self.request.user)
# 装饰特定方法
@method_decorator(login_required, name='get')
@method_decorator(user_passes_test(lambda u: u.is_staff), name='post')
class AdminOnlyView(View):
"""不同方法需要不同权限"""
def get(self, request):
# GET请求需要登录
return render(request, 'admin/dashboard.html')
def post(self, request):
# POST请求需要管理员权限
if request.is_ajax():
return JsonResponse({'status': 'ok'})
return redirect('admin:dashboard')
# 组合使用method_decorator和Mixin
class CustomLoginRequiredMixin:
"""自定义登录Mixin,使用method_decorator"""
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
class PostCreateView(CustomLoginRequiredMixin, CreateView):
"""使用自定义登录Mixin"""
model = Post
fields = ['title', 'content']
template_name = 'blog/post_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
# 动态装饰器应用
class DynamicDecoratorsView(View):
"""动态应用装饰器"""
decorators_map = {
'GET': [login_required],
'POST': [login_required, require_POST],
'PUT': [login_required, csrf_exempt],
}
def dispatch(self, request, *args, **kwargs):
# 获取当前HTTP方法的装饰器
decorators = self.decorators_map.get(request.method.upper(), [])
# 应用装饰器
view_func = super().dispatch
for decorator in decorators:
view_func = decorator(view_func)
return view_func(request, *args, **kwargs)
# 装饰器工厂
def class_view_decorator(decorator):
"""类视图装饰器工厂"""
def _decorator(cls):
cls.dispatch = method_decorator(decorator)(cls.dispatch)
return cls
return _decorator
# 使用装饰器工厂
@class_view_decorator(login_required)
class LoginRequiredPostListView(ListView):
"""需要登录的文章列表视图"""
model = Post
template_name = 'blog/post_list.html'
@class_view_decorator(cache_page(60 * 30))
class CachedPostListView(ListView):
"""缓存的文章列表视图"""
model = Post
template_name = 'blog/post_list.html'
# 组合多个装饰器
def require_authenticated(view_func):
"""要求用户认证的装饰器"""
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
return JsonResponse({
'error': 'Authentication required',
'login_url': '/login/'
}, status=401)
return view_func(request, *args, **kwargs)
return wrapper
def rate_limit(max_requests=100, window=3600):
"""速率限制装饰器"""
def decorator(view_func):
def wrapper(request, *args, **kwargs):
# 实现速率限制逻辑
client_ip = request.META.get('REMOTE_ADDR')
cache_key = f"rate_limit_{client_ip}"
request_count = cache.get(cache_key, 0)
if request_count >= max_requests:
return JsonResponse({
'error': 'Rate limit exceeded'
}, status=429)
cache.set(cache_key, request_count + 1, window)
return view_func(request, *args, **kwargs)
return wrapper
return decorator
@class_view_decorator(require_authenticated)
@class_view_decorator(rate_limit(max_requests=1000, window=3600))
class APILoginRequiredView(View):
"""API视图,需要认证和速率限制"""
def get(self, request):
return JsonResponse({
'user': request.user.username,
'message': 'Authenticated API access'
})
---
b.装饰器链和优先级
a.功能说明
多个装饰器按从下到上的顺序执行(装饰时从上到下,执行时从下到上),装饰器链的作用域影响功能范围。
条件装饰器根据配置或运行时状态决定是否应用,提供灵活的装饰器组合策略。
b.代码示例
---
from functools import wraps
from django.utils.decorators import available_attrs
def conditional_decorator(condition, decorator):
"""条件装饰器"""
def _decorator(view_func):
if condition:
return decorator(view_func)
return view_func
return _decorator
def debug_only(decorator):
"""仅在DEBUG模式下生效的装饰器"""
from django.conf import settings
return conditional_decorator(settings.DEBUG, decorator)
def staff_only(decorator):
"""仅对管理员生效的装饰器"""
def _decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def wrapper(request, *args, **kwargs):
if request.user.is_staff:
return decorator(view_func)(request, *args, **kwargs)
return view_func(request, *args, **kwargs)
return wrapper
return _decorator
# 使用条件装饰器
@method_decorator(debug_only(cache_page(60 * 5)), name='dispatch')
class DebugCacheView(ListView):
"""仅在调试模式下缓存的视图"""
model = Post
template_name = 'blog/debug_cache.html'
@method_decorator(staff_only(login_required), name='dispatch')
class StaffOrLoginView(View):
"""管理员使用登录装饰器,普通用户直接访问"""
def get(self, request):
if request.user.is_staff:
# 管理员可以看到额外信息
context = {
'is_staff': True,
'debug_info': get_debug_info()
}
else:
context = {'is_staff': False}
return render(request, 'conditional_view.html', context)
# 装饰器优先级示例
def log_execution(decorator):
"""记录执行日志装饰器"""
def _decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def wrapper(*args, **kwargs):
logger.info(f"Starting {view_func.__name__}")
try:
result = view_func(*args, **kwargs)
logger.info(f"Completed {view_func.__name__}")
return result
except Exception as e:
logger.error(f"Error in {view_func.__name__}: {str(e)}")
raise
return wrapper
return _decorator
def measure_time(decorator):
"""测量执行时间装饰器"""
def _decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def wrapper(*args, **kwargs):
import time
start_time = time.time()
result = view_func(*args, **kwargs)
end_time = time.time()
duration = end_time - start_time
logger.info(f"{view_func.__name__} took {duration:.3f}s")
return result
return wrapper
return _decorator
# 装饰器链:先执行时间测量,再执行日志记录
@method_decorator(measure_time)
@method_decorator(log_execution)
@method_decorator(login_required)
class MonitoredPostListView(ListView):
"""带监控的视图"""
model = Post
template_name = 'blog/monitored_list.html'
# 自定义装饰器类
class DecoratorChain:
"""装饰器链类"""
def __init__(self, *decorators):
self.decorators = decorators
def apply(self, view_func):
"""应用装饰器链"""
result = view_func
for decorator in self.decorators:
result = decorator(result)
return result
# 使用装饰器链
decorators = DecoratorChain(
login_required,
cache_page(60 * 15),
measure_time,
log_execution
)
@method_decorator(decorators.apply, name='dispatch')
class ChainedDecoratorsView(ListView):
"""使用装饰器链的视图"""
model = Post
template_name = 'blog/chained_decorators.html'
---
c.自定义装饰器开发
a.功能说明
自定义装饰器封装特定的视图处理逻辑,支持参数配置和条件判断,使用wraps保持被装饰函数的元数据。
装饰器可实现API验证、限流、功能开关、权限检查等横切关注点,提高代码的模块化和可维护性。
b.代码示例
---
from django.utils.decorators import method_decorator, available_attrs
from django.http import JsonResponse, HttpResponseForbidden
from django.core.exceptions import PermissionDenied
from functools import wraps
import json
import time
def api_view(http_methods=None):
"""API视图装饰器"""
if http_methods is None:
http_methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def wrapper(request, *args, **kwargs):
# 检查HTTP方法
if request.method not in http_methods:
return JsonResponse({
'error': f'Method {request.method} not allowed'
}, status=405)
# 检查Content-Type
if request.method in ['POST', 'PUT', 'PATCH']:
content_type = request.content_type
if not content_type or 'application/json' not in content_type:
return JsonResponse({
'error': 'Content-Type must be application/json'
}, status=400)
# 执行视图
try:
result = view_func(request, *args, **kwargs)
# 如果返回的是字典,转换为JSON响应
if isinstance(result, dict):
return JsonResponse(result)
return result
except Exception as e:
return JsonResponse({
'error': str(e),
'type': type(e).__name__
}, status=500)
return wrapper
return decorator
def throttle(rate='100/h'):
"""API限流装饰器"""
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def wrapper(request, *args, **kwargs):
# 解析速率限制
if '/' in rate:
requests, period = rate.split('/')
requests = int(requests)
if period == 's':
period_seconds = 1
elif period == 'm':
period_seconds = 60
elif period == 'h':
period_seconds = 3600
elif period == 'd':
period_seconds = 86400
else:
period_seconds = 3600
else:
requests = int(rate)
period_seconds = 3600
# 获取客户端标识
client_id = (
request.META.get('HTTP_X_FORWARDED_FOR') or
request.META.get('REMOTE_ADDR') or
request.session.session_key or
'anonymous'
)
# 检查限流
from django.core.cache import cache
cache_key = f"throttle_{client_id}:{view_func.__name__}"
current_requests = cache.get(cache_key, [])
# 清理过期的请求记录
now = time.time()
current_requests = [
req_time for req_time in current_requests
if now - req_time < period_seconds
]
if len(current_requests) >= requests:
return JsonResponse({
'error': 'Rate limit exceeded',
'retry_after': period_seconds
}, status=429)
# 记录当前请求
current_requests.append(now)
cache.set(cache_key, current_requests, period_seconds)
return view_func(request, *args, **kwargs)
return wrapper
return decorator
def feature_flag(feature_name, default=False):
"""功能开关装饰器"""
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def wrapper(request, *args, **kwargs):
from django.conf import settings
# 检查功能开关
feature_flags = getattr(settings, 'FEATURE_FLAGS', {})
is_enabled = feature_flags.get(feature_name, default)
if not is_enabled:
return JsonResponse({
'error': f'Feature {feature_name} is disabled'
}, status=403)
return view_func(request, *args, **kwargs)
return wrapper
return decorator
def permission_check(permissions, any_user=False):
"""权限检查装饰器"""
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
return JsonResponse({
'error': 'Authentication required'
}, status=401)
if any_user:
# any_user=True时,已认证用户即可访问
return view_func(request, *args, **kwargs)
# 检查具体权限
if isinstance(permissions, str):
permissions = [permissions]
has_permission = any(
request.user.has_perm(perm) for perm in permissions
)
if not has_permission:
return JsonResponse({
'error': 'Permission denied',
'required_permissions': permissions
}, status=403)
return view_func(request, *args, **kwargs)
return wrapper
return decorator
def validate_schema(schema=None):
"""请求schema验证装饰器"""
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def wrapper(request, *args, **kwargs):
if request.method in ['POST', 'PUT', 'PATCH'] and schema:
try:
# 解析JSON数据
data = json.loads(request.body)
# 验证schema
from jsonschema import validate, ValidationError
validate(data, schema)
except json.JSONDecodeError:
return JsonResponse({
'error': 'Invalid JSON format'
}, status=400)
except ValidationError as e:
return JsonResponse({
'error': 'Validation error',
'details': e.message
}, status=400)
# 将验证后的数据添加到请求中
request.validated_data = data
return view_func(request, *args, **kwargs)
return wrapper
return decorator
# 使用自定义装饰器
@method_decorator(api_view(['GET', 'POST']), name='dispatch')
@method_decorator(throttle('10/m'), name='dispatch')
@method_decorator(feature_flag('new_api'), name='dispatch')
class NewAPIPostView(View):
"""新API文章视图"""
def get(self, request):
posts = Post.objects.filter(published=True)
data = {
'posts': [
{
'id': post.id,
'title': post.title,
'author': post.author.username,
'created_at': post.created_at.isoformat()
}
for post in posts
]
}
return data
@method_decorator(validate_schema({
'type': 'object',
'properties': {
'title': {'type': 'string', 'minLength': 1},
'content': {'type': 'string', 'minLength': 1},
'category': {'type': 'string'}
},
'required': ['title', 'content']
}))
def post(self, request):
data = request.validated_data
post = Post.objects.create(
title=data['title'],
content=data['content'],
author=request.user,
category_id=data.get('category')
)
return {
'id': post.id,
'message': 'Post created successfully'
}
# 装饰器工厂模式
class DecoratorFactory:
"""装饰器工厂类"""
@staticmethod
def create_auth_decorator(permissions=None, roles=None):
"""创建认证装饰器"""
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
raise PermissionDenied
if permissions:
if isinstance(permissions, str):
permissions = [permissions]
if not any(request.user.has_perm(p) for p in permissions):
raise PermissionDenied
if roles:
if isinstance(roles, str):
roles = [roles]
user_roles = request.user.groups.values_list('name', flat=True)
if not any(role in user_roles for role in roles):
raise PermissionDenied
return view_func(request, *args, **kwargs)
return wrapper
return decorator
@staticmethod
def create_cache_decorator(timeout=300, key_func=None):
"""创建缓存装饰器"""
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def wrapper(request, *args, **kwargs):
from django.core.cache import cache
if key_func:
cache_key = key_func(request, view_func, *args, **kwargs)
else:
cache_key = f"{view_func.__name__}:{request.path}:{request.GET.urlencode()}"
result = cache.get(cache_key)
if result is not None:
return result
result = view_func(request, *args, **kwargs)
cache.set(cache_key, result, timeout)
return result
return wrapper
return decorator
# 使用装饰器工厂
@method_decorator(
DecoratorFactory.create_auth_decorator(
permissions=['blog.add_post'],
roles=['author']
),
name='dispatch'
)
class AuthorOnlyPostView(View):
"""仅作者可访问的文章视图"""
def post(self, request):
# 只有拥有blog.add_post权限或author角色的用户才能访问
return JsonResponse({'message': 'Access granted'})
---
4.6 视图性能优化
01.查询优化
a.select_related和prefetch_related
a.功能说明
select_related通过JOIN减少外键和一对一关系的查询次数,prefetch_related通过额外查询优化多对多和反向关系。
使用Prefetch对象自定义预加载查询集,annotate添加聚合字段避免后续查询,有效解决N+1查询问题。
b.代码示例
---
from django.views.generic import ListView, DetailView
from django.db.models import Prefetch, Count, Q
from .models import Post, Category, Comment, Tag
class OptimizedPostListView(ListView):
"""优化的文章列表视图"""
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 20
def get_queryset(self):
"""优化查询集,减少数据库访问"""
queryset = Post.objects.filter(published=True)
# 使用select_related优化外键关系
queryset = queryset.select_related(
'author', # 一对多关系
'category' # 一对多关系
)
# 使用prefetch_related优化多对多和反向关系
queryset = queryset.prefetch_related(
'tags', # 多对多关系
Prefetch(
'comments',
queryset=Comment.objects.filter(approved=True),
to_attr='approved_comments'
)
)
# 添加注解字段,避免后续查询
queryset = queryset.annotate(
comments_count=Count('comments', filter=Q(comments__approved=True)),
likes_count=Count('likes')
)
return queryset.order_by('-created_at')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# 优化分类查询,避免重复查询
context['categories'] = Category.objects.prefetch_related(
Prefetch(
'post_set',
queryset=Post.objects.filter(published=True),
to_attr='published_posts'
)
).annotate(posts_count=Count('post', filter=Q(post__published=True)))
return context
class OptimizedPostDetailView(DetailView):
"""优化的文章详情视图"""
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
def get_queryset(self):
"""优化查询集"""
return Post.objects.select_related(
'author',
'category'
).prefetch_related(
'tags',
Prefetch(
'comments',
queryset=Comment.objects.filter(
approved=True
).select_related('user'),
to_attr='approved_comments'
),
'likes'
).filter(published=True)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
post = context['post']
# 相关文章(避免N+1查询)
context['related_posts'] = Post.objects.filter(
category=post.category,
published=True
).exclude(pk=post.pk).select_related(
'author'
)[:6]
# 作者的其他文章
context['author_posts'] = Post.objects.filter(
author=post.author,
published=True
).exclude(pk=post.pk).select_related(
'category'
)[:5]
return context
class OptimizedCategoryDetailView(DetailView):
"""优化的分类详情视图"""
model = Category
template_name = 'blog/category_detail.html'
context_object_name = 'category'
def get_queryset(self):
return Category.objects.prefetch_related(
Prefetch(
'post_set',
queryset=Post.objects.filter(
published=True
).select_related('author', 'category').prefetch_related('tags'),
to_attr='published_posts'
)
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
category = context['category']
# 直接使用预加载的文章,无需额外查询
published_posts = category.published_posts
# 分页处理
from django.core.paginator import Paginator
paginator = Paginator(published_posts, 12)
page = self.request.GET.get('page', 1)
posts_page = paginator.get_page(page)
context.update({
'posts_page': posts_page,
'paginator': paginator,
'posts_count': len(published_posts)
})
return context
---
b.数据库连接和事务优化
a.功能说明
使用transaction.atomic()确保数据一致性,批量操作使用update()、bulk_create()减少SQL执行次数。
合理配置数据库连接池参数,使用using()指定数据库,监控查询数量和执行时间,优化慢查询和高频操作。
b.代码示例
---
from django.db import transaction, connection
from django.views.generic import View
from django.http import JsonResponse
from .models import Post, Comment, Tag
class OptimizedBulkOperationView(View):
"""优化的批量操作视图"""
def post(self, request):
action = request.POST.get('action')
post_ids = request.POST.getlist('post_ids')
if not post_ids:
return JsonResponse({'error': 'No posts selected'}, status=400)
try:
with transaction.atomic(): # 使用事务
if action == 'publish':
# 批量更新,减少SQL查询次数
Post.objects.filter(pk__in=post_ids).update(
published=True,
published_at=timezone.now()
)
elif action == 'delete':
# 先删除相关评论,再删除文章
Comment.objects.filter(post_id__in=post_ids).delete()
Post.objects.filter(pk__in=post_ids).delete()
elif action == 'add_tags':
tag_names = request.POST.getlist('tags')
tags = Tag.objects.filter(name__in=tag_names)
# 批量添加标签关系
for post in Post.objects.filter(pk__in=post_ids):
post.tags.add(*tags)
return JsonResponse({'success': True, 'count': len(post_ids)})
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
def get(self, request):
"""显示批量操作页面"""
# 使用select_related优化查询
posts = Post.objects.select_related('author', 'category')
# 添加统计信息,避免后续查询
posts = posts.annotate(
comments_count=Count('comments', filter=Q(comments__approved=True)),
likes_count=Count('likes')
)
return render(request, 'blog/bulk_operations.html', {
'posts': posts
})
class OptimizedImportView(View):
"""优化的数据导入视图"""
def post(self, request):
try:
# 解析上传的文件
import_file = request.FILES['import_file']
import_data = json.loads(import_file.read().decode('utf-8'))
with transaction.atomic():
# 批量创建,减少数据库访问
posts_to_create = []
comments_to_create = []
for post_data in import_data:
# 创建文章对象(不立即保存)
post = Post(
title=post_data['title'],
content=post_data['content'],
author_id=post_data['author_id'],
category_id=post_data['category_id'],
published=post_data.get('published', False)
)
posts_to_create.append(post)
# 批量创建文章
created_posts = Post.objects.bulk_create(posts_to_create, batch_size=100)
# 为创建的文章批量添加评论
for post, post_data in zip(created_posts, import_data):
if 'comments' in post_data:
for comment_data in post_data['comments']:
comments_to_create.append(
Comment(
post=post,
content=comment_data['content'],
user_id=comment_data['user_id'],
approved=comment_data.get('approved', True)
)
)
# 批量创建评论
Comment.objects.bulk_create(comments_to_create, batch_size=200)
return JsonResponse({
'success': True,
'posts_created': len(created_posts),
'comments_created': len(comments_to_create)
})
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
def get(self, request):
return render(request, 'blog/import_data.html')
# 数据库连接优化配置
class DatabaseOptimizedMixin:
"""数据库优化Mixin"""
using = None # 指定使用的数据库
def get_queryset(self):
"""使用指定的数据库"""
queryset = super().get_queryset()
if self.using:
queryset = queryset.using(self.using)
return queryset
def form_valid(self, form):
"""保存时使用指定数据库"""
if self.using:
form.instance.save(using=self.using)
return HttpResponseRedirect(self.get_success_url())
return super().form_valid(form)
# 监控数据库查询
class DatabaseQueryMonitorMixin:
"""数据库查询监控Mixin"""
debug_queries = False
def dispatch(self, request, *args, **kwargs):
if self.debug_queries:
from django.conf import settings
if settings.DEBUG:
from django.db import connection
initial_queries = len(connection.queries)
response = super().dispatch(request, *args, **kwargs)
if self.debug_queries and settings.DEBUG:
final_queries = len(connection.queries)
query_count = final_queries - initial_queries
if query_count > 50: # 查询次数过多时警告
import logging
logger = logging.getLogger(__name__)
logger.warning(
f"High query count in {self.__class__.__name__}: {query_count} queries"
)
# 添加响应头信息
response['X-DB-Query-Count'] = str(query_count)
return response
---
c.缓存策略
a.功能说明
视图级缓存使用cache_page装饰器缓存整个响应,片段缓存通过cache模板标签缓存部分内容,查询缓存存储查询结果减少数据库访问。
根据内容更新频率、用户状态、请求参数设计缓存键和过期时间,平衡性能和数据新鲜度。
b.代码示例
---
from django.views.decorators.cache import cache_page, never_cache
from django.core.cache import cache
from django.utils.decorators import method_decorator
from django.views.generic import ListView, DetailView
import hashlib
class SmartCachingMixin:
"""智能缓存Mixin"""
cache_timeout = 300 # 5分钟
cache_key_prefix = ''
def get_cache_key(self, request):
"""生成缓存键"""
# 包含URL、用户信息、查询参数
key_data = {
'url': request.get_full_path(),
'user_id': request.user.id if request.user.is_authenticated else None,
'get_params': dict(request.GET),
'lang': getattr(request, 'LANGUAGE_CODE', 'en')
}
key_string = json.dumps(key_data, sort_keys=True)
key_hash = hashlib.md5(key_string.encode()).hexdigest()
return f"{self.cache_key_prefix}{self.__class__.__name__}:{key_hash}"
def should_cache(self, request, response):
"""判断是否应该缓存"""
# 不缓存错误响应
if response.status_code >= 400:
return False
# 不缓存包含敏感信息的响应
if 'user' in str(response.content) and request.user.is_authenticated:
return False
# 不缓存POST/PUT/DELETE请求的响应
if request.method not in ['GET', 'HEAD']:
return False
return True
def dispatch(self, request, *args, **kwargs):
cache_key = self.get_cache_key(request)
# 尝试从缓存获取
cached_response = cache.get(cache_key)
if cached_response:
return cached_response
# 执行视图
response = super().dispatch(request, *args, **kwargs)
# 缓存响应
if self.should_cache(request, response):
cache.set(cache_key, response, self.cache_timeout)
return response
class CachedPostListView(SmartCachingMixin, ListView):
"""缓存的文章列表视图"""
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
cache_key_prefix = 'post_list:'
cache_timeout = 600 # 10分钟
def get_queryset(self):
"""获取查询集并应用查询缓存"""
cache_key = f"post_list_queryset:{self.request.GET.urlencode()}"
queryset = cache.get(cache_key)
if queryset is None:
queryset = Post.objects.filter(published=True).select_related(
'author', 'category'
).prefetch_related('tags').order_by('-created_at')
# 缓存查询集
cache.set(cache_key, queryset, 300) # 5分钟
return queryset
@method_decorator(cache_page(60 * 15), name='dispatch')
class HomePageView(ListView):
"""首页视图(固定缓存)"""
model = Post
template_name = 'blog/home.html'
context_object_name = 'featured_posts'
def get_queryset(self):
return Post.objects.filter(
featured=True,
published=True
).select_related('author')[:10
class ConditionalCacheMixin:
"""条件缓存Mixin"""
cache_conditions = {}
def get_cache_timeout(self, request):
"""根据条件获取缓存时间"""
for condition, timeout in self.cache_conditions.items():
if condition(request):
return timeout
return 300 # 默认5分钟
def dispatch(self, request, *args, **kwargs):
# 根据条件决定缓存策略
cache_timeout = self.get_cache_timeout(request)
cache_key = f"conditional_cache:{request.path}"
if cache_timeout > 0:
cached_response = cache.get(cache_key)
if cached_response:
return cached_response
response = super().dispatch(request, *args, **kwargs)
if cache_timeout > 0:
cache.set(cache_key, response, cache_timeout)
return response
class DynamicCachedPostView(ConditionalCacheMixin, DetailView):
"""动态缓存文章视图"""
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
cache_conditions = {
# 文章浏览次数小于1000时缓存15分钟
lambda request: getattr(request, 'post', None) and getattr(request.post, 'views_count', 0) < 1000: 900,
# 文章浏览次数大于1000时缓存5分钟
lambda request: getattr(request, 'post', None) and getattr(request.post, 'views_count', 0) >= 1000: 300,
# 匿名用户请求缓存10分钟
lambda request: not request.user.is_authenticated: 600,
# 已登录用户不缓存
lambda request: request.user.is_authenticated: 0,
}
def get_object(self, queryset=None):
post = super().get_object(queryset)
# 将文章对象添加到request中供条件判断使用
self.request.post = post
return post
# 标签片段缓存
from django.core.cache.utils import make_template_fragment_key
class FragmentCachedMixin:
"""片段缓存Mixin"""
fragment_cache_timeout = 300
def render_fragment(self, fragment_name, context):
"""渲染缓存片段"""
cache_key = make_template_fragment_key(fragment_name, context.values())
cached_content = cache.get(cache_key)
if cached_content:
return cached_content
# 渲染模板片段
template = get_template(f"blog/fragments/{fragment_name}.html")
content = template.render(context)
cache.set(cache_key, content, self.fragment_cache_timeout)
return content
class CachedPostDetailView(FragmentCachedMixin, DetailView):
"""使用片段缓存的文章详情视图"""
model = Post
template_name = 'blog/post_detail_cached.html'
context_object_name = 'post'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
post = context['post']
# 缓存相关文章片段
context['related_posts_fragment'] = self.render_fragment(
'related_posts',
{'posts': post.get_related_posts()}
)
# 缓存评论片段
context['comments_fragment'] = self.render_fragment(
'comments',
{'comments': post.approved_comments.all()}
)
return context
---
5 模板与静态文件
5.1 模板引擎
01.DTL基础语法
a.变量显示
a.功能说明
Django模板变量使用双大括号语法显示数据,支持对象属性访问、字典键访问和列表索引访问,可使用过滤器格式化输出和设置默认值。
b.代码示例
---
<!-- 基础变量显示 -->
<h1>{{ post.title }}</h1>
<p>作者:{{ post.author.username }}</p>
<p>发布时间:{{ post.created_at|date:"Y-m-d H:i" }}</p>
<!-- 对象和字典访问 -->
<div class="post-category">
分类:{{ post.category.name }}
</div>
<!-- 列表访问 -->
<ul>
<li>第一个标签:{{ post.tags.0.name }}</li>
<li>标签数量:{{ post.tags|length }}</li>
---
b.模板标签
a.功能说明
模板标签使用大括号百分号语法实现逻辑控制,包括if条件判断、for循环遍历、with变量赋值等,支持嵌套使用和复杂逻辑处理。
b.代码示例
---
<!-- 条件标签 -->
{% if post.published %}
<div class="published-post">
<h2>{{ post.title }}</h2>
<p>状态:已发布</p>
</div>
{% elif post.draft %}
<div class="draft-post">
<h2>{{ post.title }}</h2>
<p>状态:草稿</p>
</div>
{% else %}
---
{% block content %}
<div class="post-detail">
<!-- 使用父级块内容 -->
{{ block.super }}
<!-- 添加新内容 -->
<h1>{{ post.title }}</h1>
<div class="post-content">
{{ post.content|safe }}
</div>
</div>
{% endblock %}
{% block extra_css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'css/post_detail.css' %}">
{% endblock %}
{% block extra_js %}
{{ block.super }}
<script src="{% static 'js/post_detail.js' %}"></script>
{% endblock %}
---
c.内置过滤器
a.功能说明
内置过滤器用于格式化和转换变量输出,包括字符串处理、日期格式化、数值计算、列表操作等,可链式调用多个过滤器实现复杂转换。
b.代码示例
---
<!-- 字符串过滤器 -->
<h2>{{ post.title|title }}</h2>
<p>{{ post.content|truncatewords:50 }}</p>
<p>{{ post.content|truncatechars:200 }}</p>
<p>{{ user.email|lower }}</p>
<p>{{ post.category.name|upper }}</p>
<!-- 字符串格式化 -->
<p>{{ post.content|wordcount }} 个单词</p>
<p>{{ post.content|linebreaksbr }}</p>
<p>{{ post.content|linebreaks }}</p>
<p>{{ post.content|striptags }}</p>
<!-- 链接和URL处理 -->
---
02.模板继承系统
a.基础模板设计
a.功能说明
基础模板定义页面整体结构和公共部分,使用block标签定义可覆盖区域,子模板通过extends继承并重写block内容,实现模板复用和统一布局。
b.代码示例
---
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% block meta %}
<meta name="description" content="{{ meta_description|default:'Django博客网站' }}">
<meta name="keywords" content="{{ meta_keywords|default:'Django, 博客, Python' }}">
<meta name="author" content="{{ meta_author|default:'作者' }}">
{% endblock %}
<title>{% block title %}{{ site_name|default:'Django Blog' }}{% endblock %}</title>
{% endblock %}
</div></div>
</nav>
</header>
{% endblock %}
{% block messages %}
<div class="container mt-3">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert">
<span>×</span>
</button>
</div>
{% endfor %}
{% endif %}
</div>
{% endblock %}
{% block breadcrumbs %}
<div class="container mt-3">
{% block breadcrumb %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="{% url 'home' %}">首页</a>
</li>
{% block breadcrumb_items %}
{% endblock %}
</ol>
</nav>
{% endblock %}
</div>
{% endblock %}
{% block content %}
<main class="container my-4">
<div class="row">
{% block main_content %}
<div class="col-lg-8">
<!-- 主要内容区域 -->
</div>
{% endblock %}
{% block sidebar %}
<div class="col-lg-4">
<!-- 侧边栏区域 -->
</div>
{% endblock %}
</div>
</main>
{% endblock %}
{% block footer %}
<footer class="site-footer bg-light mt-5">
<div class="container">
<div class="row">
<div class="col-md-4">
<h5>关于我们</h5>
<p>{{ footer_about|default:'这是一个基于Django的博客网站' }}</p>
</div>
<div class="col-md-4">
<h5>快速链接</h5>
<ul class="list-unstyled">
<li><a href="{% url 'blog:post_list' %}">所有文章</a></li>
<li><a href="{% url 'categories' %}">分类目录</a></li>
<li><a href="{% url 'tags' %}">标签云</a></li>
</ul>
</div>
<div class="col-md-4">
<h5>联系方式</h5>
<p>{{ footer_contact|default:'联系邮箱:[email protected]' }}</p>
</div>
</div>
<hr>
<div class="text-center py-3">
<p>© {% now "Y" %} {{ site_name|default:'Django Blog' }}.
保留所有权利 |
<a href="{% url 'privacy' %}">隐私政策</a> |
<a href="{% url 'terms' %}">使用条款</a>
</p>
</div>
</div>
</footer>
{% endblock %}
{% block js %}
<!-- 基础JavaScript -->
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
{% endblock %}
{% block extra_js %}
<!-- 额外的JavaScript块,供子模板重写 -->
{% endblock %}
{% block analytics %}
<!-- 网站统计代码 -->
{% if google_analytics_id %}
<script async src="https://www.googletagmanager.com/gtag/js?id={{ google_analytics_id }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ google_analytics_id }}');
</script>
{% endif %}
{% endblock %}
</body>
</html>
---
b.子模板继承
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
<!-- templates/blog/post_list.html -->
{% extends 'base.html' %}
{% block title %}
文章列表 - {{ block.super }}
{% endblock %}
{% block meta_description %}
{% endblock %}
{% block meta_keywords %}
{% endblock %}
{% block breadcrumb_items %}
{{ block.super }}
<li class="breadcrumb-item active">文章列表</li>
{{ post.category.name }}
</a>
</div>
{% endif %}
{% if post.tags %}
<div class="mb-3">
{% for tag in post.tags %}
<a href="{% url 'blog:tag_posts' tag.slug %}"
class="badge badge-light">#{{ tag.name }}</a>
{% endfor %}
</div>
{% endif %}
<p class="card-text">{{ post.content|striptags|truncatewords:30 }}</p>
<div class="d-flex justify-content-between align-items-center">
<a href="{% url 'blog:post_detail' post.slug %}" class="btn btn-primary">
阅读全文
</a>
{% if user == post.author or user.is_staff %}
<div>
<a href="{% url 'blog:post_edit' post.id %}" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-edit"></i> 编辑
</a>
<a href="{% url 'blog:post_delete' post.id %}" class="btn btn-sm btn-outline-danger">
<i class="fas fa-trash"></i> 删除
</a>
</div>
{% endif %}
</div>
</div>
</article>
{% endfor %}
<!-- 分页 -->
{% include 'common/pagination.html' with page_obj=posts_page page_range=paginator.page_range %}
{% else %}
<div class="text-center py-5">
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
<h4>暂无文章</h4>
<p class="text-muted">当前没有符合条件的文章</p>
{% if user.is_authenticated %}
<a href="{% url 'blog:post_create' %}" class="btn btn-primary">
写第一篇文章
</a>
{% endif %}
</div>
{% endif %}
</div>
{% endblock %}
{% block sidebar %}
<div class="col-lg-4">
{% include 'blog/sidebar/search.html' %}
{% include 'blog/sidebar/categories.html' %}
{% include 'blog/sidebar/recent_posts.html' %}
{% include 'blog/sidebar/tags.html' %}
{% include 'blog/sidebar/archive.html' %}
</div>
{% endblock %}
{% block extra_css %}
{{ block.super }}
<style>
.post-meta i {
width: 16px;
text-align: center;
}
.badge {
font-size: 0.75em;
}
</style>
{% endblock %}
{% block extra_js %}
{{ block.super }}
<script>
// 自动提交搜索表单(选择分类或排序时)
$(document).ready(function() {
$('select[name="category"], select[name="sort"]').change(function() {
$(this).closest('form').submit();
});
});
</script>
{% endblock %}
---
c.多层继承和包含
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
<!-- templates/blog/base_blog.html -->
{% extends 'base.html' %}
{% block title %}
{% block blog_title %}博客{% endblock %} - {{ block.super }}
{% endblock %}
{% block breadcrumb_items %}
{{ block.super }}
<li class="breadcrumb-item">
<a href="{% url 'blog:post_list' %}">博客</a>
</li>
{% block blog_breadcrumb_items %}{% endblock %}
{% endblock %}
</div>
{% endif %}
<div class="post-content">
{{ post.content|safe }}
</div>
<footer class="post-footer mt-4 pt-4 border-top">
<!-- 文章操作按钮 -->
<div class="post-actions mb-3">
{% if user.is_authenticated %}
<button class="btn btn-outline-primary btn-sm like-btn"
data-post-id="{{ post.id }}"
{% if user in post.likes.all %}disabled{% endif %}>
<i class="fas fa-heart"></i>
{{ post.likes.count }} 点赞
</button>
<button class="btn btn-outline-secondary btn-sm bookmark-btn"
data-post-id="{{ post.id }}"
{% if user in post.bookmarks.all %}disabled{% endif %}>
<i class="fas fa-bookmark"></i>
收藏
</button>
{% endif %}
<div class="float-right">
<button class="btn btn-outline-info btn-sm share-btn">
<i class="fas fa-share"></i> 分享
</button>
</div>
</div>
<!-- 作者信息 -->
<div class="author-info card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="author-avatar mr-3">
{% if post.author.profile.avatar %}
<img src="{{ post.author.profile.avatar.url }}"
alt="{{ post.author.username }}"
class="rounded-circle" width="60" height="60">
{% else %}
<div class="rounded-circle bg-primary text-white d-flex align-items-center justify-content-center"
style="width: 60px; height: 60px;">
{{ post.author.username.0|upper }}
</div>
{% endif %}
</div>
<div class="author-details">
<h5 class="mb-1">
<a href="{% url 'users:profile' post.author.id %}">
{{ post.author.username }}
</a>
</h5>
<p class="text-muted mb-0">
发布了 {{ post.author.posts.count }} 篇文章
</p>
{% if post.author.profile.bio %}
<p class="text-muted small mb-0">{{ post.author.profile.bio }}</p>
{% endif %}
</div>
</div>
</div>
</div>
<!-- 相关文章 -->
{% if related_posts %}
<div class="related-posts mt-4">
<h4>相关文章</h4>
<div class="row">
{% for related_post in related_posts %}
<div class="col-md-6 mb-3">
<div class="card h-100">
{% if related_post.featured_image %}
<img src="{{ related_post.featured_image.url }}"
class="card-img-top"
alt="{{ related_post.title }}"
style="height: 150px; object-fit: cover;">
{% endif %}
<div class="card-body d-flex flex-column">
<h6 class="card-title">
<a href="{% url 'blog:post_detail' related_post.slug %}"
class="text-decoration-none">
{{ related_post.title|truncatechars:50 }}
</a>
</h6>
<p class="card-text text-muted small">
{{ related_post.created_at|date:"Y年m月d日" }}
</p>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</footer>
</article>
<!-- 评论区 -->
{% include 'blog/comments/section.html' %}
{% endblock %}
{% block blog_sidebar %}
{{ block.super }}
<!-- 文章目录 -->
{% include 'blog/sidebar/table_of_contents.html' %}
{% endblock %}
{% block extra_css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'css/pygments.css' %}">
<style>
.post-content {
line-height: 1.8;
font-size: 16px;
}
.post-content h1,
.post-content h2,
.post-content h3,
.post-content h4,
.post-content h5,
.post-content h6 {
margin-top: 2rem;
margin-bottom: 1rem;
}
.post-content img {
max-width: 100%;
height: auto;
border-radius: 8px;
margin: 1rem 0;
}
.post-content pre {
background-color: #f8f9fa;
border-radius: 4px;
padding: 1rem;
overflow-x: auto;
}
.author-info .card {
background-color: #f8f9fa;
}
</style>
{% endblock %}
{% block extra_js %}
{{ block.super }}
<script src="{% static 'js/post-detail.js' %}"></script>
{% endblock %}
---
03.自定义模板标签和过滤器
a.简单过滤器
a.功能说明
自定义过滤器通过register.filter装饰器注册函数,接收变量值和可选参数,返回处理后的结果,需在templatetags目录下创建模块并在模板中load加载。
b.代码示例
---
if len(value) <= length:
text = re.sub(r'<[^>]+>', '', value) # 移除HTML标签
if len(text) <= length:
highlighted = pattern.sub(f'<mark>{term}</mark>', text)
return mark_safe(highlighted)
@register.filter
def user_avatar_url(user, size=50):
"""
获取用户头像URL
"""
if hasattr(user, 'profile') and user.profile.avatar:
return user.profile.avatar.url
else:
# 生成默认头像
initials = user.username[:2].upper()
return f"https://ui-avatars.com/api/?name={initials}&size={size}&background=007bff&color=ffffff"
@register.filter
def add_css_class(field, css_class):
"""
为表单字段添加CSS类
"""
return field.as_widget(attrs={"class": css_class})
@register.filter
def star_rating(rating):
"""
将数字评分转换为星级显示
"""
full_stars = int(rating)
half_star = 1 if rating % 1 >= 0.5 else 0
empty_stars = 5 - full_stars - half_star
stars_html = ''
stars_html += '<i class="fas fa-star text-warning"></i>' * full_stars
if half_star:
stars_html += '<i class="fas fa-star-half-alt text-warning"></i>'
stars_html += '<i class="far fa-star text-warning"></i>' * empty_stars
return mark_safe(f'<div class="star-rating">{stars_html}</div>')
@register.filter
def currency_format(amount, currency='CNY'):
"""
货币格式化
"""
currency_symbols = {
'CNY': '¥',
'USD': '$',
'EUR': '€',
'GBP': '£',
'JPY': '¥'
}
symbol = currency_symbols.get(currency, currency)
if isinstance(amount, (int, float)):
return f"{symbol}{amount:,.2f}"
return f"{symbol}{amount}"
@register.filter
def privacy_mask(value, mask_char='*'):
"""
隐私信息遮罩
"""
if not value:
return value
value_str = str(value)
if len(value_str) <= 3:
return mask_char * len(value_str)
start = value_str[:2]
middle = mask_char * (len(value_str) - 4)
end = value_str[-2:]
return f"{start}{middle}{end}"
@register.filter
def social_link(platform, username):
"""
生成社交媒体链接
"""
platforms = {
'twitter': 'https://twitter.com/',
'github': 'https://github.com/',
'linkedin': 'https://linkedin.com/in/',
'instagram': 'https://instagram.com/',
'facebook': 'https://facebook.com/',
'youtube': 'https://youtube.com/user/'
}
base_url = platforms.get(platform.lower())
if base_url:
return mark_safe(f'<a href="{base_url}{username}" target="_blank" rel="noopener">{platform.title()}</a>')
return username
@register.filter
def progress_bar(percentage, label=''):
"""
生成进度条HTML
"""
try:
percent = float(percentage)
if percent < 0:
percent = 0
elif percent > 100:
percent = 100
except (ValueError, TypeError):
percent = 0
# 根据百分比选择颜色
if percent < 30:
color_class = 'bg-danger'
elif percent < 70:
color_class = 'bg-warning'
else:
color_class = 'bg-success'
html = f'''
<div class="progress">
<div class="progress-bar {color_class}" role="progressbar"
style="width: {percent}%" aria-valuenow="{percent}"
aria-valuemin="0" aria-valuemax="100">
{label} {percent}%
</div>
</div>
'''
return mark_safe(html)
@register.filter
def reading_time(content):
"""
估算文章阅读时间
"""
if not content:
return '0分钟'
# 移除HTML标签
text = re.sub(r'<[^>]+>', '', str(content))
word_count = len(text.split())
# 假设每分钟阅读200个单词
minutes = word_count / 200
if minutes < 1:
return '不到1分钟'
elif minutes < 60:
return f'{int(minutes)}分钟'
else:
hours = int(minutes / 60)
remaining_minutes = int(minutes % 60)
if remaining_minutes == 0:
return f'{hours}小时'
else:
return f'{hours}小时{remaining_minutes}分钟'
@register.filter
def email_obfuscate(email):
"""
邮箱地址混淆,防止垃圾邮件
"""
if not email:
return ''
# HTML实体编码
encoded = ''
for char in email:
encoded += f'&#{ord(char)};'
return mark_safe(encoded)
@register.filter
def phone_format(phone):
"""
电话号码格式化
"""
# 移除所有非数字字符
digits = re.sub(r'\D', '', str(phone))
if len(digits) == 11: # 中国手机号
return f'{digits[:3]}-{digits[3:7]}-{digits[7:]}'
elif len(digits) == 10: # 美国电话号
return f'({digits[:3]}) {digits[3:6]}-{digits[6:]}'
else:
return phone
---
b.简单标签
a.功能说明
自定义标签通过register.simple_tag或inclusion_tag创建,支持接收参数和上下文,可返回字符串或渲染模板,实现复杂的模板逻辑封装。
b.代码示例
---
# blog/templatetags/blog_tags.py
from django import template
from django.utils.safestring import mark_safe
from django.urls import reverse
from django.db.models import Count, Q
from ..models import Post, Category, Tag
import random
register = template.Library()
@register.simple_tag
def latest_posts(count=5):
获取最新文章
posts = Post.objects.filter(published=True).order_by('-created_at')[:count]
return posts
---
def get_related_posts(post, count=3):
"""
获取相关文章(基于分类和标签)
"""
related_posts = Post.objects.filter(
published=True
).filter(
Q(category=post.category) |
Q(tags__in=post.tags.all())
).exclude(pk=post.pk).distinct()
return related_posts[:count]
@register.simple_tag(takes_context=True)
def social_share_links(context, title, url):
"""
生成社交媒体分享链接
"""
request = context['request']
full_url = request.build_absolute_uri(url) if url.startswith('/') else url
links = {
'weibo': f'http://service.weibo.com/share/share.php?title={title}&url={full_url}',
'qq': f'https://connect.qq.com/widget/shareqq/index.html?title={title}&url={full_url}',
'wechat': f'#', # 微信分享需要使用特殊实现
'twitter': f'https://twitter.com/intent/tweet?text={title}&url={full_url}',
'facebook': f'https://www.facebook.com/sharer/sharer.php?u={full_url}',
'linkedin': f'https://www.linkedin.com/sharing/share-offsite/?url={full_url}'
}
return links
@register.simple_tag
def get_navigation_menu():
"""
获取导航菜单
"""
menu_items = [
{'title': '首页', 'url': reverse('home'), 'icon': 'fas fa-home'},
{'title': '博客', 'url': reverse('blog:post_list'), 'icon': 'fas fa-blog'},
{'title': '分类', 'url': reverse('blog:categories'), 'icon': 'fas fa-folder'},
{'title': '标签', 'url': reverse('blog:tags'), 'icon': 'fas fa-tags'},
{'title': '归档', 'url': reverse('blog:archive'), 'icon': 'fas fa-calendar'},
{'title': '关于', 'url': reverse('about'), 'icon': 'fas fa-info-circle'},
{'title': '联系', 'url': reverse('contact'), 'icon': 'fas fa-envelope'},
]
return menu_items
@register.simple_tag
def get_breadcrumb_items(current_page=None, **kwargs):
"""
生成面包屑导航
"""
items = [
{'title': '首页', 'url': reverse('home'), 'active': False}
]
if current_page == 'post_list':
items.append({'title': '博客', 'url': reverse('blog:post_list'), 'active': True})
elif current_page == 'post_detail' and 'post' in kwargs:
post = kwargs['post']
items.append({'title': '博客', 'url': reverse('blog:post_list'), 'active': False})
if post.category:
items.append({
'title': post.category.name,
'url': reverse('blog:category_posts', args=[post.category.slug]),
'active': False
})
items.append({'title': post.title, 'url': None, 'active': True})
elif current_page == 'category_posts' and 'category' in kwargs:
category = kwargs['category']
items.append({'title': '博客', 'url': reverse('blog:post_list'), 'active': False})
items.append({'title': category.name, 'url': None, 'active': True})
return items
@register.simple_tag
def generate_qr_code(url, size=150):
"""
生成二维码(需要安装qrcode库)
"""
try:
import qrcode
from io import BytesIO
import base64
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buffer = BytesIO()
img.save(buffer, format='PNG')
img_str = base64.b64encode(buffer.getvalue()).decode()
return mark_safe(f'<img src="data:image/png;base64,{img_str}" alt="QR Code" width="{size}" height="{size}">')
except ImportError:
return mark_safe(f'<a href="{url}">{url}</a>')
@register.simple_tag(takes_context=True)
def get_user_notifications(context):
"""
获取用户通知
"""
user = context['request'].user
if not user.is_authenticated:
return []
# 假设有Notification模型
from ..models import Notification
notifications = Notification.objects.filter(
user=user,
read=False
).order_by('-created_at')[:5]
return notifications
@register.simple_tag
def get_site_statistics():
"""
获取网站统计信息
"""
stats = {
'total_posts': Post.objects.filter(published=True).count(),
'total_categories': Category.objects.count(),
'total_tags': Tag.objects.count(),
'total_views': Post.objects.aggregate(
total_views=Count('views_count')
)['total_views'] or 0,
}
return stats
@register.simple_tag
def generate_table_of_contents(content):
"""
生成文章目录
"""
import re
from django.utils.html import escape
# 提取标题
headings = re.findall(r'<h([1-6])[^>]*>(.*?)</h[1-6]>', content)
toc_items = []
for level, title in headings:
# 清理标题文本
clean_title = re.sub(r'<[^>]+>', '', title)
# 生成锚点ID
anchor_id = re.sub(r'[^a-zA-Z0-9\u4e00-\u9fff]+', '-', clean_title.lower()).strip('-')
toc_items.append({
'level': int(level),
'title': clean_title,
'anchor_id': anchor_id
})
return toc_items
@register.simple_tag
def generate_rating_stars(rating, max_rating=5):
"""
生成评分星星HTML
"""
try:
rating = float(rating)
max_rating = int(max_rating)
except (ValueError, TypeError):
return ''
stars_html = ''
full_stars = int(rating)
has_half_star = (rating - full_stars) >= 0.5
empty_stars = max_rating - full_stars - (1 if has_half_star else 0)
# 实心星星
stars_html += '<i class="fas fa-star text-warning"></i>' * full_stars
# 半星
if has_half_star:
stars_html += '<i class="fas fa-star-half-alt text-warning"></i>'
# 空心星星
stars_html += '<i class="far fa-star text-warning"></i>' * empty_stars
return mark_safe(f'<div class="rating-stars" title="{rating}/{max_rating}">{stars_html}</div>')
@register.simple_tag
def get_weather_info(city):
"""
获取天气信息(示例实现)
"""
# 这里应该调用天气API
# 简化的实现,返回模拟数据
weather_data = {
'temperature': random.randint(15, 30),
'condition': random.choice(['晴', '多云', '阴', '小雨']),
'humidity': random.randint(40, 80),
'wind_speed': random.randint(5, 20)
}
return weather_data
---
5.2 模板上下文处理器
01.内置上下文处理器
a.request处理器
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
<!-- 在settings.py中启用 -->
<!-- 在模板中使用request对象 -->
<!DOCTYPE html>
<html lang="{{ request.LANGUAGE_CODE|default:'zh-CN' }}">
<head>
<meta charset="UTF-8">
<title>{% block title %}{{ site_name }}{% endblock %}</title>
<!-- 获取当前URL和查询参数 -->
{% block meta %}
<meta name="description" content="{{ request.get_full_path }}">
<link rel="canonical" href="{{ request.build_absolute_uri }}">
{% endblock %}
---
b.auth处理器
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
<!-- settings.py中启用auth上下文处理器 -->
<!-- 在模板中使用认证上下文 -->
<nav class="user-navigation">
{% if user.is_authenticated %}
<div class="user-profile">
<!-- 用户头像 -->
<div class="user-avatar">
{% if user.profile.avatar %}
<img src="{{ user.profile.avatar.url }}" alt="{{ user.username }}">
{% else %}
<div class="default-avatar">
{{ user.username|first|upper }}
查看详情
</a>
{% if perms.blog.change_post %}
<a href="{% url 'blog:post_edit' post.id %}" class="btn btn-secondary">
编辑
</a>
{% endif %}
{% if perms.blog.delete_post %}
<a href="{% url 'blog:post_delete' post.id %}" class="btn btn-danger">
删除
</a>
{% endif %}
<!-- 检查特定对象权限 -->
{% if perms.blog.can_moderate_post %}
{% if post.published %}
<button class="btn btn-warning" onclick="unpublishPost({{ post.id }})">
下架文章
</button>
{% else %}
<button class="btn btn-success" onclick="publishPost({{ post.id }})">
发布文章
</button>
{% endif %}
{% endif %}
</div>
</article>
{% endfor %}
{% else %}
<div class="alert alert-warning">
<h4>权限不足</h4>
<p>您没有查看文章的权限。请联系管理员获取相应权限。</p>
</div>
{% endif %}
</div>
<!-- 消息显示 -->
{% if messages %}
<div class="messages-container">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
<!-- 消息级别图标 -->
{% if message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}
<i class="fas fa-check-circle"></i>
{% elif message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}
<i class="fas fa-exclamation-circle"></i>
{% elif message.level == DEFAULT_MESSAGE_LEVELS.WARNING %}
<i class="fas fa-exclamation-triangle"></i>
{% elif message.level == DEFAULT_MESSAGE_LEVELS.INFO %}
<i class="fas fa-info-circle"></i>
{% endif %}
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
{% endfor %}
</div>
{% endif %}
<!-- 基于用户组显示不同内容 -->
<aside class="sidebar">
{% if user.is_authenticated %}
{% if 'authors' in user.groups.all %}
<div class="widget author-tools">
<h3>作者工具</h3>
<ul>
<li><a href="{% url 'blog:post_create' %}">写新文章</a></li>
<li><a href="{% url 'blog:drafts' %}">草稿箱 ({{ user.posts.filter(published=False).count }})</a></li>
<li><a href="{% url 'blog:my_posts' %}">我的文章</a></li>
</ul>
</div>
{% endif %}
{% if user.is_staff %}
<div class="widget admin-tools">
<h3>管理工具</h3>
<ul>
<li><a href="{% url 'admin:index' %}">管理后台</a></li>
<li><a href="{% url 'admin:blog_post_changelist' %}">文章管理</a></li>
<li><a href="{% url 'admin:auth_user_changelist' %}">用户管理</a></li>
</ul>
</div>
{% endif %}
{% endif %}
</aside>
---
c.debug处理器
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
<!-- 仅在DEBUG=True时显示调试信息 -->
{% if debug %}
<div class="debug-panel" id="debug-panel">
<button class="debug-toggle" onclick="toggleDebugPanel()">
调试面板 ({{ sql_queries|length }} 查询)
</button>
<div class="debug-content" style="display: none;">
<!-- 请求信息 -->
<section class="debug-section">
<h4>请求信息</h4>
<table class="debug-table">
<tr>
{% endfor %}
</div>
{% else %}
<p>没有SQL查询记录</p>
{% endif %}
</section>
<!-- 模板信息 -->
<section class="debug-section">
<h4>模板信息</h4>
<table class="debug-table">
<tr>
<th>模板引擎</th>
<td>Django Template Language</td>
</tr>
<tr>
<th>当前模板</th>
<td>{{ template_name|default:"未知" }}</td>
</tr>
</table>
</section>
<!-- 设置信息 -->
<section class="debug-section">
<h4>设置信息</h4>
<table class="debug-table">
<tr>
<th>DEBUG模式</th>
<td>{{ debug }}</td>
</tr>
<tr>
<th>数据库引擎</th>
<td>{{ DATABASES.default.ENGINE }}</td>
</tr>
<tr>
<th>缓存后端</th>
<td>{{ CACHES.default.BACKEND }}</td>
</tr>
<tr>
<th>语言代码</th>
<td>{{ LANGUAGE_CODE }}</td>
</tr>
<tr>
<th>时区</th>
<td>{{ TIME_ZONE }}</td>
</tr>
</table>
</section>
<!-- 上下文变量 -->
<section class="debug-section">
<h4>上下文变量</h4>
<div class="context-vars">
{% for key, value in context_data.items %}
<div class="context-var">
<strong>{{ key }}:</strong>
{% if value %}
{{ value|truncatechars:100 }}
{% if value|length > 100 %}...{% endif %}
{% else %}
None
{% endif %}
</div>
{% endfor %}
</div>
</section>
</div>
</div>
<style>
.debug-panel {
position: fixed;
top: 10px;
right: 10px;
z-index: 9999;
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
max-width: 500px;
}
.debug-toggle {
background: #007bff;
color: white;
border: none;
padding: 10px 15px;
cursor: pointer;
border-radius: 4px 4px 0 0;
}
.debug-content {
max-height: 500px;
overflow-y: auto;
padding: 15px;
}
.debug-section {
margin-bottom: 20px;
}
.debug-section h4 {
margin-top: 0;
border-bottom: 1px solid #eee;
padding-bottom: 5px;
}
.debug-table {
width: 100%;
border-collapse: collapse;
}
.debug-table th,
.debug-table td {
padding: 5px;
border: 1px solid #ddd;
text-align: left;
}
.debug-table th {
background: #f8f9fa;
width: 120px;
}
.query-item {
margin-bottom: 10px;
border: 1px solid #eee;
border-radius: 4px;
overflow: hidden;
}
.query-header {
background: #f8f9fa;
padding: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.query-number {
font-weight: bold;
background: #007bff;
color: white;
padding: 2px 6px;
border-radius: 3px;
font-size: 12px;
}
.query-time {
color: #dc3545;
font-family: monospace;
}
.query-sql {
padding: 8px;
background: #f8f9fa;
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
}
.query-summary {
background: #e9ecef;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
}
.context-var {
padding: 5px;
border-left: 3px solid #007bff;
margin-bottom: 5px;
}
</style>
<script>
function toggleDebugPanel() {
var content = document.querySelector('.debug-content');
content.style.display = content.style.display === 'none' ? 'block' : 'none';
}
</script>
{% endif %}
---
02.自定义上下文处理器
a.创建自定义处理器
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
# blog/context_processors.py
from django.conf import settings
from .models import Category, Tag, Post
from django.db.models import Count, Q
def site_info_context(request):
return {
'site_name': getattr(settings, 'SITE_NAME', 'Django Blog'),
'site_description': getattr(settings, 'SITE_DESCRIPTION', '基于Django的博客网站'),
'site_keywords': getattr(settings, 'SITE_KEYWORDS', 'Django, 博客, Python'),
'site_author': getattr(settings, 'SITE_AUTHOR', 'Author'),
'site_logo_url': getattr(settings, 'SITE_LOGO_URL', None),
'contact_email': getattr(settings, 'CONTACT_EMAIL', '[email protected]'),
'ga_tracking_id': getattr(settings, 'GA_TRACKING_ID', None),
}
'linkedin': getattr(settings, 'SOCIAL_LINKEDIN', ''),
'instagram': getattr(settings, 'SOCIAL_INSTAGRAM', ''),
'youtube': getattr(settings, 'SOCIAL_YOUTUBE', ''),
'facebook': getattr(settings, 'SOCIAL_FACEBOOK', ''),
}
# 只返回配置了链接的社交媒体
enabled_social_links = {
platform: url for platform, url in social_links.items()
if url
}
return {'social_links': enabled_social_links}
def recent_content_context(request):
"""最近内容上下文处理器"""
# 最新文章
recent_posts = Post.objects.filter(published=True).order_by('-created_at')[:5]
# 最新评论(需要Comment模型)
try:
from .models import Comment
recent_comments = Comment.objects.filter(
approved=True
).select_related('user', 'post').order_by('-created_at')[:5]
except ImportError:
recent_comments = []
# 热门文章
popular_posts = Post.objects.filter(
published=True
).order_by('-views_count')[:5]
return {
'recent_posts': recent_posts,
'recent_comments': recent_comments,
'popular_posts': popular_posts,
}
def user_dashboard_context(request):
"""用户仪表板上下文处理器"""
if not request.user.is_authenticated:
return {}
user = request.user
# 用户统计信息
dashboard_stats = {
'my_posts_count': Post.objects.filter(author=user).count(),
'published_posts_count': Post.objects.filter(
author=user, published=True
).count(),
'draft_posts_count': Post.objects.filter(
author=user, published=False
).count(),
'total_views': Post.objects.filter(
author=user, published=True
).aggregate(total=Count('views_count'))['total'] or 0,
}
# 用户权限
user_permissions = {
'can_create_post': user.has_perm('blog.add_post'),
'can_edit_post': user.has_perm('blog.change_post'),
'can_delete_post': user.has_perm('blog.delete_post'),
'can_moderate': user.has_perm('blog.can_moderate_post'),
'is_author': user.groups.filter(name='authors').exists(),
'is_editor': user.groups.filter(name='editors').exists(),
}
return {
'user_dashboard_stats': dashboard_stats,
'user_permissions': user_permissions,
}
def theme_context(request):
"""主题和样式上下文处理器"""
# 从session或cookie获取用户主题偏好
theme = request.session.get('theme', request.COOKIES.get('theme', 'light'))
# 检测设备类型
user_agent = request.META.get('HTTP_USER_AGENT', '').lower()
is_mobile = 'mobile' in user_agent or 'android' in user_agent
is_tablet = 'tablet' in user_agent or 'ipad' in user_agent
return {
'theme': theme,
'is_mobile': is_mobile,
'is_tablet': is_tablet,
'is_desktop': not is_mobile and not is_tablet,
}
def analytics_context(request):
"""分析追踪上下文处理器"""
# 当前页面信息用于分析
page_info = {
'title': getattr(request, 'page_title', ''),
'category': getattr(request, 'page_category', ''),
'tags': getattr(request, 'page_tags', ''),
}
# 用户行为追踪数据
tracking_data = {
'page_type': getattr(request, 'page_type', 'unknown'),
'content_id': getattr(request, 'content_id', None),
'author_id': getattr(request, 'author_id', None),
}
return {
'analytics_page': page_info,
'analytics_tracking': tracking_data,
}
def seo_context(request):
"""SEO优化上下文处理器"""
# 基础SEO信息
seo_data = {
'canonical_url': request.build_absolute_uri(),
'og_type': 'website',
'og_site_name': getattr(settings, 'SITE_NAME', 'Django Blog'),
'twitter_card': 'summary_large_image',
'twitter_site': '@' + getattr(settings, 'TWITTER_HANDLE', ''),
}
# 根据页面类型调整SEO信息
path = request.path
if path.startswith('/blog/'):
if 'category' in path:
seo_data.update({
'og_type': 'website',
'page_type': 'category'
})
elif 'tag' in path:
seo_data.update({
'og_type': 'website',
'page_type': 'tag'
})
elif 'post' in path:
seo_data.update({
'og_type': 'article',
'page_type': 'article'
})
return {'seo_data': seo_data}
def notification_context(request):
"""通知上下文处理器"""
if not request.user.is_authenticated:
return {}
# 获取用户未读通知
try:
from .models import Notification
notifications = Notification.objects.filter(
user=request.user,
is_read=False
).order_by('-created_at')[:5]
notification_count = Notification.objects.filter(
user=request.user,
is_read=False
).count()
except ImportError:
notifications = []
notification_count = 0
return {
'user_notifications': notifications,
'notification_count': notification_count,
}
# 在settings.py中注册自定义上下文处理器
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'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',
# 自定义上下文处理器
'blog.context_processors.site_info_context',
'blog.context_processors.navigation_context',
'blog.context_processors.blog_stats_context',
'blog.context_processors.social_links_context',
'blog.context_processors.recent_content_context',
'blog.context_processors.user_dashboard_context',
'blog.context_processors.theme_context',
'blog.context_processors.analytics_context',
'blog.context_processors.seo_context',
'blog.context_processors.notification_context',
],
},
},
]
---
b.条件性上下文处理
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
# blog/context_processors.py (续)
from django.core.cache import cache
from django.utils import timezone
def conditional_category_context(request):
# 只在需要分类信息的页面加载分类
paths_need_categories = [
'/blog/',
'/categories/',
'/tags/',
'/admin/blog/',
]
if not any(request.path.startswith(path) for path in paths_need_categories):
return {}
# 根据用户角色生成不同菜单
user_menu_items = []
# 基础菜单项
user_menu_items.extend([
{'title': '个人资料', 'url': 'users:profile', 'icon': 'fas fa-user'},
{'title': '设置', 'url': 'users:settings', 'icon': 'fas fa-cog'},
])
# 作者菜单
if request.user.groups.filter(name='authors').exists():
user_menu_items.extend([
{'title': '我的文章', 'url': 'blog:my_posts', 'icon': 'fas fa-list'},
{'title': '写文章', 'url': 'blog:post_create', 'icon': 'fas fa-plus'},
])
# 管理员菜单
if request.user.is_staff:
user_menu_items.append({
'title': '管理后台',
'url': 'admin:index',
'icon': 'fas fa-tachometer-alt'
})
return {'user_menu_items': user_menu_items}
def conditional_recent_activity_context(request):
"""条件性最近活动上下文处理器"""
if not request.user.is_authenticated:
return {}
# 只在仪表板页面显示最近活动
if not request.path.startswith('/dashboard/') and not request.path.startswith('/profile/'):
return {}
try:
# 获取用户最近的活动
recent_posts = Post.objects.filter(
author=request.user
).order_by('-created_at')[:3]
recent_comments = Comment.objects.filter(
user=request.user
).order_by('-created_at')[:3]
return {
'recent_user_posts': recent_posts,
'recent_user_comments': recent_comments,
}
except ImportError:
return {}
def performance_optimized_context(request):
"""性能优化的上下文处理器"""
context = {}
# 使用请求级缓存避免重复计算
cache_key_prefix = f"context_{request.user.id}_{request.path}"
# 只对GET请求进行缓存
if request.method == 'GET':
site_stats_key = f"{cache_key_prefix}_site_stats"
site_stats = cache.get(site_stats_key)
if site_stats is None:
site_stats = {
'total_posts': Post.objects.filter(published=True).count(),
'online_users': get_online_users_count(), # 假设有这个函数
}
cache.set(site_stats_key, site_stats, 60) # 缓存1分钟
context['site_stats'] = site_stats
# 根据设备类型返回不同数据
user_agent = request.META.get('HTTP_USER_AGENT', '').lower()
if 'mobile' in user_agent:
context['is_mobile'] = True
context['template_suffix'] = '_mobile'
else:
context['is_mobile'] = False
context['template_suffix'] = ''
return context
def security_context(request):
"""安全相关的上下文处理器"""
security_data = {}
# CSRF令牌(已经默认包含,但可以在这里处理特殊情况)
security_data['csrf_token'] = request.META.get('CSRF_COOKIE')
# 内容安全策略
if getattr(settings, 'SECURE_CONTENT_TYPE_NOSNIFF', True):
security_data['content_security_policy'] = True
# HTTPS重定向状态
security_data['is_secure'] = request.is_secure()
# 检测可疑请求
suspicious_indicators = [
request.META.get('HTTP_X_FORWARDED_FOR') != request.META.get('REMOTE_ADDR'),
len(request.GET.keys()) > 20, # 过多GET参数
len(request.POST.keys()) > 20, # 过多POST参数
]
security_data['is_suspicious'] = any(suspicious_indicators)
return {'security': security_data}
def multilingual_context(request):
"""多语言支持上下文处理器"""
if not getattr(settings, 'USE_I18N', False):
return {}
from django.utils import translation
from django.conf import settings
# 当前语言信息
current_language = translation.get_language()
available_languages = [
(code, name) for code, name in settings.LANGUAGES
]
# 语言切换URL
language_urls = {}
for code, _ in settings.LANGUAGES:
with translation.override(code):
language_urls[code] = request.path
return {
'current_language': current_language,
'available_languages': available_languages,
'language_urls': language_urls,
'use_i18n': True,
}
---
6 表单与认证
6.1 Django表单系统
01.表单基础
a.Form类定义
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
# forms.py
from django import forms
from django.core.validators import RegexValidator
from django.utils.translation import gettext_lazy as _
class ContactForm(forms.Form):
name = forms.CharField(
label=_("姓名"),
max_length=100,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('请输入您的姓名')
}),
error_messages={
'required': _('请输入您的姓名'),
'max_length': _('姓名不能超过100个字符')
email = self.cleaned_data.get('email')
if email and 'example.com' in email:
raise forms.ValidationError(
_('请使用真实的邮箱地址,不要使用example.com域名')
)
return email
def clean_message(self):
"""自定义消息验证"""
message = self.cleaned_data.get('message')
if message and len(message) < 10:
raise forms.ValidationError(
_('消息内容不能少于10个字符')
)
return message
def clean(self):
"""表单整体验证"""
cleaned_data = super().clean()
email = cleaned_data.get('email')
phone = cleaned_data.get('phone')
contact_preference = cleaned_data.get('contact_preference')
# 检查联系方式的完整性
if contact_preference == 'email' and not email:
raise forms.ValidationError(
_('选择邮箱联系方式时必须提供邮箱地址')
)
if contact_preference == 'phone' and not phone:
raise forms.ValidationError(
_('选择电话联系方式时必须提供手机号码')
)
return cleaned_data
# 视图中使用表单
def contact_view(request):
if request.method == 'POST':
form = ContactForm(request.POST, request.FILES)
if form.is_valid():
# 处理表单数据
name = form.cleaned_data['name']
email = form.cleaned_data['email']
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
phone = form.cleaned_data['phone']
contact_preference = form.cleaned_data['contact_preference']
attachment = form.cleaned_data.get('attachment')
# 发送邮件
try:
send_contact_email(
name=name,
email=email,
subject=subject,
message=message,
phone=phone,
contact_preference=contact_preference,
attachment=attachment
)
messages.success(
request,
_('您的消息已成功发送,我们会尽快回复您!')
)
return redirect('contact_success')
except Exception as e:
messages.error(
request,
_('发送消息时出现错误,请稍后重试')
)
logger.error(f"Contact form error: {str(e)}")
else:
form = ContactForm()
return render(request, 'contact/form.html', {'form': form})
---
b.ModelForm使用
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
# models.py
from django.db import models
from django.contrib.auth.models import User
class BlogPost(models.Model):
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
('archived', '已归档'),
]
title = models.CharField('标题', max_length=200)
slug = models.SlugField('URL别名', unique=True)
content = models.TextField('内容')
excerpt = models.TextField('摘要', blank=True)
author = models.ForeignKey(
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '请输入文章标题'
}),
'slug': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '自动生成或手动输入URL别名'
}),
'content': forms.Textarea(attrs={
'class': 'form-control content-editor',
'rows': 20,
'placeholder': '请输入文章内容,支持Markdown格式'
}),
'category': forms.Select(attrs={
'class': 'form-control'
}),
'featured_image': forms.FileInput(attrs={
'class': 'form-control-file',
'accept': 'image/*'
}),
'status': forms.Select(attrs={
'class': 'form-control'
}),
'is_featured': forms.CheckboxInput(attrs={
'class': 'form-check-input'
}),
'meta_title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '留空则使用文章标题'
}),
'meta_description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': '留空则使用文章摘要'
}),
'meta_keywords': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '用逗号分隔关键词'
})
}
labels = {
'title': '文章标题',
'slug': 'URL别名',
'content': '文章内容',
'category': '文章分类',
'featured_image': '特色图片',
'status': '发布状态',
'is_featured': '精选文章',
'meta_title': 'SEO标题',
'meta_description': 'SEO描述',
'meta_keywords': 'SEO关键词'
}
help_texts = {
'slug': '将用于生成文章URL,建议使用英文字母、数字和连字符',
'content': '支持Markdown语法,可以使用代码高亮、表格等功能',
'featured_image': '建议尺寸:1200x630像素,支持jpg、png格式',
'is_featured': '精选文章将在首页特别展示',
'meta_keywords': '多个关键词请用英文逗号分隔'
}
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
# 根据用户权限调整表单
if self.user and not self.user.has_perm('blog.can_publish_post'):
# 普通用户不能直接发布文章
self.fields['status'].choices = [
('draft', '草稿'),
]
self.fields['status'].initial = 'draft'
self.fields['status'].widget.attrs['disabled'] = 'disabled'
if self.user and not self.user.has_perm('blog.can_feature_post'):
# 普通用户不能设置精选
self.fields['is_featured'].widget.attrs['disabled'] = 'disabled'
# 动态设置分类选项
if self.user and not self.user.is_staff:
# 非管理员只能选择自己的分类
self.fields['category'].queryset = Category.objects.filter(
allowed_users=self.user
)
# 如果是编辑模式,调整标签显示
if self.instance and self.instance.pk:
self.fields['tags'].initial = self.instance.tags.all()
def clean_slug(self):
"""验证URL别名唯一性"""
slug = self.cleaned_data.get('slug')
title = self.cleaned_data.get('title')
# 如果没有填写slug,根据标题生成
if not slug and title:
from django.utils.text import slugify
slug = slugify(title)
# 验证唯一性(编辑时排除当前文章)
queryset = BlogPost.objects.filter(slug=slug)
if self.instance and self.instance.pk:
queryset = queryset.exclude(pk=self.instance.pk)
if queryset.exists():
raise forms.ValidationError('该URL别名已存在,请使用其他别名')
return slug
def clean_content(self):
"""验证文章内容"""
content = self.cleaned_data.get('content')
if content and len(content) < 50:
raise forms.ValidationError('文章内容不能少于50个字符')
return content
def clean_featured_image(self):
"""验证特色图片"""
image = self.cleaned_data.get('featured_image')
if image:
# 检查文件大小(最大5MB)
if image.size > 5 * 1024 * 1024:
raise forms.ValidationError('图片文件大小不能超过5MB')
# 检查文件类型
if not image.content_type.startswith('image/'):
raise forms.ValidationError('请上传有效的图片文件')
# 检查图片尺寸
from PIL import Image
try:
img = Image.open(image)
width, height = img.size
if width < 800 or height < 400:
raise forms.ValidationError(
'图片尺寸太小,建议最小尺寸800x400像素'
)
except Exception:
raise forms.ValidationError('无法读取图片文件,请确保文件格式正确')
return image
def clean(self):
"""表单整体验证"""
cleaned_data = super().clean()
status = cleaned_data.get('status')
publish_later = cleaned_data.get('publish_later')
publish_date = cleaned_data.get('publish_date')
custom_excerpt = cleaned_data.get('custom_excerpt')
# 检查发布时间
if publish_later and not publish_date:
self.add_error(
'publish_date',
'选择稍后发布时必须指定发布时间'
)
if publish_date and publish_date <= timezone.now():
self.add_error(
'publish_date',
'发布时间必须是未来时间'
)
# 设置SEO字段默认值
title = cleaned_data.get('title')
if title and not cleaned_data.get('meta_title'):
cleaned_data['meta_title'] = title
content = cleaned_data.get('content')
if custom_excerpt:
cleaned_data['excerpt'] = custom_excerpt
elif content and not cleaned_data.get('excerpt'):
# 自动生成摘要
cleaned_data['excerpt'] = content[:200] + '...' if len(content) > 200 else content
return cleaned_data
def save(self, commit=True):
"""保存表单数据"""
post = super().save(commit=False)
# 设置作者
if not post.author and self.user:
post.author = self.user
# 处理发布时间
status = self.cleaned_data.get('status')
publish_later = self.cleaned_data.get('publish_later')
publish_date = self.cleaned_data.get('publish_date')
if status == 'published':
if publish_later and publish_date:
post.published_at = publish_date
elif not post.published_at:
post.published_at = timezone.now()
else:
# 非发布状态清空发布时间
post.published_at = None
if commit:
post.save()
# 保存多对多关系
self.save_m2m()
return post
# 视图中使用ModelForm
class PostCreateView(LoginRequiredMixin, CreateView):
model = BlogPost
form_class = BlogPostForm
template_name = 'blog/post_create.html'
success_url = reverse_lazy('blog:post_list')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
form.instance.author = self.request.user
messages.success(self.request, '文章创建成功!')
return super().form_valid(self)
def form_invalid(self, form):
messages.error(self.request, '请检查表单中的错误')
return super().form_invalid(form)
---
c.自定义表单字段
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
# forms.py
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
import re
class PhoneField(forms.CharField):
widget = forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '请输入手机号码'
})
def __init__(self, *args, **kwargs):
kwargs.setdefault('max_length', 20)
super().__init__(*args, **kwargs)
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices', [
(1, '★☆☆☆☆'),
(2, '★★☆☆☆'),
(3, '★★★☆☆'),
(4, '★★★★☆'),
(5, '★★★★★'),
])
kwargs['choices'] = choices
super().__init__(*args, **kwargs)
def validate(self, value):
super().validate(value)
if value:
try:
rating = int(value)
if rating < 1 or rating > 5:
raise ValidationError(
_('评分必须在1到5之间'),
code='invalid_rating'
)
except ValueError:
raise ValidationError(
_('请输入有效的评分'),
code='invalid_rating_format'
)
class TagInputField(forms.CharField):
"""标签输入字段"""
widget = forms.TextInput(attrs={
'class': 'form-control tag-input',
'placeholder': '输入标签后按回车添加',
'data-role': 'tagsinput'
})
def __init__(self, *args, **kwargs):
self.tag_model = kwargs.pop('tag_model', None)
super().__init__(*args, **kwargs)
def to_python(self, value):
if not value:
return []
# 分割标签
tags = [tag.strip() for tag in value.split(',') if tag.strip()]
return tags
def validate(self, value):
super().validate(value)
if value:
for tag in value:
if len(tag) > 20:
raise ValidationError(
_('标签 "%(tag)s" 长度不能超过20个字符') % {'tag': tag},
code='tag_too_long'
)
# 验证标签是否包含特殊字符
if re.search(r'[^\w\u4e00-\u9fff\-_\s]', tag):
raise ValidationError(
_('标签 "%(tag)s" 包含无效字符') % {'tag': tag},
code='invalid_tag_chars'
)
def clean(self, value):
tags = super().clean(value)
if tags:
# 去重并排序
tags = list(set(tag.lower() for tag in tags))
tags.sort()
return tags
# 使用自定义字段的表单
class UserProfileForm(forms.ModelForm):
phone = PhoneField(
label='手机号码',
required=False
)
favorite_color = ColorField(
label='喜欢的颜色',
required=False
)
contact_emails = MultiEmailField(
label='联系邮箱',
required=False,
help_text='每行输入一个邮箱地址'
)
rating = RatingField(
label='评分',
required=False
)
interests = TagInputField(
label='兴趣爱好',
required=False,
help_text='输入多个兴趣爱好,用逗号分隔'
)
class Meta:
model = UserProfile
fields = ['bio', 'avatar', 'phone', 'favorite_color', 'contact_emails', 'rating', 'interests']
widgets = {
'bio': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4
}),
'avatar': forms.FileInput(attrs={
'class': 'form-control-file'
})
}
labels = {
'bio': '个人简介',
'avatar': '头像'
}
class AdvancedSearchForm(forms.Form):
"""高级搜索表单,使用多种自定义字段"""
keyword = forms.CharField(
label='关键词',
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '搜索关键词'
}),
required=False
)
category = forms.ModelChoiceField(
queryset=Category.objects.all(),
widget=forms.Select(attrs={'class': 'form-control'}),
empty_label='所有分类',
required=False
)
tags = TagInputField(
label='标签',
required=False
)
date_from = forms.DateField(
label='开始日期',
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
required=False
)
date_to = forms.DateField(
label='结束日期',
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
required=False
)
author = forms.CharField(
label='作者',
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '输入作者用户名'
}),
required=False
)
min_rating = RatingField(
label='最低评分',
required=False
)
sort_by = forms.ChoiceField(
label='排序方式',
choices=[
('relevance', '相关性'),
('date_desc', '最新发布'),
('date_asc', '最早发布'),
('title_asc', '标题 A-Z'),
('title_desc', '标题 Z-A'),
('rating_desc', '评分最高'),
('views_desc', '浏览最多'),
],
initial='relevance',
widget=forms.Select(attrs={'class': 'form-control'})
)
def clean(self):
cleaned_data = super().clean()
date_from = cleaned_data.get('date_from')
date_to = cleaned_data.get('date_to')
if date_from and date_to and date_from > date_to:
raise ValidationError('开始日期不能晚于结束日期')
return cleaned_data
---
02.表单验证和清理
a.字段级验证
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
# forms.py
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from django.core.validators import RegexValidator
import re
from datetime import date, datetime
class RegistrationForm(forms.Form):
username = forms.CharField(
label=_('用户名'),
max_length=150,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('4-20个字符,支持字母、数字和下划线')
}),
validators=[
RegexValidator(
regex=r'^\d{17}[\dXx]$',
message=_('请输入有效的18位身份证号')
)
]
)
agreement = forms.BooleanField(
label=_('我已阅读并同意服务条款和隐私政策'),
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
)
# 验证码
captcha = forms.CharField(
label=_('验证码'),
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('请输入验证码')
})
)
def clean_username(self):
"""用户名验证"""
username = self.cleaned_data.get('username')
# 长度检查
if len(username) < 4:
raise ValidationError(_('用户名长度不能少于4个字符'))
# 非法字符检查
if re.search(r'[^a-zA-Z0-9_]', username):
raise ValidationError(_('用户名只能包含字母、数字和下划线'))
# 特殊用户名检查
forbidden_usernames = ['admin', 'root', 'system', 'test', 'guest']
if username.lower() in forbidden_usernames:
raise ValidationError(_('该用户名已被系统保留'))
# 重复性检查
from django.contrib.auth.models import User
if User.objects.filter(username__iexact=username).exists():
raise ValidationError(_('该用户名已被使用'))
return username.lower()
def clean_email(self):
"""邮箱验证"""
email = self.cleaned_data.get('email')
# 邮箱格式检查
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
raise ValidationError(_('请输入有效的邮箱地址'))
# 禁用邮箱域名检查
forbidden_domains = ['10minutemail.com', 'tempmail.org', 'mailinator.com']
domain = email.split('@')[1].lower()
if domain in forbidden_domains:
raise ValidationError(_('请使用真实的邮箱地址,不要使用临时邮箱'))
# 重复性检查
from django.contrib.auth.models import User
if User.objects.filter(email__iexact=email).exists():
raise ValidationError(_('该邮箱地址已被注册'))
return email.lower()
def clean_password(self):
"""密码验证"""
password = self.cleaned_data.get('password')
# 长度检查
if len(password) < 8:
raise ValidationError(_('密码长度不能少于8个字符'))
# 复杂度检查
if not re.search(r'[a-zA-Z]', password):
raise ValidationError(_('密码必须包含至少一个字母'))
if not re.search(r'\d', password):
raise ValidationError(_('密码必须包含至少一个数字'))
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
raise ValidationError(_('密码必须包含至少一个特殊字符'))
# 常见密码检查
common_passwords = ['password', '12345678', 'qwerty123', 'admin123']
if password.lower() in common_passwords:
raise ValidationError(_('请使用更安全的密码,不要使用常见密码组合'))
# 检查是否包含用户名
username = self.cleaned_data.get('username', '')
if username.lower() in password.lower():
raise ValidationError(_('密码不能包含用户名'))
return password
def clean_confirm_password(self):
"""确认密码验证"""
confirm_password = self.cleaned_data.get('confirm_password')
password = self.cleaned_data.get('password')
if confirm_password != password:
raise ValidationError(_('两次输入的密码不一致'))
return confirm_password
def clean_birth_date(self):
"""出生日期验证"""
birth_date = self.cleaned_data.get('birth_date')
if birth_date:
today = date.today()
age = today.year - birth_date.year - (
(today.month, today.day) < (birth_date.month, birth_date.day)
)
if age < 13:
raise ValidationError(_('您必须年满13岁才能注册'))
if age > 120:
raise ValidationError(_('请输入有效的出生日期'))
return birth_date
def clean_phone(self):
"""手机号码验证"""
phone = self.cleaned_data.get('phone')
if phone:
# 移除所有非数字字符
phone_digits = re.sub(r'[^\d+]', '', phone)
# 中国手机号验证
if not re.match(r'^(\+86)?1[3-9]\d{9}$', phone_digits):
raise ValidationError(_('请输入有效的中国手机号码'))
return phone_digits
return phone
def clean_id_number(self):
"""身份证号验证"""
id_number = self.cleaned_data.get('id_number')
if id_number:
id_number = id_number.upper()
# 基本格式检查
if not re.match(r'^\d{17}[\dX]$', id_number):
raise ValidationError(_('请输入有效的18位身份证号'))
# 地区码验证(简单检查)
area_code = id_number[:6]
if not (110000 <= int(area_code) <= 659004):
raise ValidationError(_('身份证号地区码无效'))
# 出生日期验证
try:
birth_date = datetime.strptime(id_number[6:14], '%Y%m%d').date()
except ValueError:
raise ValidationError(_('身份证号出生日期无效'))
# 年龄检查
today = date.today()
age = today.year - birth_date.year - (
(today.month, today.day) < (birth_date.month, birth_date.day)
)
if age < 13 or age > 120:
raise ValidationError(_('身份证号年龄不符合要求'))
# 校验码验证
weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
check_codes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
total = 0
for i in range(17):
total += int(id_number[i]) * weights[i]
check_code = check_codes[total % 11]
if id_number[-1] != check_code:
raise ValidationError(_('身份证号校验码无效'))
return id_number
return id_number
def clean_captcha(self):
"""验证码验证"""
captcha = self.cleaned_data.get('captcha')
stored_captcha = self.request.session.get('captcha', '')
if not captcha or captcha.upper() != stored_captcha.upper():
raise ValidationError(_('验证码错误'))
return captcha
def clean(self):
"""表单整体验证"""
cleaned_data = super().clean()
first_name = cleaned_data.get('first_name')
last_name = cleaned_data.get('last_name')
birth_date = cleaned_data.get('birth_date')
id_number = cleaned_data.get('id_number')
# 姓名检查
if first_name and last_name:
full_name = f"{first_name}{last_name}"
if len(full_name) < 2 or len(full_name) > 30:
raise ValidationError(_('姓名长度应在2-30个字符之间'))
# 检查是否包含数字
if re.search(r'\d', full_name):
raise ValidationError(_('姓名不能包含数字'))
# 身份证和出生日期一致性检查
if birth_date and id_number:
try:
id_birth_date = datetime.strptime(id_number[6:14], '%Y%m%d').date()
if birth_date != id_birth_date:
raise ValidationError(_('出生日期与身份证号不匹配'))
except ValueError:
pass # 已在上面的方法中处理
return cleaned_data
---
b.表单级验证
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
# forms.py
class ProductOrderForm(forms.Form):
product = forms.ModelChoiceField(
queryset=Product.objects.all(),
label='选择产品',
widget=forms.Select(attrs={'class': 'form-control'})
)
quantity = forms.IntegerField(
label='数量',
min_value=1,
widget=forms.NumberInput(attrs={'class': 'form-control'})
)
unit_price = forms.DecimalField(
label='单价',
except AttributeError:
pass
def clean_quantity(self):
"""数量验证"""
quantity = self.cleaned_data.get('quantity')
product = self.cleaned_data.get('product')
if product and quantity:
if quantity > product.stock:
raise ValidationError(
_('库存不足,当前库存为 %(stock)d 件') % {'stock': product.stock}
)
if quantity > product.max_purchase_quantity:
raise ValidationError(
_('单次购买数量不能超过 %(max)d 件') % {'max': product.max_purchase_quantity}
)
return quantity
def clean_discount_code(self):
"""优惠码验证"""
discount_code = self.cleaned_data.get('discount_code')
if discount_code:
try:
from .models import DiscountCode
code = DiscountCode.objects.filter(
code=discount_code.upper(),
active=True
).first()
if not code:
raise ValidationError(_('优惠码无效或已过期'))
if code.usage_limit and code.used_count >= code.usage_limit:
raise ValidationError(_('优惠码使用次数已达上限'))
if code.min_amount:
# 暂时无法验证总金额,在clean中处理
pass
self.discount_code_obj = code
except Exception:
raise ValidationError(_('优惠码验证失败'))
return discount_code
def clean_contact_phone(self):
"""联系电话验证"""
phone = self.cleaned_data.get('contact_phone')
if phone:
# 中国手机号验证
if not re.match(r'^1[3-9]\d{9}$', phone.replace(' ', '')):
raise ValidationError(_('请输入有效的中国手机号码'))
return phone
def clean(self):
"""表单整体验证"""
cleaned_data = super().clean()
product = cleaned_data.get('product')
quantity = cleaned_data.get('quantity')
payment_method = cleaned_data.get('payment_method')
shipping_method = cleaned_data.get('shipping_method')
discount_code = cleaned_data.get('discount_code')
# 计算总金额
total_amount = 0
if product and quantity:
total_amount = product.price * quantity
# 应用优惠码
if hasattr(self, 'discount_code_obj') and self.discount_code_obj:
code = self.discount_code_obj
if code.min_amount and total_amount < code.min_amount:
raise ValidationError(
_('订单金额需满 %(amount)d 元才能使用此优惠码') % {
'amount': code.min_amount
}
)
# 计算折扣金额
if code.discount_type == 'percentage':
discount_amount = total_amount * code.discount_value / 100
else:
discount_amount = min(code.discount_value, total_amount)
total_amount -= discount_amount
# 记录折扣信息
self.final_discount_amount = discount_amount
# 配送费用
shipping_fees = {
'standard': 10,
'express': 20,
'overnight': 50
}
total_amount += shipping_fees.get(shipping_method, 10)
self.final_total_amount = total_amount
# 支付方式限制检查
if payment_method == 'cod' and total_amount > 1000:
raise ValidationError(
_('货到付款订单金额不能超过1000元,请选择其他支付方式')
)
# 特殊商品支付方式限制
if product and product.payment_method_restricted:
allowed_methods = product.allowed_payment_methods.split(',')
if payment_method not in allowed_methods:
raise ValidationError(
_('该商品仅支持 %(methods)s 支付') % {
'methods': '、'.join(
dict(self.fields['payment_method'].choices)[method]
for method in allowed_methods
)
}
)
# 配送方式和地区限制
if shipping_method == 'overnight':
address = cleaned_data.get('delivery_address', '')
if '偏远地区' in address or '新疆' in address or '西藏' in address:
raise ValidationError(_('偏远地区不支持隔夜配送'))
# 验证用户购买限制
if self.user and self.user.is_authenticated and product:
from django.utils import timezone
from datetime import timedelta
# 检查24小时内购买数量限制
recent_orders = Order.objects.filter(
user=self.user,
product=product,
created_at__gte=timezone.now() - timedelta(hours=24),
status__in=['pending', 'paid', 'shipped']
)
recent_quantity = sum(order.quantity for order in recent_orders)
if recent_quantity + quantity > product.daily_purchase_limit:
raise ValidationError(
_('24小时内购买此商品数量不能超过 %(limit)d 件,您已购买 %(purchased)d 件') % {
'limit': product.daily_purchase_limit,
'purchased': recent_quantity
}
)
return cleaned_data
class EventRegistrationForm(forms.Form):
"""活动报名表单"""
event = forms.ModelChoiceField(
queryset=Event.objects.all(),
label='选择活动',
widget=forms.Select(attrs={'class': 'form-control'})
)
participants = forms.IntegerField(
label='参与人数',
min_value=1,
widget=forms.NumberInput(attrs={'class': 'form-control'})
)
name = forms.CharField(
label='联系人姓名',
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '请输入真实姓名'
})
)
email = forms.EmailField(
label='邮箱地址',
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': '用于接收活动通知'
})
)
phone = forms.CharField(
label='联系电话',
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '请输入手机号码'
})
)
company = forms.CharField(
label='公司/单位',
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '请输入公司或单位名称'
})
)
position = forms.CharField(
label='职位',
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '请输入职位'
})
)
dietary_restrictions = forms.CharField(
label='饮食限制',
required=False,
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': '如有特殊饮食要求请说明'
})
)
emergency_contact = forms.CharField(
label='紧急联系人',
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '紧急联系人姓名'
})
)
emergency_phone = forms.CharField(
label='紧急联系电话',
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '紧急联系人电话'
})
)
agreement = forms.BooleanField(
label='我同意活动条款和隐私政策',
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
)
def clean_participants(self):
"""参与人数验证"""
participants = self.cleaned_data.get('participants')
event = self.cleaned_data.get('event')
if event and participants:
if participants < 1:
raise ValidationError(_('参与人数至少为1人'))
if participants > event.max_participants_per_registration:
raise ValidationError(
_('每次最多报名 %(max)d 人') % {
'max': event.max_participants_per_registration
}
)
if participants > event.remaining_capacity:
raise ValidationError(
_('剩余名额不足,当前仅可报名 %(remaining)d 人') % {
'remaining': event.remaining_capacity
}
)
return participants
def clean_email(self):
"""邮箱验证"""
email = self.cleaned_data.get('email')
event = self.cleaned_data.get('event')
if event and email:
# 检查是否已经报名过
if EventRegistration.objects.filter(
event=event,
email__iexact=email,
status='confirmed'
).exists():
raise ValidationError(_('该邮箱已报名过此活动'))
return email
def clean_phone(self):
"""手机号验证"""
phone = self.cleaned_data.get('phone')
if phone:
# 手机号格式验证
if not re.match(r'^1[3-9]\d{9}$', phone.replace(' ', '')):
raise ValidationError(_('请输入有效的中国手机号码'))
return phone
def clean_emergency_phone(self):
"""紧急联系电话验证"""
emergency_phone = self.cleaned_data.get('emergency_phone')
if emergency_phone and not re.match(r'^1[3-9]\d{9}$', emergency_phone.replace(' ', '')):
raise ValidationError(_('请输入有效的中国手机号码'))
return emergency_phone
def clean(self):
"""表单整体验证"""
cleaned_data = super().clean()
event = cleaned_data.get('event')
participants = cleaned_data.get('participants')
dietary_restrictions = cleaned_data.get('dietary_restrictions')
emergency_contact = cleaned_data.get('emergency_contact')
emergency_phone = cleaned_data.get('emergency_phone')
# 检查活动是否已截止报名
if event and event.registration_deadline:
from django.utils import timezone
if timezone.now() > event.registration_deadline:
raise ValidationError(_('该活动报名已截止'))
# 检查活动是否已满员
if event and event.is_full():
raise ValidationError(_('该活动报名人数已满'))
# 检查年龄限制
if event and event.age_restriction:
# 这里需要用户填写出生日期信息,简化处理
pass
# 检查饮食限制是否需要特殊处理
if event and event.provides_meals and dietary_restrictions:
# 记录特殊饮食要求
pass
# 检查紧急联系人信息完整性
if emergency_contact and not emergency_phone:
self.add_error(
'emergency_phone',
_('填写紧急联系人时必须提供紧急联系电话')
)
if emergency_phone and not emergency_contact:
self.add_error(
'emergency_contact',
_('填写紧急联系电话时必须提供紧急联系人姓名')
)
# 检查报名总人数限制
if event and participants:
current_registrations = EventRegistration.objects.filter(
event=event,
status='confirmed'
).aggregate(total_participants=models.Sum('participants'))
current_total = current_registrations['total_participants'] or 0
if current_total + participants > event.max_participants:
raise ValidationError(
_('活动报名人数已满,当前已报名 %(current)d 人,最多 %(max)d 人') % {
'current': current_total,
'max': event.max_participants
}
)
return cleaned_data
---
6.2 Django认证系统
01.用户认证
a.用户模型和认证后端
a.功能说明
Django认证系统提供用户登录、登出、权限检查等功能,使用authenticate验证凭据、login创建会话、logout清除会话,支持装饰器保护视图。
b.代码示例
---
# models.py - 自定义用户模型
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.core.mail import send_mail
import uuid
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError(_('邮箱地址是必填项'))
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
from django.contrib import messages
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes, force_str
from django.template.loader import render_to_string
from django.contrib.sites.shortcuts import get_current_site
from .tokens import account_activation_token
from .forms import LoginForm, RegistrationForm, PasswordResetForm, SetPasswordForm
@require_http_methods(["GET", "POST"])
def login_view(request):
"""用户登录视图"""
if request.user.is_authenticated:
return redirect('dashboard')
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
email = form.cleaned_data['email']
password = form.cleaned_data['password']
remember_me = form.cleaned_data.get('remember_me', False)
# 验证用户
user = authenticate(request, email=email, password=password)
if user is not None:
if user.is_active:
# 登录用户
login(request, user)
# 记录登录IP
from .utils import get_client_ip
user.last_login_ip = get_client_ip(request)
user.save(update_fields=['last_login_ip'])
# 记录登录日志
LoginLog.objects.create(
user=user,
ip_address=get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
success=True
)
messages.success(request, '登录成功!欢迎回来!')
# 重定向到登录前的页面或首页
next_page = request.GET.get('next')
return redirect(next_page or 'dashboard')
else:
messages.error(request, '您的账户已被禁用,请联系管理员')
else:
# 记录失败登录
LoginLog.objects.create(
email=email,
ip_address=get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
success=False,
reason='认证失败'
)
messages.error(request, '邮箱或密码错误')
else:
form = LoginForm()
return render(request, 'auth/login.html', {'form': form})
def logout_view(request):
"""用户退出视图"""
if request.method == 'POST':
logout(request)
messages.info(request, '您已成功退出登录')
return redirect('login')
# GET请求显示确认页面
return render(request, 'auth/logout_confirm.html')
@require_http_methods(["GET", "POST"])
def register_view(request):
"""用户注册视图"""
if request.user.is_authenticated:
return redirect('dashboard')
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
# 创建用户
user = form.save(commit=False)
user.is_active = False # 需要邮箱验证
user.save()
# 发送激活邮件
current_site = get_current_site(request)
mail_subject = '激活您的账户'
message = render_to_string('auth/activation_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
})
user.email_user(mail_subject, message)
messages.success(
request,
'注册成功!我们已向您的邮箱发送了激活链接,请查收并激活账户。'
)
return redirect('registration_success')
else:
form = RegistrationForm()
return render(request, 'auth/register.html', {'form': form})
def activate_account(request, uidb64, token):
"""激活账户视图"""
try:
uid = force_str(urlsafe_base64_decode(uidb64))
user = CustomUser.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
user = None
if user is not None and account_activation_token.check_token(user, token):
user.is_active = True
user.is_verified = True
user.save()
# 自动登录用户
login(request, user)
messages.success(request, '账户激活成功!欢迎加入我们!')
return redirect('dashboard')
else:
messages.error(request, '激活链接无效或已过期')
return redirect('login')
@require_http_methods(["GET", "POST"])
def password_reset_request(request):
"""密码重置请求视图"""
if request.method == 'POST':
form = PasswordResetForm(request.POST)
if form.is_valid():
email = form.cleaned_data['email']
associated_users = CustomUser.objects.filter(email=email)
if associated_users.exists():
for user in associated_users:
# 生成重置链接
current_site = get_current_site(request)
mail_subject = '重置您的密码'
message = render_to_string('auth/password_reset_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': default_token_generator.make_token(user),
})
user.email_user(mail_subject, message)
messages.success(
request,
'如果该邮箱地址存在,我们已向您发送了密码重置链接。'
)
return redirect('password_reset_done')
else:
form = PasswordResetForm()
return render(request, 'auth/password_reset.html', {'form': form})
def password_reset_confirm(request, uidb64, token):
"""密码重置确认视图"""
try:
uid = force_str(urlsafe_base64_decode(uidb64))
user = CustomUser.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
user = None
if user is not None and default_token_generator.check_token(user, token):
if request.method == 'POST':
form = SetPasswordForm(user, request.POST)
if form.is_valid():
form.save()
messages.success(request, '密码重置成功!请使用新密码登录。')
return redirect('password_reset_complete')
else:
form = SetPasswordForm(user)
return render(request, 'auth/password_reset_confirm.html', {'form': form})
else:
messages.error(request, '密码重置链接无效或已过期')
return redirect('login')
# settings.py - 认证配置
AUTH_USER_MODEL = 'users.CustomUser'
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'users.authentication.EmailOrUsernameModelBackend',
]
LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'dashboard'
LOGOUT_REDIRECT_URL = 'home'
# 密码验证配置
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 8,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
{
'NAME': 'users.validators.PasswordComplexityValidator',
},
]
# sessions.py - 自定义认证后端
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
User = get_user_model()
class EmailOrUsernameModelBackend(BaseBackend):
"""支持邮箱或用户名登录的认证后端"""
def authenticate(self, request, email=None, password=None, **kwargs):
try:
# 如果邮箱格式,使用邮箱查找
if '@' in email:
user = User.objects.get(email=email.lower())
else:
# 否则使用用户名查找
user = User.objects.get(username=email.lower())
except User.DoesNotExist:
return None
if user.check_password(password) and self.user_can_authenticate(user):
return user
return None
def get_user(self, user_id):
try:
user = User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
class PhoneBackend(BaseBackend):
"""手机号认证后端"""
def authenticate(self, request, phone=None, password=None, **kwargs):
try:
user = User.objects.get(phone=phone)
except User.DoesNotExist:
return None
if user.check_password(password) and self.user_can_authenticate(user):
return user
return None
def get_user(self, user_id):
try:
user = User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
---
b.权限系统
a.功能说明
该示例展示了核心功能的实现方法,包含必要的配置和代码逻辑,可直接应用于实际项目开发。
b.代码示例
---
# models.py - 自定义权限模型
from django.db import models
from django.contrib.auth.models import Permission, Group
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import AbstractUser
class ExtendedPermission(models.Model):
name = models.CharField('权限名称', max_length=100)
codename = models.CharField('权限代码', max_length=100, unique=True)
description = models.TextField('权限描述', blank=True)
content_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
verbose_name='内容类型'
)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
@login_required
@user_passes_test(lambda u: u.is_staff or u.groups.filter(name='editors').exists())
def moderate_posts_view(request):
"""内容审核视图 - 需要管理员或编辑者权限"""
if request.method == 'POST':
action = request.POST.get('action')
post_id = request.POST.get('post_id')
post = get_object_or_404(Post, pk=post_id)
if action == 'approve':
post.status = 'published'
post.published_at = timezone.now()
post.save()
messages.success(request, '文章已审核通过')
elif action == 'reject':
post.status = 'rejected'
post.save()
messages.success(request, '文章已拒绝')
return redirect('moderate_posts')
# 获取待审核文章
posts = Post.objects.filter(status='pending').order_by('-created_at')
return render(request, 'blog/moderate_posts.html', {'posts': posts})
# 基于类的视图权限检查
class PostCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""创建文章类视图"""
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
permission_required = 'blog.add_post'
raise_exception = True # 无权限时抛出异常而不是重定向
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
"""更新文章类视图"""
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def test_func(self):
"""测试用户权限"""
post = self.get_object()
return (
post.author == self.request.user or
self.request.user.has_perm('blog.change_post') or
self.request.user.has_perm('blog.can_edit_any_post')
)
def handle_no_permission(self):
"""处理权限不足"""
if self.request.user.is_authenticated:
return HttpResponseForbidden("您没有权限编辑此文章")
return redirect('login')
# 自定义权限检查装饰器
def role_required(role_name):
"""角色权限装饰器"""
def decorator(view_func):
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect('login')
try:
user_role = UserRole.objects.get(
user=request.user,
role__name=role_name,
is_active=True
)
# 检查角色是否过期
if user_role.expires_at and user_role.expires_at < timezone.now():
user_role.is_active = False
user_role.save()
return HttpResponseForbidden("您的角色已过期")
return view_func(request, *args, **kwargs)
except UserRole.DoesNotExist:
return HttpResponseForbidden(f"您需要 {role_name} 角色权限")
return wrapper
return decorator
def has_permission_or_role(permission=None, role=None):
"""权限或角色检查装饰器"""
def decorator(view_func):
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect('login')
# 检查权限
if permission and request.user.has_perm(permission):
return view_func(request, *args, **kwargs)
# 检查角色
if role:
try:
UserRole.objects.get(
user=request.user,
role__name=role,
is_active=True
)
return view_func(request, *args, **kwargs)
except UserRole.DoesNotExist:
pass
return HttpResponseForbidden("权限不足")
return wrapper
return decorator
# 使用自定义装饰器
@login_required
@role_required('content_manager')
def manage_content_view(request):
"""内容管理视图 - 需要内容管理员角色"""
return render(request, 'admin/manage_content.html')
@login_required
@has_permission_or_role(permission='blog.moderate_post', role='moderator')
def moderate_comments_view(request):
"""评论审核视图 - 需要审核权限或角色"""
return render(request, 'admin/moderate_comments.html')
# 权限管理工具
class PermissionManager:
"""权限管理器"""
@staticmethod
def assign_permission(user, permission_codename, content_type=None):
"""为用户分配权限"""
try:
if content_type:
permission = Permission.objects.get(
codename=permission_codename,
content_type=content_type
)
else:
permission = Permission.objects.get(codename=permission_codename)
user.user_permissions.add(permission)
return True
except Permission.DoesNotExist:
return False
@staticmethod
def assign_role(user, role_name, assigned_by=None, expires_at=None):
"""为用户分配角色"""
try:
role = Role.objects.get(name=role_name, is_active=True)
UserRole.objects.update_or_create(
user=user,
role=role,
defaults={
'assigned_by': assigned_by,
'expires_at': expires_at,
'is_active': True
}
)
return True
except Role.DoesNotExist:
return False
@staticmethod
def revoke_role(user, role_name):
"""撤销用户角色"""
try:
role = Role.objects.get(name=role_name)
user_role = UserRole.objects.get(user=user, role=role)
user_role.is_active = False
user_role.save()
return True
except (Role.DoesNotExist, UserRole.DoesNotExist):
return False
@staticmethod
def user_has_permission(user, permission_codename):
"""检查用户是否有指定权限"""
return user.has_perm(permission_codename)
@staticmethod
def user_has_role(user, role_name):
"""检查用户是否有指定角色"""
try:
UserRole.objects.get(
user=user,
role__name=role_name,
is_active=True
)
return True
except UserRole.DoesNotExist:
return False
# 在信号中自动分配权限
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=CustomUser)
def assign_default_permissions(sender, instance, created, **kwargs):
"""为新用户分配默认权限"""
if created:
# 分组处理
if instance.is_staff:
# 管理员组
try:
admin_group = Group.objects.get(name='administrators')
instance.groups.add(admin_group)
except Group.DoesNotExist:
pass
else:
# 普通用户组
try:
user_group = Group.objects.get(name='regular_users')
instance.groups.add(user_group)
except Group.DoesNotExist:
pass
# 分配个人权限
PermissionManager.assign_permission(
instance,
'blog.add_post'
)
PermissionManager.assign_permission(
instance,
'blog.change_post'
)
PermissionManager.assign_permission(
instance,
'blog.delete_post'
)
---
c.会话管理
a.功能说明
会话管理通过request.session字典接口存取数据,支持设置过期时间、清除会话、修改会话键,提供安全的服务器端数据存储机制。
b.代码示例
---
# settings.py - 会话配置
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 数据库存储
# SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 缓存存储
# 会话Cookie设置
SESSION_COOKIE_NAME = 'sessionid' # Cookie名称
SESSION_COOKIE_AGE = 1209600 # Cookie有效期(2周)
SESSION_COOKIE_DOMAIN = None # Cookie域名
SESSION_COOKIE_PATH = '/' # Cookie路径
SESSION_COOKIE_SECURE = False # HTTPS传输
SESSION_COOKIE_HTTPONLY = True # 仅HTTP访问
SESSION_COOKIE_SAMESITE = 'Lax' # SameSite策略
# 会话保存设置
SESSION_SAVE_EVERY_REQUEST = False # 每次请求都保存会话
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 浏览器关闭时过期
product_id = request.POST.get('product_id')
quantity = int(request.POST.get('quantity', 1))
if action == 'add':
# 添加商品到购物车
if product_id in cart:
cart[product_id]['quantity'] += quantity
else:
# 获取商品信息
try:
product = Product.objects.get(pk=product_id)
cart[product_id] = {
'name': product.name,
'price': str(product.price),
'quantity': quantity,
'added_at': timezone.now().isoformat()
}
except Product.DoesNotExist:
pass
elif action == 'update':
# 更新商品数量
if product_id in cart:
cart[product_id]['quantity'] = quantity
elif action == 'remove':
# 移除商品
if product_id in cart:
del cart[product_id]
elif action == 'clear':
# 清空购物车
cart = {}
# 保存购物车到会话
request.session[cart_key] = cart
# 标记会话为已修改
request.session.modified = True
return JsonResponse({'cart': cart, 'cart_count': len(cart)})
# 自定义会话存储后端
from django.contrib.sessions.backends.base import SessionBase
from django.core.cache import caches
import pickle
import json
class CustomSessionStore(SessionBase):
"""自定义会话存储"""
def __init__(self, session_key=None):
super().__init__(session_key)
self.cache = caches['sessions']
def load(self):
"""加载会话数据"""
try:
session_data = self.cache.get(self.session_key)
if session_data is None:
self.create()
return {}
return self.decode(session_data)
except Exception:
self.create()
return {}
def create(self):
"""创建新会话"""
self.session_key = self._get_new_session_key()
self._session_cache = {}
self.save()
def save(self, must_create=False):
"""保存会话数据"""
if self.session_key is None:
return self.create()
data = self.encode(self._session_cache)
if must_create:
# 确保会话不存在
result = self.cache.add(self.session_key, data, self.get_expiry_age())
else:
# 更新现有会话
result = self.cache.set(self.session_key, data, self.get_expiry_age())
if not result:
raise CreateError
def exists(self, session_key):
"""检查会话是否存在"""
return self.cache.has_key(session_key)
def delete(self, session_key=None):
"""删除会话"""
if session_key is None:
session_key = self.session_key
self.cache.delete(session_key)
@classmethod
def clear_expired(cls):
"""清理过期会话"""
pass # 缓存后端自动处理过期
# 会话中间件扩展
class SessionMiddlewareExtension:
"""会话中间件扩展"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 处理会话前的逻辑
self._before_session_processing(request)
response = self.get_response(request)
# 处理会话后的逻辑
self._after_session_processing(request, response)
return response
def _before_session_processing(self, request):
"""会话处理前"""
# 检查会话是否过期
if request.user.is_authenticated:
last_activity = request.session.get('last_activity')
if last_activity:
last_activity_time = datetime.fromisoformat(last_activity)
if timezone.now() - last_activity_time > timedelta(hours=24):
# 会话超时,强制重新登录
from django.contrib.auth import logout
logout(request)
def _after_session_processing(self, request, response):
"""会话处理后"""
# 设置安全标志的Cookie
if request.is_secure():
response.set_cookie(
'session_security',
'secure',
max_age=SESSION_COOKIE_AGE,
secure=True,
httponly=True,
samesite='Strict'
)
# 会话分析工具
class SessionAnalyzer:
"""会话分析工具"""
@staticmethod
def get_active_sessions(minutes=30):
"""获取活跃会话"""
cutoff_time = timezone.now() - timedelta(minutes=minutes)
sessions = Session.objects.filter(
expire_date__gte=cutoff_time
)
return sessions
@staticmethod
def get_session_count_by_ip():
"""按IP统计会话数量"""
from django.db.models import Count
sessions = Session.objects.annotate(
ip_address=Cast('session_data', output_field=TextField())
)
# 这里需要解析session_data获取IP信息
return sessions
@staticmethod
def cleanup_expired_sessions():
"""清理过期会话"""
deleted_count = Session.objects.filter(
expire_date__lt=timezone.now()
).delete()[0]
return deleted_count
@staticmethod
def analyze_user_session_patterns(user_id):
"""分析用户会话模式"""
from django.contrib.auth import get_user_model
User = get_user_model()
try:
user = User.objects.get(pk=user_id)
# 分析用户访问模式
# 这里需要实现具体的分析逻辑
return {
'user': user,
'avg_session_duration': 0,
'preferred_pages': [],
'activity_times': []
}
except User.DoesNotExist:
return None
# API会话管理
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class APISessionAuthentication(BaseAuthentication):
"""API会话认证"""
def authenticate(self, request):
# 获取会话token
session_token = request.headers.get('X-Session-Token')
if not session_token:
return None
try:
# 验证会话token
session_data = APISession.objects.get(
token=session_token,
is_active=True,
expires_at__gt=timezone.now()
)
user = session_data.user
return (user, session_data)
except APISession.DoesNotExist:
raise AuthenticationFailed('无效的会话token')
def authenticate_header(self, request):
return 'Session'
# REST API会话视图
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def create_api_session(request):
"""创建API会话"""
# 使现有会话失效
APISession.objects.filter(user=request.user, is_active=True).update(is_active=False)
# 创建新的API会话
api_session = APISession.objects.create(
user=request.user,
token=generate_token(),
expires_at=timezone.now() + timedelta(hours=24),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
ip_address=request.META.get('REMOTE_ADDR')
)
return Response({
'token': api_session.token,
'expires_at': api_session.expires_at
}, status=status.HTTP_201_CREATED)
---
7 实战与部署
7.1 实战案例
01.功能模块
a.分类1
用户管理:注册、登录、个人资料
文章管理:创建、编辑、删除、发布
分类标签:分类管理、标签系统
评论系统:评论发表、回复、审核
搜索功能:全文搜索、分类筛选
用户交互:点赞、收藏、分享
b.数据模型设计
用户模型扩展
文章模型设计
评论模型设计
模型关系定义
02.博客系统架构设计
a.项目结构
blog_project/
├── manage.py
├── requirements.txt
├── blog_project/
│ ├── __init__.py
│ ├── settings/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── development.py
│ │ ├── production.py
│ │ └── testing.py
│ ├── urls.py
│ ├── wsgi.py
│ └── asgi.py
├── apps/
│ ├── __init__.py
│ ├── users/
│ │ ├── __init__.py
│ │ ├── models.py
│ │ ├── views.py
│ │ ├── forms.py
│ │ ├── urls.py
│ │ ├── admin.py
│ │ ├── managers.py
│ │ ├── signals.py
│ │ └── migrations/
│ ├── blog/
│ │ ├── __init__.py
│ │ ├── models.py
│ │ ├── views.py
│ │ ├── forms.py
│ │ ├── urls.py
│ │ ├── admin.py
│ │ ├── managers.py
│ │ ├── utils.py
│ │ ├── signals.py
│ │ └── migrations/
│ ├── comments/
│ │ ├── __init__.py
│ │ ├── models.py
│ │ ├── views.py
│ │ ├── forms.py
│ │ ├── urls.py
│ │ ├── admin.py
│ │ └── migrations/
│ └── core/
│ ├── __init__.py
│ ├── models.py
│ ├── views.py
│ ├── utils.py
│ ├── context_processors.py
│ ├── middleware.py
│ ├── templatetags/
│ └── management/
├── templates/
│ ├── base.html
│ ├── blog/
│ ├── users/
│ ├── comments/
│ └── core/
├── static/
│ ├── css/
│ ├── js/
│ ├── images/
│ └── fonts/
└── media/
├── avatars/
├── posts/
└── uploads/
b.功能模块设计
a.用户模块 (users)
用户注册、登录、退出
个人资料管理
密码重置
用户权限管理
b.博客模块 (blog)
文章CRUD操作
分类管理
标签系统
文章搜索
SEO优化
c.评论模块 (comments)
评论发表和管理
评论回复
评论审核
评论点赞
d.核心模块 (core)
通用工具函数
中间件
上下文处理器
自定义标签过滤器
03.数据模型示例
# apps/users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
class User(AbstractUser):
"""扩展用户模型"""
username = None # 不使用username字段
email = models.EmailField(_('邮箱地址'), unique=True)
# 基本信息
first_name = models.CharField(_('姓名'), max_length=50)
last_name = models.CharField(_('姓氏'), max_length=50)
nickname = models.CharField(_('昵称'), max_length=50, blank=True)
bio = models.TextField(_('个人简介'), max_length=500, blank=True)
avatar = models.ImageField(_('头像'), upload_to='avatars/', blank=True)
website = models.URLField(_('个人网站'), blank=True)
# 社交媒体
github = models.CharField(_('GitHub'), max_length=100, blank=True)
twitter = models.CharField(_('Twitter'), max_length=100, blank=True)
linkedin = models.CharField(_('LinkedIn'), max_length=100, blank=True)
# 设置字段
email_verified = models.BooleanField(_('邮箱已验证'), default=False)
is_premium = models.BooleanField(_('高级用户'), default=False)
theme_preference = models.CharField(
_('主题偏好'),
max_length=10,
choices=[
('light', '浅色'),
('dark', '深色'),
('auto', '自动')
],
default='light'
)
# 统计字段
post_count = models.PositiveIntegerField(_('文章数量'), default=0)
comment_count = models.PositiveIntegerField(_('评论数量'), default=0)
like_count = models.PositiveIntegerField(_('获赞数量'), default=0)
# 时间字段
last_login_ip = models.GenericIPAddressField(_('最后登录IP'), null=True, blank=True)
created_at = models.DateTimeField(_('创建时间'), auto_now_add=True)
updated_at = models.DateTimeField(_('更新时间'), auto_now=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name']
class Meta:
verbose_name = _('用户')
verbose_name_plural = _('用户')
db_table = 'blog_user'
def __str__(self):
return self.get_full_name() or self.email
def get_absolute_url(self):
return reverse('users:profile', kwargs={'pk': self.pk})
def get_full_name(self):
"""获取全名"""
return f"{self.first_name} {self.last_name}".strip()
def get_display_name(self):
"""获取显示名称"""
return self.nickname or self.get_full_name() or self.email
# apps/blog/models.py
from django.contrib.auth import get_user_model
from django.urls import reverse
from django.utils.text import slugify
from django.utils import timezone
User = get_user_model()
class Category(models.Model):
"""文章分类"""
name = models.CharField(_('分类名称'), max_length=100, unique=True)
slug = models.SlugField(_('URL别名'), unique=True)
description = models.TextField(_('分类描述'), blank=True)
color = models.CharField(_('颜色'), max_length=7, default='#007bff')
icon = models.CharField(_('图标'), max_length=50, blank=True)
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True)
order = models.PositiveIntegerField(_('排序'), default=0)
is_active = models.BooleanField(_('是否激活'), default=True)
class Meta:
verbose_name = _('分类')
verbose_name_plural = _('分类')
ordering = ['order', 'name']
db_table = 'blog_category'
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('blog:category_posts', kwargs={'slug': self.slug})
def get_post_count(self):
"""获取分类下的文章数量"""
return self.posts.filter(status='published').count()
class Tag(models.Model):
"""文章标签"""
name = models.CharField(_('标签名称'), max_length=50, unique=True)
slug = models.SlugField(_('URL别名'), unique=True)
description = models.TextField(_('标签描述'), blank=True)
color = models.CharField(_('颜色'), max_length=7, default='#6c757d')
class Meta:
verbose_name = _('标签')
verbose_name_plural = _('标签')
ordering = ['name']
db_table = 'blog_tag'
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('blog:tag_posts', kwargs={'slug': self.slug})
def get_post_count(self):
"""获取标签下的文章数量"""
return self.posts.filter(status='published').count()
class PostManager(models.Manager):
"""文章管理器"""
def published(self):
return self.filter(status='published')
def featured(self):
return self.filter(status='published', is_featured=True)
def popular(self):
return self.annotate(
comment_count=models.Count('comments')
).filter(status='published').order_by('-comment_count', '-views_count')
def by_category(self, category):
return self.filter(category=category, status='published')
def by_author(self, author):
return self.filter(author=author, status='published')
class Post(models.Model):
"""文章模型"""
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
('archived', '已归档'),
('private', '私密'),
]
title = models.CharField(_('标题'), max_length=200)
slug = models.SlugField(_('URL别名'), max_length=200, unique=True)
content = models.TextField(_('内容'))
excerpt = models.TextField(_('摘要'), max_length=500, blank=True)
# 关联字段
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='posts',
verbose_name=_('作者')
)
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='posts',
verbose_name=_('分类')
)
tags = models.ManyToManyField(
Tag,
blank=True,
related_name='posts',
verbose_name=_('标签')
)
# 图片字段
featured_image = models.ImageField(
_('特色图片'),
upload_to='posts/images/',
blank=True,
null=True
)
# 状态和标志
status = models.CharField(
_('状态'),
max_length=10,
choices=STATUS_CHOICES,
default='draft'
)
is_featured = models.BooleanField(_('是否精选'), default=False)
allow_comments = models.BooleanField(_('允许评论'), default=True)
is_top = models.BooleanField(_('是否置顶'), default=False)
# SEO字段
meta_title = models.CharField(_('SEO标题'), max_length=200, blank=True)
meta_description = models.TextField(_('SEO描述'), max_length=300, blank=True)
meta_keywords = models.CharField(_('SEO关键词'), max_length=200, blank=True)
# 统计字段
views_count = models.PositiveIntegerField(_('浏览次数'), default=0)
likes_count = models.PositiveIntegerField(_('点赞次数'), default=0)
comments_count = models.PositiveIntegerField(_('评论次数'), default=0)
# 时间字段
created_at = models.DateTimeField(_('创建时间'), auto_now_add=True)
updated_at = models.DateTimeField(_('更新时间'), auto_now=True)
published_at = models.DateTimeField(_('发布时间'), null=True, blank=True)
objects = PostManager()
class Meta:
verbose_name = _('文章')
verbose_name_plural = _('文章')
ordering = ['-is_top', '-published_at', '-created_at']
indexes = [
models.Index(fields=['status', 'published_at']),
models.Index(fields=['author', 'status']),
models.Index(fields=['category', 'status']),
]
def __str__(self):
return self.title
def save(self, *args, **kwargs):
# 自动生成slug
if not self.slug:
self.slug = slugify(self.title)
# 自动生成摘要
if not self.excerpt:
from django.utils.html import strip_tags
plain_content = strip_tags(self.content)
self.excerpt = plain_content[:200] + '...' if len(plain_content) > 200 else plain_content
# 自动设置发布时间
if self.status == 'published' and not self.published_at:
self.published_at = timezone.now()
# 自动生成SEO字段
if not self.meta_title:
self.meta_title = self.title
if not self.meta_description and self.excerpt:
self.meta_description = self.excerpt
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('blog:post_detail', kwargs={'slug': self.slug})
def get_next_post(self):
"""获取下一篇文章"""
return self.__class__.objects.filter(
status='published',
published_at__gt=self.published_at
).order_by('published_at').first()
def get_previous_post(self):
"""获取上一篇文章"""
return self.__class__.objects.filter(
status='published',
published_at__lt=self.published_at
).order_by('-published_at').first()
def get_related_posts(self, limit=5):
"""获取相关文章"""
related_posts = self.__class__.objects.filter(
status='published'
).filter(
models.Q(category=self.category) |
models.Q(tags__in=self.tags.all())
).exclude(pk=self.pk).distinct()
return related_posts[:limit]
def get_reading_time(self):
"""估算阅读时间(分钟)"""
word_count = len(self.content.split())
reading_time = max(1, word_count // 200) # 假设每分钟200字
return reading_time
# apps/comments/models.py
class CommentManager(models.Manager):
"""评论管理器"""
def approved(self):
return self.filter(is_approved=True)
def by_post(self, post):
return self.filter(post=post, is_approved=True)
class Comment(models.Model):
"""评论模型"""
post = models.ForeignKey(
'blog.Post',
on_delete=models.CASCADE,
related_name='comments',
verbose_name=_('文章')
)
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='comments',
verbose_name=_('作者')
)
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='replies',
verbose_name=_('父评论')
)
content = models.TextField(_('评论内容'))
is_approved = models.BooleanField(_('已审核'), default=False)
likes_count = models.PositiveIntegerField(_('点赞次数'), default=0)
created_at = models.DateTimeField(_('创建时间'), auto_now_add=True)
updated_at = models.DateTimeField(_('更新时间'), auto_now=True)
objects = CommentManager()
class Meta:
verbose_name = _('评论')
verbose_name_plural = _('评论')
ordering = ['created_at']
indexes = [
models.Index(fields=['post', 'is_approved', 'created_at']),
]
def __str__(self):
return f"{self.author.get_display_name()} on {self.post.title}"
def get_replies(self):
"""获取回复"""
return self.replies.filter(is_approved=True).order_by('created_at')
def is_reply(self):
"""是否为回复"""
return self.parent is not None
def get_level(self):
"""获取评论层级"""
level = 0
parent = self.parent
while parent:
level += 1
parent = parent.parent
if level >= 3: # 限制回复层级
break
return level
04.视图和URL配置
# apps/blog/views.py
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404, redirect
from django.contrib import messages
from django.db.models import Q, Count
from django.core.paginator import Paginator
from django.http import JsonResponse, HttpResponseBadRequest
from django.views.decorators.http import require_POST
from django.utils.decorators import method_decorator
from .models import Post, Category, Tag
from .forms import PostForm, PostSearchForm
class PostListView(ListView):
"""文章列表视图"""
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
queryset = Post.objects.published().select_related('author', 'category').prefetch_related('tags')
# 搜索功能
search_form = PostSearchForm(self.request.GET)
if search_form.is_valid():
query = search_form.cleaned_data.get('q')
if query:
queryset = queryset.filter(
Q(title__icontains=query) |
Q(content__icontains=query) |
Q(excerpt__icontains=query)
)
# 分类过滤
category_slug = self.kwargs.get('category_slug')
if category_slug:
category = get_object_or_404(Category, slug=category_slug)
queryset = queryset.filter(category=category)
# 标签过滤
tag_slug = self.kwargs.get('tag_slug')
if tag_slug:
tag = get_object_or_404(Tag, slug=tag_slug)
queryset = queryset.filter(tags=tag)
# 作者过滤
author_id = self.kwargs.get('author_id')
if author_id:
queryset = queryset.filter(author_id=author_id)
# 排序
sort_by = self.request.GET.get('sort', 'latest')
if sort_by == 'popular':
queryset = queryset.annotate(
comment_count=Count('comments')
).order_by('-comment_count', '-views_count')
elif sort_by == 'views':
queryset = queryset.order_by('-views_count', '-published_at')
else:
queryset = queryset.order_by('-is_top', '-published_at')
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# 搜索表单
context['search_form'] = PostSearchForm(self.request.GET)
# 分类信息
category_slug = self.kwargs.get('category_slug')
if category_slug:
context['current_category'] = get_object_or_404(Category, slug=category_slug)
# 标签信息
tag_slug = self.kwargs.get('tag_slug')
if tag_slug:
context['current_tag'] = get_object_or_404(Tag, slug=tag_slug)
# 排序选项
context['current_sort'] = self.request.GET.get('sort', 'latest')
# 侧边栏数据
context['recent_posts'] = Post.objects.published()[:5]
context['popular_posts'] = Post.objects.published().order_by('-views_count')[:5]
context['categories'] = Category.objects.filter(is_active=True)
context['tags'] = Tag.objects.all()[:20]
return context
class PostDetailView(DetailView):
"""文章详情视图"""
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
def get_queryset(self):
return Post.objects.published().select_related('author', 'category').prefetch_related(
'tags', 'comments__author'
)
def get_object(self, queryset=None):
obj = super().get_object(queryset)
# 增加浏览次数
Post.objects.filter(pk=obj.pk).update(views_count=F('views_count') + 1)
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
post = context['post']
# 相关文章
context['related_posts'] = post.get_related_posts()
# 下一篇文章
context['next_post'] = post.get_next_post()
# 上一篇文章
context['previous_post'] = post.get_previous_post()
# 评论
if post.allow_comments:
context['comments'] = post.comments.approved().order_by('created_at')
context['comment_count'] = post.comments.approved().count()
# 用户是否点赞
if self.request.user.is_authenticated:
context['is_liked'] = post.likes.filter(
user=self.request.user
).exists()
return context
class PostCreateView(LoginRequiredMixin, CreateView):
"""创建文章视图"""
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['author'] = self.request.user
return kwargs
def form_valid(self, form):
form.instance.author = self.request.user
response = super().form_valid(form)
messages.success(self.request, '文章创建成功!')
return response
def get_success_url(self):
if self.object.status == 'published':
return self.object.get_absolute_url()
return reverse_lazy('blog:drafts')
class PostUpdateView(LoginRequiredMixin, UpdateView):
"""更新文章视图"""
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def get_queryset(self):
return Post.objects.filter(author=self.request.user)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['author'] = self.request.user
return kwargs
def form_valid(self, form):
response = super().form_valid(form)
messages.success(self.request, '文章更新成功!')
return response
class PostDeleteView(LoginRequiredMixin, DeleteView):
"""删除文章视图"""
model = Post
template_name = 'blog/post_confirm_delete.html'
success_url = reverse_lazy('blog:post_list')
def get_queryset(self):
return Post.objects.filter(author=self.request.user)
def delete(self, request, *args, **kwargs):
messages.success(request, '文章删除成功!')
return super().delete(request, *args, **kwargs)
# API视图
@method_decorator(require_POST, name='dispatch')
class PostLikeView(LoginRequiredMixin, View):
"""文章点赞视图"""
def post(self, request, pk):
post = get_object_or_404(Post, pk=pk)
# 切换点赞状态
like, created = PostLike.objects.get_or_create(
post=post,
user=request.user
)
if not created:
like.delete()
liked = False
message = '已取消点赞'
else:
liked = True
message = '点赞成功'
# 更新点赞计数
post.likes_count = PostLike.objects.filter(post=post).count()
post.save(update_fields=['likes_count'])
return JsonResponse({
'liked': liked,
'likes_count': post.likes_count,
'message': message
})
@method_decorator(require_POST, name='dispatch')
class PostBookmarkView(LoginRequiredMixin, View):
"""文章收藏视图"""
def post(self, request, pk):
post = get_object_or_404(Post, pk=pk)
# 切换收藏状态
bookmark, created = PostBookmark.objects.get_or_create(
post=post,
user=request.user
)
if not created:
bookmark.delete()
bookmarked = False
message = '已取消收藏'
else:
bookmarked = True
message = '收藏成功'
return JsonResponse({
'bookmarked': bookmarked,
'message': message
})
# 搜索视图
from django.views import View
from django.http import JsonResponse
class PostSearchView(View):
"""文章搜索视图"""
def get(self, request):
query = request.GET.get('q', '').strip()
if not query:
return JsonResponse({'posts': [], 'total': 0})
posts = Post.objects.published().filter(
Q(title__icontains=query) |
Q(content__icontains=query) |
Q(excerpt__icontains=query)
).select_related('author', 'category').prefetch_related('tags')[:20]
results = []
for post in posts:
results.append({
'id': post.pk,
'title': post.title,
'excerpt': post.excerpt,
'author': post.author.get_display_name(),
'category': post.category.name if post.category else None,
'published_at': post.published_at.strftime('%Y-%m-%d'),
'url': post.get_absolute_url(),
'thumbnail': post.featured_image.url if post.featured_image else None
})
return JsonResponse({
'posts': results,
'total': len(results),
'query': query
})
# apps/blog/urls.py
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
# 文章列表
path('', views.PostListView.as_view(), name='post_list'),
path('search/', views.PostListView.as_view(), name='search'),
path('category/<slug:category_slug>/', views.PostListView.as_view(), name='category_posts'),
path('tag/<slug:tag_slug>/', views.PostListView.as_view(), name='tag_posts'),
path('author/<int:author_id>/', views.PostListView.as_view(), name='author_posts'),
# 文章详情
path('post/<slug:slug>/', views.PostDetailView.as_view(), name='post_detail'),
# 文章管理
path('create/', views.PostCreateView.as_view(), name='post_create'),
path('edit/<int:pk>/', views.PostUpdateView.as_view(), name='post_edit'),
path('delete/<int:pk>/', views.PostDeleteView.as_view(), name='post_delete'),
path('drafts/', views.DraftListView.as_view(), name='drafts'),
# API
path('api/<int:pk>/like/', views.PostLikeView.as_view(), name='post_like'),
path('api/<int:pk>/bookmark/', views.PostBookmarkView.as_view(), name='post_bookmark'),
path('api/search/', views.PostSearchView.as_view(), name='api_search'),
]
7.2 环境搭建
00.项目环境准备
a.Windows10
PyCharm 2020.3.3
b.Centos7.6
python-3.7.2、django-2.1.7、cookiecutter-django-2.0.13、MySQL-8.0.20、Redis-3.2.1
01.Python-3.7.2、Pip3-19.0.2
a.安装依赖
yum -y install wget
yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel libffi-devel gcc make
yum -y install python-devel mysql-devel mysql-lib bzip2-devel
b.下载Python安装包
cd /usr/local
wget https://www.python.org/ftp/python/3.7.2/Python-3.7.2.tar.xz
tar -xvJf Python-3.7.2.tar.xz
cd /usr/local/Python-3.7.2
./configure --prefix=/usr/local/Python-3.7.2/ --enable-optimizations
make && make install
c.环境变量
echo 'export PATH=$PATH:/usr/local/Python-3.7.2/bin' >> /etc/profile && source /etc/profile
d.创建软链接
ln -s /usr/local/Python-3.7.2/bin/python3 /usr/bin/python3
ln -s /usr/local/Python-3.7.2/bin/pip3 /usr/bin/pip3
e.检查是否安装成功
python3 -V
pip3 -V
f.用update-alternatives来为整个系统更改Python版本
a.查看系统自带的Python、pip
whereis python
whereis pip
b.设置优先级
update-alternatives --install /usr/bin/python python /usr/bin/python2 1
update-alternatives --install /usr/bin/python python /usr/bin/python3 2
c.列出可用的Python替代版本
update-alternatives --list
d.选择可用的Python替代版本
update-alternatives --config python
e.测试
python --version --Python 3.7.2
python2 --version --Python 2.7.5
python3 --version --Python 3.7.2
g.更改默认python为python3后,无法使用yum命令,原因:yum依赖python2
a.修改第一处地方
vi /usr/bin/yum -> 将 #!/usr/bin/python 改为 #!/usr/bin/python2
b.修改第二处地方
vi /usr/libexec/urlgrabber-ext-down -> 将 #!/usr/bin/python 改为 #!/usr/bin/python2
02.cookiecutter-django-2.0.13模板,创建answer项目
a.安装Cookiecutter命令
pip3 install cookiecutter -i https://pypi.python.org/simple
b.使用cookiecutter-django-2.0.13模板来创建项目
cd /root/
cookiecutter https://github.com/pydanny/cookiecutter-django/archive/2.0.13.zip
---------------------------------------------------------------
You've downloaded C:\Users\mysla\.cookiecutters\cookiecutter-django before.
Is it okay to delete and re-download it? [yes]: no --不下载新版本
Do you want to re-use the existing version? [yes]: yes --仍然使用该本地版本
project_name [My Awesome Project]: answer --project_name
project_slug [answer]: answer --project_slug
description [Behold My Awesome Project!]: answer --description
author_name [Daniel Roy Greenfeld]: halavah --author_name
domain_name [example.com]: halavah.buzz --domain_name
email [[email protected]]: [email protected] --email
version [0.1.0]: --version
Select open_source_license:
1 - MIT
2 - BSD
3 - GPLv3
4 - Apache Software License 2.0
5 - Not open source
Choose from 1, 2, 3, 4, 5 [1]: 5 --Not open source
timezone [UTC]: Asia/Shanghai --timezone
windows [n]: n --windows
use_pycharm [n]: y --use_pycharm
use_docker [n]: n --use_docker
Select postgresql_version:
1 - 10.5
2 - 10.4
3 - 10.3
4 - 10.2
5 - 10.1
6 - 9.6
7 - 9.5
8 - 9.4
9 - 9.3
Choose from 1, 2, 3, 4, 5, 6, 7, 8, 9 [1]: 1 --postgresql_version
Select js_task_runner:
1 - None
2 - Gulp
Choose from 1, 2 [1]: --js_task_runner
custom_bootstrap_compilation [n]: --bootstrap_compilation
use_compressor [n]: y --use_compressor
use_celery [n]: y --use_celery
use_mailhog [n]: --use_mailhog
use_sentry [n]: --use_sentry
use_whitenoise [n]: --use_whitenoise
use_heroku [n]: --use_heroku
use_travisci [n]: --use_travisci
keep_local_envs_in_vcs [y]: n --keep_local_envs_in_vcs
debug [n]: y --debug
03.Pipenv虚拟环境
a.安装pipenv
python -m pip install --upgrade pip==21.0.1 && pip install pipenv -i https://pypi.python.org/simple
b.创建虚拟环境(python==3.7.2)
cd /root/answer && pipenv --python 3.7.2
Virtualenv location: /root/.local/share/virtualenvs/answer-tFUt2dYh/bin/python
c.创建项目环境
a.进入项目文件夹
[root@bigdata01 ~]# cd /root/answer
[root@bigdata01 workspace_pycharm]# ll
total 8
-rw-r--r--. 1 root root 192 Mar 18 19:39 Pipfile --替代原来的requirements.txt
-rw-r--r--. 1 root root 1082 Mar 18 19:34 Pipfile.lock --根据Pipfile生成的JSON格式依赖文件
b.修改镜像源,vi Pipfile
[[source]] --设置仓库地址
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
verify_ssl = true
name = "pypi"
[packages] --生产环境:项目依赖的包
django = "==2.1.7"
pytz = "==2018.9"
[dev-packages] --开发环境:项目依赖的包
[requires]
python_version = "3.7"
c.安装项目依赖(django==2.1.7、pytz==2018.9)
pipenv install django==2.1.7 pytz==2018.9 --生产环境:更新Pipfile.lock中hash值
pipenv install django==2.1.7 pytz==2018.9 --skip-lock --生产环境:跳过更新hash值
pipenv install django==2.1.7 pytz==2018.9 --skip-lock --dev --开发环境:跳过更新hash值
pip freeze > requirements.txt
pip install -r requirements.txt
pip unistall -r requirements.txt
pipenv install -r requirements/local.txt -i https://pypi.python.org/simple --skip-lock
pipenv install --安装Pipfile中[packages]的包
pipenv install --dev --安装Pipfile中[dev-packages]的包
pipenv install --system --使用系统的pip命令而不是虚拟环境命令
pipenv install --ignore-pipfile --忽略Pipfile直接安装Pipfile.lock
pipenv install --skip-lock --忽略Pipfile.lock直接安装Pipfile
pipenv uninstall --all --删除虚拟环境包,不改变Pipfile
pipenv uninstall --all-dev --删除虚拟环境包,并从Pipfile删除
d.启动项目(startapp -> DJANGO_APPS、THIRD_PARTY_APPS、LOCAL_APPS -> install、migrate、runserver)
pipenv run python manage.py makemigrations --针对某个字段进行迁移更新
pipenv run python manage.py migrate --针对某个字段进行重新生成
pipenv run python manage.py startapp news --生成应用
pipenv run python manage.py runserver 0.0.0.0:8000 --启动项目
pipenv run python manage.py createsuperuser --配置admin(halavah、QETU1234)
d.其他
a.管理虚拟环境
pipenv shell --进入虚拟环境
pipenv --rm --删除虚拟环境
exit --退出虚拟环境
python manage.py createsuperuser --进入pipenv创建管理
python manage.py runserver 192.168.2.128:8000 --进入pipenv启动项目
b.生成测试覆盖度报告
pipenv install coverage --skip-lock --安装测试工具
pipenv run coverage run manage.py test -v 2 --运行测试用例
pipenv run coverage html --生成测试报告
c.常见命令汇总
pipenv [OPTIONS] COMMAND [ARGS]
OPTIONS:
--where --显示项目文件所在路径
--venv --显示虚拟环境文件路径
--py --显示解释器的所在路径
--envs --显示虚拟环境的选项变量
--rm --删除当前使用的虚拟环境
--bare --最小化输出
--completion --完整输出
COMMAND:
check --检查安全漏洞
graph --显示当前依赖关系图信息
install --安全虚拟环境或第三方库
lock --锁定生成Pipfile.lock
open --在编辑器中查看一个库
run --在虚拟环境中运行命令
shell --进入虚拟环境
uninstall --卸载一个库
update --卸载当前包并安装最新版
04.Windows中PyCharm配置远程开发环境
a.创建新文件夹
mkdir D:\software_ware\workspace_pycharm\answer -> PyCharm打开answer新文件夹
b.Centos7远程项目
a.配置
a.打开配置
Tools -> Deployment -> Configuration -> + -> 类型SFTP -> 名称answer
b.Connection
SSH Configurations -> 192.168.2.128、root、4023615
Root Path:/root
Web server URL:http://192.168.2.128
Advanced:Send keep alive messages each -> 2 seconds
c.Mappings
Local path:D:\software_ware\workspace_pycharm\answer
Deployment path:/answer
Web path:http://192.168.2.128
d.Excluded Paths
无
b.选项
a.打开配置
Tools -> Deployment -> Options
b.修改配置
Exclude items by name:.svn;.cvs;.idea;.DS_Store;.git;.hg;*.hprof;*.pyc,【名称排除项】
Operations logging:Details,【操作记录】
Stop operation on the first error:不勾选,【在遇到第一个错误时停止操作】
Overwrite up-to-date files:勾选,【覆盖最新文件】
Preserve files timestamps:勾选,【保留文件时间戳】
Delete target items when source ones do not exist:勾选,【当源项不存在时删除目标项】
Create empty directories:勾选,【创建空目录】
Prompt when overwriting or deleting local items:勾选,【覆盖或删除本地项时提示】
Upload changed files automatically to the default server:勾选,【自动将更改的文件上传默认服务器】
Skip external changes:勾选,【跳过外部变更】
Delete remote files when local are deleted:勾选,【本地删除后删除远程文件】
Override default permissions on files:rwxrwxrwx(777),【重写文件的默认权限】
Override default permissions on folders:rwxrwxrwx(777),【重写文件夹的默认权限】
Warn when uploading over newer file:No,【在有更新的文件情况下进行上传时发出警告】
Show warning dialog on moving on Remote Host:勾选,【在远程主机上移动时显示警告对话框】
SFTP Advanced Options(IDE level setting):勾选,SFTP高级选项(IDE级别设置)
Add new host key to known_hosts:Always,将新主机键添加到known_hosts
Hash hosts in known_hosts file:勾选,known_hosts文件中的哈希主机
c.Python解释器
a.选择SSH解释器
Project: answer -> Python Interpreter -> Add Python Interpreter -> SSH Interpreter
SSH Configurations -> 192.168.2.128、root、4023615
b.移动本地IDE编辑器到服务器
Move
c.配置SSH解释器对应的服务器相关内容
Interpreter: /root/.local/share/virtualenvs/answer-tFUt2dYh/bin/python
Sync folders:D:/software_ware/workspace_pycharm/answer 对应 /root/answer
Automatically upload project files to the server
d.同步服务器代码、开启Django支持、项目启动配置
a.同步代码
answer -> Deployment -> Sync with Deployed to answer -> 手动Ctrl+S触发自动同步
b.开启Django支持
a.开启
Languages & Frameworks -> Enable Django Support
b.配置
Django project root:D:\software_ware\workspace_pycharm\answer
Settings:config\settings\local.py
Do not use Diango test runner:不勾选
Manage script:manage.py
Environment variables:无
Folder pattern to track files:无
c.项目启动配置
a.Host
Host:192.168.2.128:8000
Additional options:无
Run browser:勾选
Costom run command:不勾选
Test server:不勾选
No reload:不勾选
b.Environment:
Environment variables:PYTHONUNBUFFERED=1
Python interpreter:Interpreter: /root/.local/share/virtualenvs/answer-tFUt2dYh/bin/python
Interpreter options:无
Working directory:D:\software_ware\workspace_pycharm\answer
Path mappings:无
Add content roots to PYTHONPATH:勾选
Add source roots to PYTHONPATH:勾选
c.Logs
show console when a message is printed to standard output stream:勾选
show console when a message is printed to standard error stream:勾选
7.3 环境部署
01.部署项目
a.准备:关闭DEBUG模式,调整静态设置
vi /www/wwwroot/ProBlog/ProBlog/setting.py
DEBUG = False --关闭DEBUG模式
ALLOWED_HOSTS = ['*'] --允许所有人访问
STATIC_URL = '/static/'
# STATICFILES_DIRS = [ --STATICFILES_DIRS替换STATIC_ROOT
# os.path.join(BASE_DIR, 'static'),
# ]
STATIC_ROOT = os.path.join(BASE_DIR, "static") --STATICFILES_DIRS替换STATIC_ROOT
b.添加ProBlog项目
项目名称:ProBlog
路径:/www/wwwroot/ProBlog
Python版本:3.7.2
框架:django
启动方式:uwsgi
启动文件/文件夹:/www/wwwroot/ProBlog/ProBlog/wsgi.py
端口:8000
是否安装模块依赖:勾选
开机启动:勾选
c.修改ProBlog配置(/www/wwwroot/ProBlog/uswgi.ini)
[uwsgi]
# 配置和nginx连接的socket连接
socket = 127.0.0.1:8997
# 配置项目所在目录
chdir = /www/wwwroot/ProBlog/
# 配置wsgi接口模块文件路径,也就是wsgi.py这个文件所在的目录
wsgi-file = /www/wwwroot/ProBlog/ProBlog/wsgi.py
# 配置项目静态文件
static-map = /static=/www/wwwroot/ProBlog/static
# 配置存放主进程的进程号文件
pidfile = uwsgi.pid
# 配置dump日志记录
daemonize = uwsgi.log
# 配置启动管理主进程
master = True
# 配置启动的进程数
processes = 4
# 配置每个进程的线程数
threads = 2
# 最大数量的请求
max-requests = 1000
d.查看ProBlog虚拟环境,修复静态文件丢失(/www/wwwroot/ProBlog/ProBlog_venv)
a.进入虚拟环境
source /www/wwwroot/ProBlog/ProBlog_venv/bin/activate
b.依赖模块
python -m pip install --upgrade pip==21.0.1 --更新pip工具
pip freeze > requirements.txt --导出requirements.txt
pip install -r requirements.txt --安装requirements.txt
pip unistall -r requirements.txt --卸载requirements.txt
c.迁移数据库
python manage.py makemigrations --迁移数据库
python manage.py migrate --同步数据库
python manage.py dumpdata > django_orm.json --导出数据
python manage.py loaddata django_orm.json --导入数据
d.创建管理员、启动项目
python manage.py createsuperuser --进入pipenv创建超级管理员
python manage.py runserver 192.168.2.128:8000 --进入pipenv启动项目
e.修复静态文件丢失
python manage.py collectstatic --收集静态文件
02.部署网站
a.添加站点
域名:www.django.cn
备注:无
根目录:/www/wwwroot/ProBlog/
FTP:不创建
数据库:不创建
PHP版本:PHP-56
网站分类:默认分类
b.配置文件:新建访问规则
server
{
listen 80;
server_name www.diango.cn;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/ProBlog;
--------------------------------------------------------------------------
location / {
include uwsgi_params;
# 启动端口:同uwsgi.ini的socket连接端口
uwsgi_pass 127.0.0.1:8997;
# 启动文件:wsgi.py所在目录名+.wsgi
uwsgi_param UWSGI_SCRIPT ProBlog.wsgi;
# 项目路径:ProBlog
uwsgi_param UWSGI_CHDIR /www/wwwroot/ProBlog/;
}
location /static/ {
# 资源路径:static
alias /www/wwwroot/ProBlog/static/;
}
--------------------------------------------------------------------------
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
#error_page 404/404.html;
#SSL-END
#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
#error_page 404 /404.html;
#error_page 502 /502.html;
#ERROR-PAGE-END
#PHP-INFO-START PHP引用配置,可以注释或修改
include enable-php-56.conf;
#PHP-INFO-END
#REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
include /www/server/panel/vhost/rewrite/www.diango.cn.conf;
#REWRITE-END
#禁止访问的文件或目录
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)
{
return 404;
}
#一键申请SSL证书验证目录相关设置
location ~ \.well-known{
allow all;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
error_log /dev/null;
access_log off;
}
location ~ .*\.(js|css)?$
{
expires 12h;
error_log /dev/null;
access_log off;
}
access_log /www/wwwlogs/www.diango.cn.log;
error_log /www/wwwlogs/www.diango.cn.error.log;
}
c.反向代理:部署静态文件
location /static/ {
# 资源路径:static
root /www/wwwroot/ProBlog/static/;
break;
}
location /media/ {
# 资源路径:media
alias /www/wwwroot/ProBlog/media/;
}
03.运行项目
a.检查
是否收集静态文件至static
DEBUG是否关闭
宝塔面板-安全里是否放行了8000端口
云服务器安全组里是否放行了8000端口
静态文件路径是否有错误,包括在html里的引入要以/static/x x x x开头的绝对路径
b.运行
cd /root/ProBlog && pipenv shell
python manage.py runserver 192.168.2.128:8000
http://192.168.2.128:8000 daiyi 123456
http://192.168.2.128:8000/admin daiyi daiyi123456
7.4 开发项目
01.快速开始
a.网址
http://192.168.2.128:8000/accounts/login/
http://192.168.2.128:8000/accounts/signup/
b.位置
/root/.local/share/virtualenvs/answer-tFUt2dYh/bin/python
/root/.local/share/virtualenvs/zanhu-qCBWC76o/bin/python
c.密码
zanhu zanhu aFrRZACBBiKJMYdc
answer answer kBeZGFXBEz244mAp