——始于2.10
Django总体流程框架图
(图片出处: Django框架全面讲解 – 小丑进场 – 博客园)
Django本身同样也是MVC架构。但是在Django中,控制器接受用户输入的部分由框架自行处理,所以 Django 里更关注的是模型(Model)、模板(Template)和视图(Views),称为 MTV模式。
M 代表模型(Model),即数据存取层。 该层处理与数据相关的所有事务——如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等。
T 代表模板(Template),即表现层。 该层处理与表现相关的决定—— 如何在页面或其他类型文档中进行显示。
V 代表视图(View),即业务逻辑层。 该层包含存取模型及调取恰当模板的相关逻辑——可以看作为模型与模板之间的桥梁。
下图为django基本框架:
Django 配置和基础命令
1. 创建一个新的 Django 项目
在当前目录下创建一个名为 project_name
的 Django 项目,使用以下命令:
django-admin startproject project_name
执行完毕后,会生成一个包含以下结构的项目:
project_name/
manage.py # 项目管理脚本,用于执行 Django 的各种命令
project_name/ # 项目目录,包含项目的设置、URL 配置等
__init__.py # 一个空文件,标识这是一个 Python 包
settings.py # 项目的配置文件
urls.py # URL 路由配置
asgi.py # ASGI 配置文件(用于异步服务器)
wsgi.py # WSGI 配置文件(用于部署)
2. 启动本地开发服务器
进入项目的根目录后,运行以下命令来启动服务器:
python manage.py runserver
默认情况下,Django 服务器会运行在 http://127.0.0.1:8000/
上。
如果需要指定 IP 和端口,可以这样运行:
python manage.py runserver 0.0.0.0:8080
上述命令会将服务器绑定到所有网络接口(0.0.0.0
)并使用端口 8080
。
3. 创建新的 App 应用
在项目下创建一个名为 app_name
的新的 App 应用:
python manage.py startapp app_name
Django 中的 App 是项目的一个功能模块,例如博客、用户管理等。运行该命令后,会在项目目录中生成以下结构的 App:
app_name/
migrations/ # 用于存放数据库迁移文件
__init__.py
__init__.py # 标识当前目录为 Python 包
admin.py # 用于在 Django 管理后台中注册模型
apps.py # App 的配置文件
models.py # 定义数据库模型
tests.py # 编写测试用例
views.py # 处理业务逻辑的视图函数
需要在项目的 settings.py
文件中注册 App,比如添加以下内容:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app_name', # 添加你的 App
]
4. 数据库操作命令
(1) 同步数据库
在 Django 1.7 及以上版本中,数据库同步分为两步操作:
- 创建迁移文件
每当修改模型文件(models.py
)后,需要运行以下命令来生成迁移文件:
python manage.py makemigrations
该命令会显示并记录所有模型的改动,例如新增字段或表。
- 迁移数据库
使用以下命令将迁移文件的更改应用到数据库中:
python manage.py migrate
(2) 创建超级管理员
运行以下命令创建管理后台的超级用户:
python manage.py createsuperuser
按照提示输入用户名、电子邮件和密码即可完成创建。
(3) 数据库交互
通过以下命令进入数据库的交互命令行(通常用于调试):
python manage.py dbshell
在此模式下,你可以直接运行 SQL 查询。
数据库连接配置
Django 默认使用 SQLite 数据库,但你也可以配置其他类型的数据库,比如 MySQL、PostgreSQL 或 Oracle。
1. SQLite 默认配置
在 settings.py
中默认的 SQLite 数据库配置如下:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # 使用 SQLite 数据库
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # 数据库文件的路径
}
}
SQLite 是 Django 内部的默认数据库,适合开发阶段使用,无需额外安装数据库服务。
2. MySQL 配置
如果需要使用 MySQL 数据库,首先确保已安装 MySQL 和 pymysql
:
pip install pymysql
然后在 settings.py
中设置如下:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 使用 MySQL 数据库引擎
'NAME': 'dbname', # 数据库名称(确保数据库使用 UTF-8 编码)
'USER': 'your_username', # 数据库用户名
'PASSWORD': 'your_password', # 数据库用户密码
'HOST': '127.0.0.1', # 数据库主机(本地则为 '127.0.0.1' 或留空)
'PORT': '3306', # 数据库端口(MySQL 默认端口为 3306)
}
}
此外,由于 Django 使用的是 MySQLdb
模块,而在 Python 3 中没有官方支持的 MySQLdb
,所以需要使用 pymysql
代替。
在项目的 __init__.py
文件中添加如下代码:
import pymysql
pymysql.install_as_MySQLdb()
3. PostgreSQL 配置
安装 PostgreSQL 和相关驱动:
pip install psycopg2
然后在 settings.py
中设置如下:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql', # 使用 PostgreSQL 数据库引擎
'NAME': 'app_data', # 数据库名称
'USER': 'your_username', # 数据库用户名
'PASSWORD': 'your_password', # 数据库用户密码
'HOST': '127.0.0.1', # 数据库主机地址
'PORT': '5432', # 数据库端口(PostgreSQL 默认端口为 5432)
}
}
4. Oracle 配置
如果需要使用 Oracle 数据库,首先安装 Oracle 驱动包 cx_Oracle
:
pip install cx_Oracle
然后在 settings.py
中设置如下:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.oracle', # 使用 Oracle 数据库引擎
'NAME': 'xe', # 数据库名称或服务名
'USER': 'a_user', # 数据库用户名
'PASSWORD': 'a_password', # 数据库用户密码
'HOST': '127.0.0.1', # 数据库主机
'PORT': '1521', # 数据库端口(Oracle 默认端口为 1521)
}
}
静态文件配置
1. 静态文件的概念
静态文件指的是前端页面中使用的 CSS、JavaScript 文件、图片等资源。为了方便管理这些文件,可以在项目中设置一个 static
文件夹。
2. 配置方法
在项目根目录下创建一个 static
文件夹,然后在 settings.py
中添加以下配置:
STATIC_URL = '/static/' # 访问静态文件的 URL 前缀
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'), # 告诉 Django 静态文件所在的目录
)
3. 在模板中使用静态文件
在 HTML 模板中引入静态文件时,需要使用 {% static %}
标签。例如:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<script src="{% static 'js/jquery-1.12.4.js' %}"></script>
</head>
<body>
<h1>Welcome to Django!</h1>
</body>
</html>
上述代码假设你的 static
目录下有 css/style.css
和 js/jquery-1.12.4.js
文件。
Django 路由系统
URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL模式以及要为该URL模式调用的视图函数之间的映射表;你就是以这种方式告诉Django,对于这个URL调用这段代码,对于那个URL调用那段代码。URL的加载是从配置文件中开始。下图为urls.py:
一个正则表达式路由例子:
一个更为常见的路由例子:
Django 二级路由
为什么需要二级路由?
随着项目功能的逐步扩展,urls.py
文件中的 URL 路由规则会变得越来越多。如果所有路由都写在主项目的 urls.py
文件中,不仅难以管理,还会显得冗杂和繁琐。为了解决这一问题,Django 提供了 include
函数,用于拆分路由配置,将不同模块的路由分配到其对应的 App 中,从而形成二级路由结构。
代码示例
一级路由配置(项目根 urls.py)
from django.urls import path, include # 导入path和include函数
urlpatterns = [
# 将所有以 "blog/" 开头的 URL 请求交由 blog 应用的二级路由处理
path('blog/', include('blog.urls')),
]
二级路由配置(App 内 urls.py,例如 blog/urls.py)
from django.urls import path # 导入path函数
from . import views # 导入当前 app 的 views 模块
urlpatterns = [
# 访问 "blog/" 时调用 views.blog_home
path('', views.blog_home, name='blog_home'),
# 访问 "blog/post/<post_id>/" 时调用 views.blog_post,并传递 post_id
path('post/<int:post_id>/', views.blog_post, name='blog_post'),
# 访问 "blog/author/<author_name>/" 时调用 views.blog_author,并传递 author_name
path('author/<str:author_name>/', views.blog_author, name='blog_author'),
]
视图函数(App 内 views.py,例如 blog/views.py)
from django.http import HttpResponse # 导入HttpResponse函数,用于返回响应
# 处理 "blog/" 的视图函数
def blog_home(request):
return HttpResponse("Welcome to the Blog Home Page!")
# 处理 "blog/post/<post_id>/" 的视图函数
def blog_post(request, post_id):
return HttpResponse(f"Viewing Post {post_id}")
# 处理 "blog/author/<author_name>/" 的视图函数
def blog_author(request, author_name):
return HttpResponse(f"Author: {author_name}")
运行逻辑
- 当访问
http://127.0.0.1:8000/blog/
时,匹配到主项目的urls.py
中的path('blog/', include('blog.urls'))
,然后进入blog
应用的路由规则。 - 如果访问的是
http://127.0.0.1:8000/blog/post/1/
,post/1/
会匹配到二级路由中的path('post/<int:post_id>/')
,并调用views.blog_post
。 - 如果访问的是
http://127.0.0.1:8000/blog/author/john/
,会匹配到path('author/<str:author_name>/')
,并调用views.blog_author
。
Django Views(视图函数)
在 Django 中,视图函数是处理客户端请求的核心逻辑。每个视图函数都会接收一个 HttpRequest
对象,并返回一个 HttpResponse
对象。
1. HttpRequest 对象
HttpRequest
是 Django 自动生成的请求对象,包含所有与 HTTP 请求相关的数据。
常见属性:
path
请求的 URL 路径,不包括域名部分。
示例:访问http://127.0.0.1:8000/blog/post/1/
,req.path
的值为/blog/post/1/
。method
请求的 HTTP 方法(如GET
或POST
)。可通过if req.method == "POST":
判断类型。GET
包含 URL 中的所有查询参数的类字典对象。
示例:访问http://127.0.0.1:8000/search?q=django
,req.GET['q']
的值为django
。POST
包含表单提交的所有数据。只能用于接收请求体中的表单数据。FILES
包含上传文件的数据。每个文件以<input type="file" name="...">
的name
属性为键。COOKIES
以字典形式存储请求中的所有 Cookie。user
当前登录的用户对象。如果用户未登录,默认为AnonymousUser
对象。
示例:req.user.is_authenticated
可判断用户是否已登录。META
包含所有可用的 HTTP 头信息。常见键值:
REMOTE_ADDR
: 客户端 IP 地址HTTP_USER_AGENT
: 用户代理字符串(浏览器信息)HTTP_REFERER
: 来源页面的 URL
2. HttpResponse 对象
HttpResponse
是 Django 自动生成的响应对象,用于将处理结果返回给客户端。
视图函数示例:
from django.http import HttpResponse
from django.shortcuts import render, redirect
def index(request):
# 示例 1: 返回简单的字符串
return HttpResponse("Hello, Django!")
# 示例 2: 渲染模板
# return render(request, "index.html", {"title": "Welcome to Django!"})
# 示例 3: 重定向到外部页面
# return redirect("http://www.baidu.com")
3. render()
方法
render
是 Django 提供的快捷方法,用于将请求对象、模板和上下文结合,生成响应页面返回给用户。
示例:
假设 views.py
中定义了以下视图函数:
from django.shortcuts import render
def my_view(request):
context = {
'title': 'My Django App',
'description': 'This is an example page rendered using Django.',
}
return render(request, 'myapp/index.html', context)
对应的模板文件 myapp/index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</body>
</html>
访问对应 URL 时,页面会显示如下内容:
<h1>My Django App</h1>
<p>This is an example page rendered using Django.</p>
小知识点:
- 字典取值方式
在 Django 模板语言中,字典的取值是通过点操作符,而不是方括号([]
)。
例如:context['title']
在模板中使用时应写为{{ title }}
。
通过这些内容,您可以更清楚地理解 Django 的二级路由、视图函数以及模板渲染的核心概念。这种结构化的拆分也能帮助开发者更高效地组织代码。
Django 模型 (Models)
什么是模型?
在 Django 中,模型就是数据库表的抽象表示。每个模型类对应一张数据库表,模型的字段(类属性)对应数据库表中的列。通过模型类,开发者能够以面向对象的方式操作数据库,而无需直接编写 SQL 语句。
模型的基本定义
以下是一个 UserInfo
模型的定义示例:
代码:模型定义
# 导入 Django 的模型模块
from django.db import models
class UserInfo(models.Model):
"""
UserInfo 模型类,用于表示用户信息。
"""
# 用户名字段
name = models.CharField(max_length=30, verbose_name="姓名")
# 最大长度为 30 字符的字符串字段
# 用户邮箱字段
email = models.EmailField(verbose_name="邮箱")
# EmailField 会验证邮箱格式,且默认最大长度为 254 字符
# 用户备注字段
memo = models.TextField(verbose_name="备注")
# TextField 不限制长度,适合存储大段文本
def __str__(self):
"""
定义模型对象的字符串表示(对象转字符串时会调用)。
"""
return self.name
class Meta:
"""
模型的元数据,提供控制模型行为的选项。
"""
db_table = "userinfo" # 自定义表名为 "userinfo"
verbose_name = "用户信息" # 模型单数名称(后台显示)
verbose_name_plural = "用户信息列表" # 模型复数名称
ordering = ['name'] # 按姓名升序排列
模型对应的数据库表
上述模型会自动生成如下数据库表(使用 SQLite 时为例):
CREATE TABLE userinfo (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- 自动生成的主键
name VARCHAR(30) NOT NULL, -- 用户名字段
email VARCHAR(254) NOT NULL, -- 用户邮箱字段
memo TEXT -- 用户备注字段
);
id
:默认的主键字段,Django 自动生成。name
、email
和memo
:对应模型中定义的字段。
模型的基本使用
创建数据
from myapp.models import UserInfo
# 方法 1:直接创建并保存
user = UserInfo(name="John", email="[email protected]", memo="Test user")
user.save()
# 方法 2:通过 create 方法
user = UserInfo.objects.create(name="Jane", email="[email protected]", memo="Another user")
查询数据
# 查询单条数据
user = UserInfo.objects.get(id=1) # 根据主键查询
# 查询所有数据
users = UserInfo.objects.all()
# 筛选数据
users = UserInfo.objects.filter(name="John") # 查询 name 为 "John" 的用户
# 排序
users = UserInfo.objects.all().order_by('name') # 按 name 升序
更新数据
# 方法 1:单行更新
user = UserInfo.objects.get(id=1)
user.name = "Updated Name"
user.save()
# 方法 2:批量更新
UserInfo.objects.filter(name="John").update(email="[email protected]")
删除数据
# 删除单条数据
user = UserInfo.objects.get(id=1)
user.delete()
# 删除多条数据
UserInfo.objects.filter(name="John").delete()
字段参数详解
null=True
数据库字段允许为空(对应数据库中的NULL
值)。
示例:email = models.CharField(max_length=100, null=True)
blank=True
后台表单允许此字段为空。
示例:email = models.CharField(max_length=100, null=True, blank=True)
primary_key=True
将该字段设置为主键。默认情况下,Django 会自动生成一个名为id
的主键。auto_now
和auto_now_add
auto_now=True
:每次保存时,自动更新为当前时间。auto_now_add=True
:只在首次创建时,设置为当前时间。
示例:created_at = models.DateTimeField(auto_now_add=True)
choices
限制字段值的选项范围(类似枚举)。
示例:
GENDER_CHOICES = (
('M', 'Male'),
('F', 'Female'),
)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
default
设置字段的默认值。
示例:status = models.CharField(max_length=10, default='active')
unique=True
确保字段值的唯一性。
示例:email = models.EmailField(unique=True)
db_index=True
为字段创建数据库索引以提高查询性能。verbose_name
设置字段在管理后台中的显示名称。help_text
在后台管理表单中,提供帮助提示信息。
常用字段类型
CharField
:字符串字段,需要设置max_length
参数。TextField
:长文本字段,无需设置max_length
。IntegerField
:整数字段。BooleanField
:布尔值字段,默认值为True/False
。EmailField
:邮箱字段,自动验证邮箱格式。DateField
和DateTimeField
:日期和日期时间字段。ImageField
:用于上传图片,需设置upload_to
参数指定上传路径。ForeignKey
:定义一对多关系。ManyToManyField
:定义多对多关系。OneToOneField
:定义一对一关系。
连表关系
一对多
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
# `on_delete=models.CASCADE` 表示当关联的 Author 删除时,相关联的 Book 也会被删除。
多对多
class Student(models.Model):
name = models.CharField(max_length=100)
class Course(models.Model):
title = models.CharField(max_length=100)
students = models.ManyToManyField(Student)
一对一
class User(models.Model):
username = models.CharField(max_length=100)
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField()
增删改查示例
添加数据
Author.objects.create(name="John Doe")
Book.objects.create(title="Book 1", author_id=1)
查询数据
# 获取作者的所有书籍
author = Author.objects.get(id=1)
books = author.book_set.all()
# 获取书籍的作者
book = Book.objects.get(id=1)
author = book.author
删除数据
Author.objects.filter(name="John Doe").delete()
更新数据
Author.objects.filter(name="John Doe").update(name="Jane Doe")
复杂查询
- 条件查询:
Book.objects.filter(title__contains="Django")
- 范围查询:
Book.objects.filter(id__range=(1, 5))
- 排序:
Book.objects.all().order_by('-title') # 按 title 降序
- 聚合:
from django.db.models import Count
Book.objects.values('author').annotate(book_count=Count('id'))
————————————— 2.12 分割线 ——————————————————
Django 文件上传及自定义上传实现
文件上传是 Django 开发中常见的功能场景,以下是自定义文件上传的详细实现及不同形式的实例(普通表单上传和 Ajax 上传)。
自定义文件上传实现(后端)
视图代码
from django.shortcuts import render
from django.http import HttpResponse
def upload_file(request):
"""
自定义处理文件上传。
"""
if request.method == "POST": # 确保只有 POST 请求时执行上传
# 获取上传的文件对象
obj = request.FILES.get('fafafa') # 'fafafa' 是前端表单中 <input type="file" name="fafafa"> 的 name 属性
if obj:
# 打开文件并以二进制写入模式保存
with open(obj.name, 'wb') as f:
# 分块读取文件内容,避免一次性读取大文件造成内存溢出
for chunk in obj.chunks():
f.write(chunk)
return HttpResponse(f"File {obj.name} uploaded successfully!") # 返回成功消息
else:
return HttpResponse("No file selected!")
return render(request, 'file.html') # 渲染上传页面
文件上传页面(HTML 表单)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>File Upload</title>
</head>
<body>
<h1>Upload File</h1>
<form method="post" action="/upload/" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="fafafa" id="fileInput">
<button type="submit">Upload</button>
</form>
</body>
</html>
基于 Django Form 的文件上传
后端代码
Form 定义
使用 Django 的 forms.Form
提供的 FileField
,可以更方便地进行文件上传的表单验证。
from django import forms
class FileForm(forms.Form):
ExcelFile = forms.FileField(label="上传文件")
模型定义
如果需要将上传的文件存储到数据库中,可以通过模型的 FileField
指定存储路径。
from django.db import models
class UploadFile(models.Model):
userid = models.CharField(max_length=30) # 用户 ID
file = models.FileField(upload_to='./upload/') # 文件存储路径(相对于 MEDIA_ROOT)
date = models.DateTimeField(auto_now_add=True) # 文件上传时间
视图定义
在视图中处理文件上传和表单验证:
from django.shortcuts import render
from .forms import FileForm
from .models import UploadFile
def upload_file_form(request):
"""
基于 Django Form 的文件上传视图。
"""
if request.method == "POST":
# 创建表单实例并绑定 POST 数据和文件
uf = FileForm(request.POST, request.FILES)
if uf.is_valid():
# 保存到模型
upload = UploadFile()
upload.userid = 1 # 示例用户 ID
upload.file = uf.cleaned_data['ExcelFile'] # 表单验证后的文件对象
upload.save()
# 返回上传文件的路径
return HttpResponse(f"File uploaded: {upload.file}")
else:
uf = FileForm() # 初始化空表单
return render(request, 'upload_form.html', {'form': uf})
上传页面(HTML 表单)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>File Upload</title>
</head>
<body>
<h1>Upload File</h1>
<form method="post" action="/upload_form/" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Upload</button>
</form>
</body>
</html>
基于 Ajax 的文件上传
后端代码
视图可以重用 upload_file_form
中的逻辑,只需确保返回 JSON 响应。
from django.http import JsonResponse
def upload_file_ajax(request):
"""
Ajax 文件上传视图。
"""
if request.method == "POST":
# 获取上传的文件对象
obj = request.FILES.get('ExcelFile')
if obj:
# 模拟保存文件
with open(obj.name, 'wb') as f:
for chunk in obj.chunks():
f.write(chunk)
return JsonResponse({'status': 'success', 'message': f"File {obj.name} uploaded successfully!"})
else:
return JsonResponse({'status': 'error', 'message': 'No file selected!'})
return JsonResponse({'status': 'error', 'message': 'Invalid request method!'})
Ajax 上传页面(HTML 和 JavaScript)
HTML 页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ajax File Upload</title>
</head>
<body>
<h1>Ajax File Upload</h1>
<input type="file" id="fileInput">
<button id="submitButton">Upload</button>
<div id="response"></div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/static/js/upload.js"></script>
</body>
</html>
JavaScript 文件(upload.js
)
$(document).ready(function () {
$('#submitButton').on('click', function () {
// 获取文件对象
var file = $('#fileInput')[0].files[0];
if (!file) {
$('#response').text('No file selected!');
return;
}
// 创建 FormData 对象并附加文件
var formData = new FormData();
formData.append('ExcelFile', file);
// Ajax 请求
$.ajax({
url: '/upload_ajax/', // 后端处理 URL
type: 'POST',
data: formData,
processData: false, // 不处理文件数据
contentType: false, // 不设置默认 Content-Type
headers: {
'X-CSRFToken': getCookie('csrftoken') // Django 的 CSRF 保护
},
success: function (response) {
$('#response').html('<p>' + response.message + '</p>');
},
error: function () {
$('#response').text('Error occurred while uploading!');
}
});
});
});
// 获取 CSRF Token
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
对比:
普通表单上传
- 适合简单的文件上传。
- 上传后刷新整个页面,用户体验稍差。
基于 Django Form 的上传
- 能够使用 Django 提供的表单验证功能,更安全、更方便扩展。
- 结合模型可以直接将文件保存到数据库。
基于 Ajax 的上传
- 无需刷新页面,用户体验更佳。
- 适合需要实时响应和多文件上传的场景。
Django 中间件
在django中,中间件其实就是一个类,在请求到来/结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法。
在django项目的settings模块中,有一个 MIDDLEWARE ( _CLASSES ) 变量,其中每一个元素就是一个中间件。
中间件运行流程示意图:
(图片出处:Django框架全面讲解 – 小丑进场 – 博客园)
自定义中间类
Form
Django Form 的作用
- 生成 HTML 表单:
- 自动生成表单标签,减少手动编写 HTML 的工作量。
- 验证用户输入:
- 提供字段验证功能,确保用户输入符合预期(如数字、邮箱格式等)。
- 支持自定义验证规则。
Form 示例
代码实现:
# -*- coding:utf-8 -*-
import re
from django import forms
from django.core.exceptions import ValidationError
# 自定义字段验证函数
def mobile_validate(value):
"""
验证手机号码格式
"""
mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
if not mobile_re.match(value):
raise ValidationError('手机号码格式错误')
# 表单类定义
class PublishForm(forms.Form):
"""
发布表单类,定义用户输入的字段及验证方式。
"""
user_type_choice = (
(0, '普通用户'),
(1, '高级用户'),
)
user_type = forms.IntegerField(
widget=forms.widgets.Select(choices=user_type_choice, attrs={'class': "form-control"})
)
title = forms.CharField(
max_length=20,
min_length=5,
error_messages={
'required': '标题不能为空',
'min_length': '标题最少为5个字符',
'max_length': '标题最多为20个字符'
},
widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': '标题5-20个字符'})
)
memo = forms.CharField(
required=False,
max_length=256,
widget=forms.widgets.Textarea(attrs={'class': "form-control no-radius", 'placeholder': '详细描述', 'rows': 3})
)
phone = forms.CharField(
validators=[mobile_validate],
error_messages={'required': '手机不能为空'},
widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': '手机号码'})
)
email = forms.EmailField(
required=False,
error_messages={'required': '邮箱不能为空', 'invalid': '邮箱格式错误'},
widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': '邮箱'})
)
处理用户提交的表单
视图实现:
import json
from django.http import HttpResponse
from .forms import PublishForm
def publish(request):
"""
处理用户发布表单的视图
"""
ret = {'status': False, 'data': '', 'error': '', 'summary': ''}
if request.method == 'POST':
request_form = PublishForm(request.POST) # 绑定用户提交的数据
if request_form.is_valid():
# 表单验证通过
request_dict = request_form.cleaned_data # 获取清洗后的数据
print(request_dict)
ret['status'] = True
else:
# 表单验证失败
error_msg = request_form.errors.as_json() # 获取错误信息
ret['error'] = json.loads(error_msg)
return HttpResponse(json.dumps(ret))
自动生成 HTML 表单
Django Form 可以自动生成 HTML 表单,同时支持添加样式和验证规则。
表单类:
from django import forms
from app01 import models
class Form1(forms.Form):
user = forms.CharField(
widget=forms.TextInput(attrs={'class': 'c1'}), # 添加 HTML 属性
error_messages={'required': '用户名不能为空'}, # 自定义错误提示
)
pwd = forms.CharField(max_length=4, min_length=2)
email = forms.EmailField(error_messages={'required': '邮箱不能为空', 'invalid': '邮箱格式错误'})
memo = forms.CharField(widget=forms.Textarea())
user_type_choice = models.BookType.objects.values_list("id", "caption")
book_type = forms.CharField(widget=forms.widgets.Select(choices=user_type_choice)) # 动态选项
动态联动数据库选项(解决实时更新问题):
def __init__(self, *args, **kwargs):
super(Form1, self).__init__(*args, **kwargs)
self.fields['book_type'] = forms.CharField(
widget=forms.widgets.Select(choices=models.BookType.objects.values_list("id", "caption"))
)
ModelForm
ModelForm
是 Django 提供的扩展,可以直接从数据库模型自动生成表单,减少重复字段的定义。
示例:
from django import forms
from app01.models import Admin
class AdminModelForm(forms.ModelForm):
"""
ModelForm 示例
"""
class Meta:
model = Admin # 绑定的模型
fields = ['username', 'email'] # 指定生成表单的字段
widgets = {
'email': forms.PasswordInput(attrs={'class': "form-control"}), # 自定义字段样式
}
认证系统(auth)
Django 提供了内置的认证系统(auth
模块),用于管理用户认证、用户权限、用户组和会话。它与 Django 管理后台(admin
)集成得很好,可以快速实现用户管理功能。
1. 启用 auth 模块
auth
模块默认启用,无需手动添加。如果没有启用,请确保 INSTALLED_APPS
中包含以下内容:
INSTALLED_APPS = [
'django.contrib.auth', # 认证系统
'django.contrib.contenttypes', # 依赖组件
...
]
2. User
模型
Django 内置了一个用户模型 User
,其在数据库中的表名为 auth_user
。常用字段和功能包括:
字段名 | 描述 |
---|---|
username | 用户名,必须唯一 |
password | 密码(存储的是加密后的 Hash 值) |
email | 电子邮箱 |
is_active | 用户是否有效(软删除标志) |
is_staff | 是否有后台管理权限 |
is_superuser | 是否是超级管理员 |
last_login | 最后一次登录时间 |
date_joined | 用户创建时间 |
3. 新建用户
Django 提供了方便的 API 来创建用户:
from django.contrib.auth.models import User
# 创建新用户
user = User.objects.create_user(username="john", email="[email protected]", password="password123")
user.save()
注意:
- 密码不会以明文存储在数据库中,而是存储为 Hash 值。
4. 用户认证
可以通过 authenticate
函数验证用户的身份:
from django.contrib.auth import authenticate
user = authenticate(username="john", password="password123")
if user is not None:
print("认证成功,用户对象为:", user)
else:
print("认证失败,用户名或密码错误")
注意:
authenticate
方法只验证用户名和密码是否正确,不检查is_active
标志位。
5. 修改密码
用户可以通过以下方式修改密码:
user.set_password("new_password")
user.save()
示例:只有认证后的用户才可以修改密码:
from django.contrib.auth import authenticate
user = authenticate(username="john", password="old_password")
if user is not None:
user.set_password("new_password")
user.save()
6. 登录
登录会在 Session 中设置用户的状态:
from django.contrib.auth import login
user = authenticate(username="john", password="password123")
if user is not None:
if user.is_active: # 检查用户是否有效
login(request, user) # 将用户登录到当前请求
print("用户已登录")
7. 退出登录
退出登录会清除 Session 中的用户信息:
from django.contrib.auth import logout
def logout_view(request):
logout(request)
return HttpResponse("您已退出登录")
8. 限制访问未登录用户
通过 @login_required
装饰器,可以强制要求用户登录后才能访问某个视图。
from django.contrib.auth.decorators import login_required
@login_required(login_url="/accounts/login/") # 未登录用户被重定向到此路径
def userinfo(request):
return HttpResponse("这是用户信息页面")
在 settings.py
中可以设置默认登录 URL:
LOGIN_URL = "/accounts/login/" # 全局的未登录重定向路径
跨站请求伪造(CSRF)
CSRF(Cross-Site Request Forgery)是一种攻击方式,通过伪造用户身份发送恶意请求。Django 默认启用了 CSRF 防护,通过中间件实现。
1. 全局启用 CSRF
确保 MIDDLEWARE
中包含以下内容:
MIDDLEWARE = [
...,
'django.middleware.csrf.CsrfViewMiddleware', # 启用 CSRF 中间件
...
]
2. 局部设置 CSRF
- 强制启用 CSRF: 使用
@csrf_protect
装饰器强制启用 CSRF 防护,即使全局 CSRF 防护未启用。
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def my_view(request):
...
- 禁用 CSRF: 使用
@csrf_exempt
装饰器禁用单个视图的 CSRF 防护。
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def my_view(request):
...
3. 在模板中使用 CSRF Token
在 HTML 表单中添加 {% csrf_token %}
:
<form method="post" action="/submit/">
{% csrf_token %}
<input type="text" name="name">
<button type="submit">提交</button>
</form>
4. Ajax 请求中的 CSRF
对于 Ajax 请求,需要手动添加 CSRF Token 到请求头中。
var csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
// 示例 Ajax 请求
$.ajax({
url: "/app01/test/",
type: "POST",
data: {id: 1},
success: function(data) {
console.log(data);
}
});
分页
分页是一个常见的功能,Django 提供了内置的分页类,也支持自定义分页逻辑。
1. 内置分页类
视图文件:
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
items = list(range(1, 101)) # 示例数据
def index(request):
page_number = request.GET.get('page', 1) # 获取当前页码,默认第一页
paginator = Paginator(items, 10) # 每页显示 10 条数据
try:
page = paginator.page(page_number)
except PageNotAnInteger:
page = paginator.page(1) # 如果页码不是整数,显示第一页
except EmptyPage:
page = paginator.page(paginator.num_pages) # 如果超出页码范围,显示最后一页
return render(request, "index.html", {"page": page})
模板文件:
<ul>
{% for item in page %}
<li>{{ item }}</li>
{% endfor %}
</ul>
<div class="pagination">
{% if page.has_previous %}
<a href="?page={{ page.previous_page_number }}">上一页</a>
{% endif %}
<span>第 {{ page.number }} 页,共 {{ page.paginator.num_pages }} 页</span>
{% if page.has_next %}
<a href="?page={{ page.next_page_number }}">下一页</a>
{% endif %}
</div>
2. 自定义分页
自定义分页可以通过计算当前页码和数据范围实现。
核心逻辑:
- 计算数据的起始索引和结束索引。
- 生成分页控件的 HTML。
示例代码已较详细,这里不重复。
Cookie
Django 支持操作客户端 Cookie,用于存储小型会话数据。
1. 获取 Cookie
# 获取 Cookie
value = request.COOKIES.get("key", "默认值")
2. 设置 Cookie
response = HttpResponse("设置 Cookie 示例")
response.set_cookie("key", "value", max_age=3600) # 设置有效期为 1 小时
return response
加密 Cookie:
response.set_signed_cookie("key", "value", salt="加密盐")
Session
Django 默认支持 Session 功能,用于在请求之间存储用户的会话数据。Django 内置 5 种类型的 Session 存储方式,开发者可以根据需求选择合适的存储方式。
1. 数据库 Session (默认存储)
Django 默认通过数据库 (表 django_session
) 存储 Session 数据。
a. 配置
在 settings.py
中进行如下配置:
# 默认设置,无需修改即可使用
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # Session 引擎,存储到数据库
SESSION_COOKIE_NAME = "sessionid" # 浏览器中保存 Session Cookie 的名称,默认是 "sessionid"
SESSION_COOKIE_PATH = "/" # Session Cookie 的保存路径,默认是根路径
SESSION_COOKIE_DOMAIN = None # Session Cookie 的域名,默认是 None(即当前域名)
SESSION_COOKIE_SECURE = False # 如果为 True,则通过 HTTPS 传输 Cookie
SESSION_COOKIE_HTTPONLY = True # 如果为 True,则客户端 JavaScript 无法访问 Cookie
SESSION_COOKIE_AGE = 1209600 # Cookie 的过期时间(单位:秒),默认是两周(14 天)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 如果为 True,则关闭浏览器时 Session 过期
SESSION_SAVE_EVERY_REQUEST = False # 如果为 True,每次请求都会保存 Session
b. 使用
在视图函数中操作 Session:
def index(request):
# 设置 Session 数据
request.session['key'] = 'value' # 设置键值对
request.session.setdefault('key', 'default_value') # 如果键不存在,则设置默认值
# 获取 Session 数据
value = request.session.get('key', None) # 获取键的值,若不存在返回 None
# 删除 Session 数据
del request.session['key'] # 删除指定 Session 数据
request.session.flush() # 清空当前用户的所有 Session 数据
# 遍历 Session 数据
keys = request.session.keys() # 获取所有键
values = request.session.values() # 获取所有值
items = request.session.items() # 获取键值对
# 获取当前用户的 session_key
key = request.session.session_key
# 检查指定 session_key 是否存在
exists = request.session.exists('session_key')
return HttpResponse("Session 操作完成")
2. 缓存 Session
Session 数据存储在缓存中,性能比数据库存储更高。
a. 配置
在 settings.py
中进行如下配置:
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 使用缓存存储 Session
SESSION_CACHE_ALIAS = 'default' # 指定缓存别名
其他配置项与数据库 Session 相同。
b. 使用
与数据库 Session 操作完全相同。
3. 文件 Session
Session 数据存储在文件中。
a. 配置
在 settings.py
中进行如下配置:
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 使用文件存储 Session
SESSION_FILE_PATH = None # 文件存储路径,默认使用 `tempfile.gettempdir()` 获取临时文件目录
b. 使用
与其他 Session 操作完全相同。
4. 缓存 + 数据库 Session
结合缓存和数据库,缓存用于加速访问,数据库用于数据持久化。
a. 配置
在 settings.py
中进行如下配置:
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 缓存 + 数据库存储 Session
b. 使用
与其他 Session 操作完全相同。
5. 加密 Cookie Session
Session 数据存储在加密的 Cookie 中。此方式避免了服务器端存储,但性能上可能稍差。
a. 配置
在 settings.py
中进行如下配置:
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 使用加密 Cookie 存储 Session
b. 使用
与其他 Session 操作完全相同。
缓存
缓存用于提高动态网站的性能,避免频繁查询数据库。当访问量较大时,缓存可以显著提升响应速度。
Django 提供了 6 种缓存方式:开发调试用缓存、内存缓存、文件缓存、数据库缓存、Memcached (python-memcached) 和 Memcached (pylibmc)。
1. 配置缓存
以下是每种缓存方式的配置:
a. 开发调试用缓存
不实际存储任何缓存数据,适用于调试阶段。
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache', # 不存储缓存
}
}
b. 内存缓存
缓存数据存储在进程内存中,性能高,但数据无法跨进程共享。
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake', # 内存缓存的标识
}
}
c. 文件缓存
缓存数据存储在文件中。
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache', # 文件存储路径
}
}
d. 数据库缓存
缓存数据存储在数据库中。
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table', # 数据库表名
}
}
创建缓存表:
python manage.py createcachetable
e. Memcached(python-memcached 模块)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211', # Memcached 地址
}
}
f. Memcached(pylibmc 模块)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': '127.0.0.1:11211',
}
}
2. 使用缓存
a. 全局缓存
通过中间件实现全局缓存:
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware', # 请求后更新缓存
# 其他中间件
'django.middleware.cache.FetchFromCacheMiddleware', # 请求前检查缓存
]
CACHE_MIDDLEWARE_SECONDS = 600 # 缓存有效期(秒)
CACHE_MIDDLEWARE_KEY_PREFIX = '' # 缓存键的前缀
b. 单独视图缓存
为单个视图函数添加缓存:
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # 缓存 15 分钟
def my_view(request):
return HttpResponse("这是缓存的内容")
或者在路由中指定:
from django.views.decorators.cache import cache_page
urlpatterns = [
path('foo/', cache_page(60 * 15)(my_view)),
]
c. 模板部分缓存
在模板中缓存部分内容:
{% load cache %}
{% cache 600 "my_cache_key" %}
<p>这是缓存的内容。</p>
{% endcache %}
序列化
Django 提供了工具将数据库数据序列化为 JSON、XML 等格式,常用于与客户端的 Ajax 通信。
1. 使用 serializers
from django.core import serializers
from myapp.models import BookType
data = serializers.serialize('json', BookType.objects.all()) # 序列化为 JSON
2. 使用 json.dumps
import json
from myapp.models import BookType
data = list(BookType.objects.all().values('caption')) # 转换为 QuerySet
json_data = json.dumps(data) # 序列化为 JSON
处理日期时间字段:
import json
from datetime import datetime, date
class CustomJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.strftime('%Y-%m-%d %H:%M:%S')
return super().default(obj)
data = json.dumps(data, cls=CustomJSONEncoder) # 使用自定义编码器
信号 (Signals)
Django 提供了 “信号调度器” 来解耦框架操作中的事件和处理逻辑。信号允许发送者在某些事件发生时通知接收者 (信号处理函数)。它可以用来在系统的特定时刻执行一些额外的操作。
1. Django 内置信号
Django 提供了丰富的内置信号,以下是常见的信号分类及其触发时机。
a. Model Signals (模型信号)
信号名称 | 触发时机 |
---|---|
pre_init | 模型实例化之前触发 |
post_init | 模型实例化之后触发 |
pre_save | 模型保存之前触发(save() 方法之前) |
post_save | 模型保存之后触发 |
pre_delete | 模型删除之前触发(delete() 方法之前) |
post_delete | 模型删除之后触发 |
m2m_changed | 操作多对多字段时触发(add 、remove 、clear ) |
class_prepared | Django 启动时,检测到注册的模型类时触发 |
b. Management Signals (管理信号)
信号名称 | 触发时机 |
---|---|
pre_migrate | 执行 migrate 命令之前触发 |
post_migrate | 执行 migrate 命令之后触发 |
c. Request/Response Signals (请求/响应信号)
信号名称 | 触发时机 |
---|---|
request_started | 请求到达之前触发 |
request_finished | 请求完成之后触发 |
got_request_exception | 请求发生异常时触发 |
d. Test Signals (测试信号)
信号名称 | 触发时机 |
---|---|
setting_changed | 使用测试框架运行时,设置文件发生修改时触发 |
template_rendered | 使用测试框架渲染模板时触发 |
e. Database Wrappers (数据库连接信号)
信号名称 | 触发时机 |
---|---|
connection_created | 创建数据库连接时触发 |
2. 注册和使用信号
Django 的信号使用步骤如下:
a. 注册信号
使用 Django 内置信号时,需要先定义回调函数 (处理逻辑),然后将其与信号绑定。
示例:
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from myapp.models import MyModel
# 定义信号处理函数
@receiver(pre_save, sender=MyModel)
def my_callback(sender, instance, **kwargs):
print(f"即将保存模型对象: {instance}")
@receiver(post_save, sender=MyModel)
def my_callback_after_save(sender, instance, created, **kwargs):
if created:
print(f"新对象已创建: {instance}")
else:
print(f"对象已更新: {instance}")
在这个例子中,当 MyModel
的实例被保存时,pre_save
和 post_save
信号会分别调用回调函数 my_callback
和 my_callback_after_save
。
b. 使用信号
不需要显式触发内置信号,它们会在 Django 执行相应操作时自动调用。
3. 自定义信号
有时你需要定义自己的信号并触发它。
a. 定义信号
使用 django.dispatch.Signal
定义一个信号:
from django.dispatch import Signal
# 自定义信号,定义时可以指定传递的参数
pizza_done = Signal(providing_args=["toppings", "size"])
b. 注册信号
将回调函数与自定义信号绑定:
def handle_pizza_done(sender, **kwargs):
print("Pizza is done!")
print(f"Sender: {sender}, Arguments: {kwargs}")
pizza_done.connect(handle_pizza_done)
c. 触发信号
在任意位置触发信号:
# 触发信号
pizza_done.send(sender="Chef", toppings=["pepperoni", "cheese"], size="large")
输出示例:
Pizza is done!
Sender: Chef, Arguments: {'toppings': ['pepperoni', 'cheese'], 'size': 'large'}
Admin 管理后台
Django 提供了一个强大的后台管理页面 (Django Admin),可以快速地对模型数据进行增删改查。以下是使用 Django Admin 的详细步骤和增强技巧。
1. 创建超级用户
超级用户可以登录后台并管理所有内容。
运行以下命令创建超级用户:
python manage.py createsuperuser
输入用户名、邮箱和密码后,超级用户会被创建。
2. URL 配置
默认情况下,Django Admin 的 URL 已配置好。如果没有,请在 urls.py
文件中添加如下代码:
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
3. 注册模型到 Admin
在 admin.py
文件中,通过 admin.site.register
将模型注册到后台管理页面。
from django.contrib import admin
from myapp.models import MyModel
# 注册模型
admin.site.register(MyModel)
注册后,你可以在浏览器中访问 /admin/
查看和管理该模型的数据。
4. 配置 Admin 显示
Django Admin 提供了多种增强模型展示和管理功能的方法。
a. 修改模型名称显示
在模型的 Meta
类中定义 verbose_name
和 verbose_name_plural
:
class MyModel(models.Model):
name = models.CharField(max_length=50)
class Meta:
verbose_name = "我的模型" # 单数名称
verbose_name_plural = "我的模型列表" # 复数名称
b. 自定义列表显示字段
在 Admin 中,默认仅显示模型的 __str__
方法的返回值。要自定义显示的字段,可在 admin.ModelAdmin
中设置 list_display
:
class MyModelAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'created_at') # 指定需要显示的字段
admin.site.register(MyModel, MyModelAdmin)
c. 添加搜索框
为模型添加搜索功能:
class MyModelAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'created_at')
search_fields = ('name',) # 在这些字段中搜索
admin.site.register(MyModel, MyModelAdmin)
d. 添加过滤功能
为模型列表添加侧边栏过滤器:
class MyModelAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'created_at')
list_filter = ('created_at',) # 按日期过滤
admin.site.register(MyModel, MyModelAdmin)
e. 自定义保存逻辑
重写 save_model
方法可以自定义对象的保存逻辑:
class MyModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# 在保存前执行一些操作
print(f"保存的对象: {obj}")
super().save_model(request, obj, form, change)
f. 自定义操作 (Actions)
可以为模型列表添加批量操作:
class MyModelAdmin(admin.ModelAdmin):
actions = ['mark_as_published'] # 注册操作
# 自定义操作
def mark_as_published(self, request, queryset):
queryset.update(status='published')
self.message_user(request, "选中的对象已标记为已发布!")
mark_as_published.short_description = "标记为已发布"
admin.site.register(MyModel, MyModelAdmin)
5. 美化后台
安装 django-grappelli 或 django-suit 等第三方库,可以显著提升后台界面的视觉效果和易用性。
pip install django-grappelli
在 settings.py
的 INSTALLED_APPS
中添加:
INSTALLED_APPS = [
'grappelli',
'django.contrib.admin',
...
]
将 URL 配置指向 Grappelli:
urlpatterns = [
path('grappelli/', include('grappelli.urls')), # Grappelli 管理页面
path('admin/', admin.site.urls),
]
访问 /grappelli/
即可查看美化后的界面。