# DRF简单介绍
Django REST framework (opens new window)是建立在 Django 基础上的 Web 开发子框架,可快速开发REST API接口应用
- 提供了序列化器Serialzier的定义,可以帮助我们简化序列化与反序列化的过程
- 提供丰富的类视图、扩展类、视图集来简化视图的编写工作
- 提供了认证、权限、限流、过滤、分页、接口文档等功能支持
- 提供了一个调试API接口 的Web可视化界面来方便查看测试接口
# 环境安装与配置
- 安装DRF
# DRF需要以下依赖:
# Python 3.6+
# Django 3.0+
pip install djangorestframework -i https://pypi.tuna.tsinghua.edu.cn/simple
- 添加rest_framework应用
# 在settings.py的INSTALLED_APPS中添加'rest_framework'
INSTALLED_APPS = [
...
'rest_framework',
]
# DRF 简写代码的过程
- 创建模型操作类
from django.db import models
class DRFStudent(models.Model):
STATUS = (
(0, "正常入学"),
(1, "正常毕业"),
(2, "已经辍学"),
)
name = models.CharField(max_length=15, verbose_name="学生名字")
age = models.SmallIntegerField(verbose_name="年龄")
sex = models.BooleanField(default=True, verbose_name="性别")
classmate = models.CharField(db_column="class", max_length=50, verbose_name="班级")
mobile = models.CharField(max_length=20, unique=True, verbose_name="下载地址")
description = models.TextField(null=True, verbose_name="手机号码")
status = models.SmallIntegerField(null=True, choices=STATUS, verbose_name="状态码")
created_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
class Meta:
db_table = "student"
verbose_name = "学生信息"
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.name}"
- 创建序列化器
from rest_framework import serializers
from student.models import Student
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = '__all__' # 序列化所有字段
read_only_fields = ['id'] # 只读字段
extra_kwargs = {
}
- 创建视图
from rest_framework.viewsets import ModelViewSet
from stuapi import models
from . import serializers
class StudentModelViewSet(ModelViewSet):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentSerializer
- 定义路由
from django.urls import path
from rest_framework import routers
from drfstu import views
# DefaultRouter 是 Django REST framework 提供的一个实用工具
# 能依据视图集自动生成一系列 RESTful 风格的 URL 路由规则
# 这些规则会涵盖常见的 CRUD(创建、读取、更新、删除)操作
router = routers.DefaultRouter()
# 把视图集注册到该路由器上,从而自动生成与学生信息相关的 RESTful API 的 URL 路由规则
router.register('student', views.StudentModelViewSet, basename='student')
# router.register('base', views.StudentBaseViewSet, basename='base')
# 将 DefaultRouter 自动生成的 URL 路由规则整合到项目的主 URL 模式里
# 项目就会同时支持手动定义的 URL 规则和 DefaultRouter 自动生成的规则
urlpatterns = [
# 普通视图类(如 APIView、IView)可以直接调用 .as_view() 方法
# path("base/", views.StudentBaseViewSet.as_view())
# 视图集(ViewSet)因为支持多种 HTTP 方法对应不同的处理动作(像 list、create等)
# 所以调用 .as_view() 时需要明确指定每个 HTTP 方法对应的处理动作
# path("base/", views.StudentBaseViewSet.as_view({'get': 'list'})),
] + router.urls
- 加载到总路由文件中
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("drfstu/", include("students.urls")),
]
- 在浏览器中输入网址 http://127.0.0.1:8000/drfstu/,可以看到 DRF 提供的 API Web 浏览页面

# 点击链接 http://127.0.0.1:8000/drfstu/student/ 可以访问获取所有数据的接口
# 在页面底下表单部分填写学生信息,可以访问添加新学生的接口,保存学生信息
# http://127.0.0.1:8000/drfstu/student/1/ 可以访问获取单一学生信息的接口
# 在页面底部表单中填写学生信息,可以访问修改学生的接口
# 点击DELETE按钮,可以访问删除学生的接口
# DRF序列化器
1. 序列化:在服务器响应时,使用序列化器可以完成对数据的序列化
# 序列化器会把模型对象转换成字典,经过视图中response对象以后变成json字符串
1. 反序列化:在客户端请求时,使用序列化器可以完成对数据的反序列化
# 视图中request会把客户端发送过来的数据转换成字典,序列化器可以把字典转成模型
# 把客户端发送过来的数据进行校验
注意
- serializer不是只能对模型类转换数据格式,也可以为非数据库模型类的转换数据格式
- serializer是独立于数据库之外的存在,并不是和模型绑定的,具体要看视图实现的业务逻辑来决定
# 常用字段类型
- serailziers类中的字段声明是提供给客户端的

- 字段的选项参数
| 参数名称 | 作用 |
|---|---|
| max_length | 最大长度 |
| min_lenght | 最小长度 |
| allow_blank | 是否允许为空字符串 |
| trim_whitespace | 是否移除字符串两边的空白字符 |
| max_value | 最小数值 |
| min_value | 最大数值 |
- 字段的通用选项参数
| 参数名称 | 说明 |
|---|---|
| read_only | 该字段仅用于序列化输出,默认False,例如:id,created_time,updated_time |
| write_only | 该字段仅用于反序列化输入,默认False,例如:password, |
| required | 该字段在反序列化时必须输入,默认True |
| default | 反序列化时使用的默认值 |
| allow_null | 反序列化时该字段是否允许传入None,默认False |
| validators | 反序列化时该字段使用的验证器函数(是定义在序列化器类外部的校验函数) |
| error_messages | 表明反序列化时如果验证出错了,返回错误错误信息的字典 |
| label | 用于HTML展示API页面时,显示的字段名称。如果不写,则默认采用模型的verbose_name,但是前提是当前序列化器继承ModelSerializer |
| help_text | 用于HTML展示API页面时,显示的字段帮助提示信息。如果不写,则默认采用模型的help_text,但是前提是当前序列化器继承ModelSerializer |
# 定义序列化器
Serializer使用类来定义,必须直接或间接继承于rest_framework.serializers.Serializer
def check_mobile(data):
"""定义在序列化器类外部的校验函数"""
print("check_mobile, data=", data)
ret = DRFStudent.objects.filter(mobile=data).first()
if ret:
raise serializers.ValidationError(code="mobile", detail="当前手机号已经被注册!")
return data # 必须把数据返回,否则后续操作中序列化器无法获取校验后的结果
class StudentBaseSerializer(Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=100)
age = serializers.IntegerField()
sex = serializers.BooleanField
classmate = serializers.CharField(max_length=100)
# 在字段中添加 validators 选项参数,也可以补充验证行为
mobile = serializers.CharField(max_length=20, validators=[check_mobile])
description = serializers.CharField(max_length=2000)
status = serializers.IntegerField()
created_time = serializers.DateTimeField(read_only=True)
updated_time = serializers.DateTimeField(read_only=True)
class Meta:
model = DRFStudent
fields = ['name', 'age', 'sex', 'classmate', 'mobile', 'description', 'status']
# fields = "__all__"
# 全部数据的校验,attrs等价于 request.body
def validate(self, attrs):
ret = DRFStudent.objects.filter(mobile=attrs.get("mobile")).first()
if ret:
raise serializers.ValidationError(code="mobile", detail="当前手机号已经被注册!")
# 可以在这里添加自定义的验证逻辑
return attrs # 必须有返回值
# 校验单个指定字段的数据
def validate_mobile(self, value):
ret = DRFStudent.objects.filter(mobile=value).first()
if ret:
raise serializers.ValidationError(code="mobile", detail="当前手机号已经被注册!")
# 必须把数据返回,否则后续操作中序列化器无法获取校验后的结果
return value
# validated_data 就是校验数据后的返回结果
def create(self, validated_data):
# 可以在这里添加自定义的创建逻辑
# 添加模型操作
# :validated_data 校验后的客户端数据,字典格式
# 视图中使用序列化器对象的save方法会自动调用这个方法
return DRFStudent.objects.create(**validated_data)
# instance 表示要修改的模型对象,validated_data 就是校验数据的返回结果,字典格式
def update(self, instance, validated_data):
# 可以在这里添加自定义的更新逻辑
instance.name = validated_data.get('name', instance.name)
instance.age = validated_data.get('age', instance.age)
# 或者
for field, value in validated_data.items():
setattr(instance, field, value)
instance.save() # 保存实例到数据库
return instance
# 创建序列化器对象
# instance:用于序列化时实例化,则需要将模型类对象传入instance参数
# data:用于反序列化时实例化,将要被反序列化的数据传入data参数
# many:用于序列化时实例化,当instance的值是一个QuerySet类型,则需要声明many=True
# context:创建Serializer对象时,还可通过context参数【字典类型】额外添加数据传入到序列化器中
serializer = 序列化器类(instance=None, data=empty, many=False, context=None, **kwargs)
# 序列化单个模型对象,当instance是模型对象
serializer = StudentSerializer(instance=student)
# 序列化多个模型对象,当instance是QuerySet对象
# 序列化器内部会根据many的值判断是否需要for循环
serializer = StudentSerializer(instance=student_list, many=True)
# 如果传递数据到序列化器中,可以使用context
serializer = StudentSerializer(其他参数..., context={'request': request})
# 通过context参数附加的数据,可以通过Serializer对象的context属性获取
视图调用序列化器
class StudentBaseViewSet(View):
def get(self, request):
sudent = DRFStudent.objects.all()
# 序列化转换数据
# many=True 表示instance参数是一个列表,序列化器内部会根据many的值判断是否需要for循环
serializer = StudentBaseSerializer(sudent, many=True)
student_list = serializer.data
# 类型:rest_framework.utils.serializer_helpers.ReturnList 继承自 list
print(type(student_list))
return JsonResponse(student_list, safe=False)
def post(self, request):
data = json.loads(request.body)
# 实例化序列化器,用于反序列化必须填写data参数
serializer = StudentSerializer(data = data)
# 调用序列化器的校验流程
if not serializer.is_valid():
# 获取校验失败后的错误(validate/validate_mobile/error_messages/validators)提示信息
print(serializer.errors)
return JsonResponse(serializer.errors, status=400)
# 除了使用is_valid的结果进行判断,手动返回错误信息以外,还可以由drf帮我们返回错误信息
# raise_exception=True 表示校验失败时
# 让drf自行返回错误(validate/validate_mobile/error_messages/validators)给客户端
serializer.is_valid(raise_exception=True)
# 模型操作
student = models.Student.objects.create(
name=data.get("name"),
mobile=data.get("mobile"),
age=data.get("age"),
status=data.get("status"),
classmate=data.get("classmate"),
description=data.get("description"),
)
# 序列化
serializer = StudentSerializer(student)
# 返回结果
instance = serializer.data
return JsonResponse(instance, status=201)
# 模型操作
# 反序列化的过程可以把数据转成模型类对象,通过在序列化器中的create()和update()两个方法来实现
# 实现了上述两个方法后,在反序列化数据的时候,就可以通过save()方法返回一个数据对象实例
# 如果创建序列化器对象时没有设置instance参数,则序列化器的create()被调用
# 反之,如果设置instance参数,则序列化器的update()被调用
instance = serializer.save()
# 源代码 rest_framework/serializers.py
def save(self, **kwargs):
validated_data = {**self.validated_data, **kwargs}
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, (
'`update()` did not return an object instance.'
)
else:
self.instance = self.create(validated_data)
assert self.instance is not None, (
'`create()` did not return an object instance.'
)
return self.instance
视图操作代码
from . import serializers
def post3(self, request):
"""添加数据操作"""
data = json.loads(request.body)
serializer = serializers.StudentSerializer(data = data)
serializer.is_valid(raise_exception=True)
# 因为上面实例化序列化器时没有传递instance参数,所以save自动调用模型类中的create方法
serializer.save()
instance = serializer.data
return JsonResponse(instance, status=201)
def put(self, request):
"""更新数据操作"""
id = request.GET.get("id")
try:
student = models.Student.objects.get(id=id)
except models.Student.DoesNotExist:
return JsonResponse({}, status=400)
data = json.loads(request.body)
serializer = serializers.StudentSerializer(instance = student, data = data)
serializer.is_valid(raise_exception=True)
# 因为上面实例化序列化器时有传递instance参数,所以save自动调用模型类中的update方法
serializer.save()
instance = serializer.data
return JsonResponse(instance, status=201)
# 部分字段跳过验证阶段的方式说明
- 在对序列化器进行save()保存时,可以传递额外数据,这些数据可以在create()和update()中的validated_data参数获取到
# 把 request 对象作为参数传入后,能在序列化器的 create() 或 update() 方法里访问该请求对象
# 这样在创建或更新对象时,就可依据请求信息(如用户认证信息、请求头)做额外处理
serializer.save(request=request) # 可以在save中,传递1个或多个不需要验证的数据到模型里面
- 默认序列化器必须传递所有required的字段,否则会抛出验证异常。但是我们可以使用partial参数来允许部分字段更新
# 在开发中可能只需要验证当前被修改的字段,而不想验证其他的字段时,可以设置partial=True
# 这种情况有很多,例如,手机号更新绑定,修改密码或头像等等。
# 例如,当前学生模型中,我们只修改mobile字段,但是不提供其他的字段修改。则代码如下:
serializer = StudentSerializer(student, data={'mobile': '13000000000'}, partial=True)
# 模型类序列化器
ModelSerializer模型类序列化器与Serializer序列化器基类声明方式与调用方式一致,但额外提供了:
- 基于模型类自动生成一系列字段声明
- 基于模型类自动为Serializer生成unique、validators、等校验规则
- 默认提供了create()和update()的实现
from rest_framework import serializers
from rest_framework.serializers import ModelSerializer
from drfstu.models import DRFStudent
def check_mobile(data):
"""定义在序列化器类外部的校验函数"""
print("check_mobile, data=", data)
ret = DRFStudent.objects.filter(mobile=data).first()
if ret:
raise serializers.ValidationError(code="mobile", detail="当前手机号已经被注册!")
return data # 必须把数据返回,否则后续操作中序列化器无法获取校验后的结果
class StudentBaseSerializer(ModelSerializer):
# 仅保留有自定义验证逻辑的字段
mobile = serializers.CharField(max_length=20, validators=[check_mobile])
class Meta:
model = DRFStudent
fields = ['id', 'name', 'age', 'sex', 'classmate', 'mobile', 'description', 'status', 'created_time', 'updated_time']
# 排除,少用,与fields互斥的
exclude = ['created_time', 'updated_time']
read_only_fields = ['id', 'created_time', 'updated_time'] # 只读字段
# 选填,字段额外选项声明
extra_kwargs = {
# "字段名": {
# "选项": 值,
# "选项": 值,
# ...
# },
# "name": {
# "error_messages": {"required": "姓名不能为空!"}
# },
#
# "age": {
# "max_value": 99,
# "min_value": 1,
# }
}
# 全部数据的校验,attrs等价于 request.body
def validate(self, attrs):
ret = DRFStudent.objects.filter(mobile=attrs.get("mobile")).first()
if ret:
raise serializers.ValidationError(code="mobile", detail="当前手机号已经被注册!!!!!")
# 可以在这里添加自定义的验证逻辑
return attrs # 必须有返回值
# 校验单个指定字段的数据
def validate_mobile(self, value):
ret = DRFStudent.objects.filter(mobile=value).first()
if ret:
raise serializers.ValidationError(code="mobile", detail="当前手机号已经被注册!!!")
# 必须把数据返回,否则后续操作中序列化器无法获取校验后的结果
return value
# validated_data 就是校验数据后的返回结果
def create(self, validated_data):
# 可以在这里添加自定义的创建逻辑
return DRFStudent.objects.create(**validated_data)
# # instance 表示要修改的模型对象,validated_data 就是校验数据的返回结果
def update(self, instance, validated_data):
# 可以在这里添加自定义的更新逻辑
# instance.name = validated_data.get('name', instance.name)
# instance.age = validated_data.get('age', instance.age)
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
视图代码
import json
from django.views import View
from django.http.response import JsonResponse
from stuapi import models
from . import serializers
class Student3View(View):
def get1(self,request):
"""基于模型类序列化器序列化单个模型数据"""
# 模型读取数据
student = models.Student.objects.first()
# 序列化
serializer = serializers.StudentModelSerializer(instance=student)
student_dict = serializer.data
# 响应数据
return JsonResponse(student_dict, safe=False)
def get(self,request):
"""基于模型类序列化器序列化QuerySet"""
# 模型读取数据
student_objs = models.Student.objects.all()
# 序列化
serializer = serializers.StudentModelSerializer(instance=student_objs, many=True)
student_list = serializer.data
# 响应数据
return JsonResponse(student_list, safe=False)
def post(self,request):
"""基于模型类序列化器实现添加数据的校验与保存"""
# 接收数据
data = json.loads(request.body)
# 实例化序列化器,用于反序列化必须填写data参数
serializer = serializers.StudentModelSerializer(data=data)
# 校验数据
serializer.is_valid(raise_exception=True)
# 模型操作
serializer.save() # 自动调用模型类中的create方法
# 返回结果
instance = serializer.data
return JsonResponse(instance, status=201)
def put(self,request):
"""基于模型类序列化器实现更新数据的校验与保存"""
# 此处为了方便演示,让客户端通过查询字符串传递id,指定要修改的模型对象
id = request.GET.get("id")
try:
student = models.Student.objects.get(id=id)
except models.Student.DoesNotExist:
return JsonResponse({"errors": "当前数据不存在!"}, status=400)
# 接收数据
data = json.loads(request.body)
# 实例化序列化器
serializer = serializers.StudentModelSerializer(instance=student, data=data)
print(serializer)
# 校验数据
serializer.is_valid(raise_exception=True)
# 模型操作
serializer.save() # 自动调用模型类中的update方法
# 返回结果
instance = serializer.data
return JsonResponse(instance, status=201)
- 路由代码
from django.urls import path, re_path
from . import views
urlpatterns = [
path("student/", views.StudentView.as_view()),
path("s1/", views.Student1View.as_view()),
path("s2/", views.Student2View.as_view()),
path("s3/", views.Student3View.as_view()),
]
# DRF请求响应
# 内容协商
- drf在django原有的基础上,新增了一个request对象内置到了drf提供的APIVIew视图类里面
- 并在django原有的HttpResponse响应类的基础上实现了一个子类rest_framework.response.Response
- 这两个类,都是基于内容协商来完成数据的格式转换的
# Request 请求
drf传入视图的request对象不再是Django默认的HttpRequest对象,而是drf自己声明的Request类的对象
# drf 提供了 Parser (http请求解析器类) 在接收到客户端请求后会自动根据 Content-Type 的请求类型
# 如JSON、html等,将数据解析为类字典 [QueryDict] 对象保存到 drf 的 Request 对象的 data 属性中
- 常用属性
request.data
# 包含了解析之后的文件和非文件数据
# 包含了对POST、PUT、PATCH请求方式解析后的数据
# 利用了REST framework的parser解析器,不仅支持表单类型数据,也支持JSON数据
# 提供了Parse解析之后的请求体数据。类似于Django中request.POST和 request.FILES、request.body属性
request.query_params
# 只要是地址栏 ? 号后面的查询字符串都可以接收,结果是QueryDict格式
- 视图代码
class StudentBaseViewSet(APIView):
def get(self, request):
# 获取地址栏上的查询字符串
print(request.query_params) # <QueryDict: {'url': ['baidu.com']}>
print(request.query_params.dict()) # {'url': 'baidu.com'}
return Response({"mgs":"get"})
def post(self, request):
# <QueryDict: {'name': ['sylone'], 'age': ['32']}>
print(request.data)
# 如果客户端提交的是表单数据,则可以使用dict方法,把QueryDict转换成普通字典
# {'name': 'sylone', 'age': '32'}
print(request.data.dict())
return Response({"mgs":"get"})
使用了drf的视图类,必须使用drf的请求与响应。同理,使用了django的视图了则使用Django的请求与响应
# Django --> View --> HttpRequest/HttpResponse/JsonResponse
# Drf --> APIView --> Request/Response
# Response 响应
REST framework 提供了Renderer 渲染器,用来根据客户端的请求头中的 Accept(接收数据类型声明)来自动转换响应数据格式
# 可以在rest_framework/settings.py查找所有的drf默认配置项
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( # 默认http响应渲染类
'rest_framework.renderers.JSONRenderer', # json渲染器,返回json数据
'rest_framework.renderers.BrowsableAPIRenderer', # 浏览器API渲染器,返回api调试界面
)
}
- Response类的构造方式
from rest_framework.response import Response
# data不能是复杂结构的数据,如Django的模型类对象,可以使用Serializer序列化处理后再传递给data参数
# REST framewrok在rest_framework.status模块中提供了常用http状态码的常量:HTTP_200_OK 等
Response(data, status=None, template_name=None, headers=None, content_type=None)
# DRF的视图类
# 视图类 APIView
- APIView 是 REST framework 所有视图类的基类,继承自Django的View视图类,APIView与View不同:
# 传入到视图方法中的是 REST framework 的 Request 对象,而不是 Django 的 HttpRequeset 对象
# 视图方法可以返回 REST framework 的 Response 对象,视图会为响应数据设置符合前端期望要求的格式
# 任何可以被 APIException 捕获到异常,都将会被 APIView 处理成合适格式的响应信息返回给客户端
# django 的 View 中所有异常全部以 HTML 格式显示,不会返回 json 格式
# drf 的 APIVIew 或者 APIView 的子类会自动根据客户端的 Accept 进行错误信息的格式转换
# drf的as_view方法在dispatch()进行路由分发前,会对请求的客户端进行身份认证、权限检查、流量控制
# APIView为了实现上面说的3个功能除了继承了View原有的属性方法意外,还新增了类属性:
# authentication_classes 值是列表或元组,成员是身份认证类
# permissoin_classes 值是列表或元组,成员是权限检查类
# throttle_classes 值是列表或元祖,成员是流量控制类
- 视图代码
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from stuapi import models
from . import serializers
class StudentAPIView(APIView):
def get(self, request):
"""获取所有学生信息"""
# 获取数据
student_objs = models.Student.objects.all()
# 序列化,instance参数是QuerySet,必须声明many参数
serializer = serializers.StudentModelSerializer(instance=student_objs, many=True)
# 返回结果
return Response(serializer.data)
def post(self,request):
"""添加一个学生信息"""
# 接受客户端数据,并反序列化【验证、保存】
serializer = serializers.StudentModelSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回结果
return Response(serializer.data, status=status.HTTP_201_CREATED)
class StudentInfoAPIView(APIView):
def get(self, request, pk):
"""获取一个学生信息"""
# 读取数据
try:
instance = models.Student.objects.get(pk=pk)
except models.Student.DoesNotExist:
return Response({"errors": "当前学生信息不存在!"},
status=status.HTTP_400_BAD_REQUEST)
# 序列化
serializer = serializers.StudentModelSerializer(instance=instance)
# 返回结果
return Response(serializer.data)
def delete(self, request, pk):
"""删除一个学生信息"""
# 删除数据
models.Student.objects.filter(pk=pk).delete()
# 返回结果
return Response({}, status=status.HTTP_204_NO_CONTENT)
def put(self, request, pk):
"""更新一个学生信息"""
# 查询要更新的学生信息
try:
instance = models.Student.objects.get(pk=pk)
except models.Student.DoesNotExist:
return Response({"errors": "当前学生信息不存在!"},
status=status.HTTP_400_BAD_REQUEST)
# 反序列化【校验、保存】,如果允许修改部分字段,则可以设置partial=True
serializer = serializers.StudentModelSerializer(instance=instance,
data=request.data,
partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回序列化结果
return Response(serializer.data, status=status.HTTP_201_CREATED)
- 路由代码
from django.urls import path,re_path
from . import views
urlpatterns = [
path("student/", views.StudentAPIView.as_view()),
re_path(r"^student/(?P<pk>\d+)/$", views.StudentInfoAPIView.as_view()),
]
# GenericAPIView 通用视图类
- 继承自APIView,主要增加了操作序列化器的1个属性与2个方法
# 属性:
serializer_class=序列化器 # 指明视图使用的序列化器类
# 方法1:
get_serializer(self, args, *kwargs) # 返回序列化器对象
# 方法2:
get_serializer_class(self) # 返回序列化器类,默认返回serializer_class
# 一个视图类中调用多个序列化器时,可以返回不同的序列化器类名就可以让视图方法执行不同的序列化器对象
class Student2GenericAPIView(GenericAPIView):
queryset = Student.objects.all()
# 整个视图类只使用一个序列化器的情况
serializer_class = StudentModelSerializert
# 整个视图类中使用多个序列化器的情况
def get_serializer_class(self):
if self.request.method.lower() == "put":
return StudentModelSerializer
else:
return Student2ModelSerializer
def get(self, request, pk):
"""获取一个模型信息"""
serializer = self.get_serializer(instance=self.get_object())
return Response(serializer.data)
def put(self, request, pk):
"""更新一个模型信息"""
serializer = self.get_serializer(instance=self.get_object(), data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
- 增加了关于数据库查询的1个属性与2个方法
# 属性:
queryset # 指明使用的数据查询的结果集
# 方法1:
get_queryset(self) # 返回视图使用的查询集QuerySet
# 方法2:
get_object(self) # 返回详情视图所需的1个模型类数据对象
- 其他可以设置的属性
pagination_class # 指明分页控制类
filter_backends # 指明数据过滤控制后端,允许客户端通过地址栏传递过滤参数
- 视图代码
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from rest_framework import status
from drfstu.baseserializer import StudentBaseSerializer
from drfstu.models import DRFStudent
class StudentBaseViewSet(GenericAPIView):
queryset = DRFStudent.objects.all()
serializer_class = StudentBaseSerializer
def get(self, request):
# 获取所有数据
instance = self.get_queryset()
# # 本质上 self.serializer_class(instance, many=True)
serializer = self.get_serializer(instance, many=True)
return Response(serializer.data)
def post(self, request):
# 新增数据,接受客户端数据,并反序列化【验证、保存】
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors)
class StudentInfoViewSet(GenericAPIView):
queryset = DRFStudent.objects
serializer_class = StudentBaseSerializer
def get(self, request, pk):
# 获取指定数据
# instance = DRFStudent.objects.get(pk=pk)
# instance = self.get_queryset().get(pk=pk)
instance = self.get_object() # 本质上就是 self.queryset.get(pk=pk)
serializer = self.get_serializer(instance) # 本质上 self.serializer_class(instance)
return Response(serializer.data)
# 注意请求地址最后一定要加 / http://127.0.0.1:8000/drfstu/stu/1/
def put(self, request, pk):
# 修改指定数据
instance = self.get_object()
# 反序列化【校验、保存】,如果允许修改部分字段,则可以设置partial=True
serializer = self.get_serializer(instance, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors)
def delete(self, request, pk):
# 删除指定数据
instance = self.get_object()
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
- 路由代码
from django.urls import path,re_path
from . import views
urlpatterns = [
path("student/", views.StudentAPIView.as_view()),
re_path(r"^student/(?P<pk>\d+)/$", views.StudentInfoAPIView.as_view()),
]
# 五个视图扩展类
- 也叫视图混入类,提供了对数据资源进行增删改查的视图方法处理流程的实现
- 如果需要编写的视图方法属于这五种,可以通过继承相应的扩展类来减少自己编写的代码量
- 需要搭配GenericAPIView通用视图类,因为需要调用GenericAPIView提供的序列化器与数据查询的方法
ListModelMixin -> list # 获取多条数据,可以过滤和分页
CreateModelMixin -> create -> perform_create # 添加一条数据
RetrieveModelMixin -> retrieve # 获取一条数据
UpdateModelMixin -> update/partial_update -> perform_update # 更新一条数据/局部更新
DestroyModelMixin -> destroy -> perform_update # 删除一条数据
- 使用GenericAPIView结合视图扩展类,实现5个基本api接口,视图代码:
from rest_framework import mixins
class StudentMixinAPIView(GenericAPIView, mixins.ListModelMixin, mixins.CreateModelMixin):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
def get(self, request):
"""获取所有数据"""
return self.list(request)
def post(self, request):
"""添加一条数据"""
return self.create(request)
class StudentInfoMixinAPIView(GenericAPIView, mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
def get(self, request, pk):
"""获取一条数据"""
return self.retrieve(request, pk)
def put(self, request, pk):
"""更新一条数据"""
return self.update(request, pk)
def delete(self, request, pk):
"""删除一条数据"""
return self.destroy(request, pk)
- 路由代码:
from django.urls import path, re_path
from . import views
urlpatterns = [
# GenericAPIView+mixins
path("student3/", views.StudentMixinAPIView.as_view()),
re_path(r"^student3/(?P<pk>\d+)/$", views.StudentInfoMixinAPIView.as_view()),
]
# 九个视图子类
1)ListAPIView
# 提供了get视图方法,内部调用了模型扩展类的list方法
# 继承自:GenericAPIView、ListModelMixin
2)CreateAPIView
# 提供了post视图方法,内部调用了模型扩展类的create方法
# 继承自: GenericAPIView、CreateModelMixin
3)RetrieveAPIView
# 提供了get视图方法,内部调用了模型扩展类的retrieve方法
# 继承自: GenericAPIView、RetrieveModelMixin
4)DestroyAPIView
# 提供了delete视图方法,内部调用了模型扩展类的destroy方法
# 继承自:GenericAPIView、DestoryModelMixin
5)UpdateAPIView
# 提供了put和patch视图方法,内部调用了模型扩展类的update和partial_update方法
# 继承自:GenericAPIView、UpdateModelMixin
6)ListCreateAPIView
# 提供了get和post方法,内部调用了list和create方法
# 继承自:GenericAPIView、ListModelMixin、CreateModelMixin
7)RetrieveUpdateAPIView
# 提供 get、put、patch方法
# 继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin
8)RetrieveDestroyAPIView
# 提供 get、delete方法
# 继承自:GenericAPIView、RetrieveModelMixin、DestroyModelMixin
9)RetrieveUpdateDestroyAPIView
# 提供 get、put、patch、delete方法
# 继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin
- 视图代码
from rest_framework import generics
# class StudentSonAPIView(generics.ListAPIView, generics.CreateAPIView):
class StudentSonAPIView(generics.ListCreateAPIView):
"""
查询所有数据
添加一条数据
"""
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
# class StudentInfoSonAPIView(generics.RetrieveAPIView, generics.UpdateAPIView, generics.DestroyAPIView):
# class StudentInfoSonAPIView(generics.RetrieveUpdateAPIView, generics.DestroyAPIView):
# class StudentInfoSonAPIView(generics.RetrieveDestroyAPIView, generics.UpdateAPIView):
class StudentInfoSonAPIView(generics.RetrieveUpdateDestroyAPIView):
"""
查询一条数据
更新一条数据
删除一条数据
"""
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
- 视图代码
from django.urls import path, re_path
from . import views
urlpatterns = [
path("student4/", views.StudentSonAPIView.as_view()),
re_path(r"^student4/(?P<pk>\d+)/$", views.StudentInfoSonAPIView.as_view()),
]
# DRF的视图集
使用视图集可以将视图相关的逻辑代码和http请求封装到一个类中,不再限制必须使用http请求的get/post...等作为视图方法名了,而是允许自定义视图方法名。
# ViewSet
ViewSet主要通过继承ViewSetMixin来重写了APIView的as_view()方法,允许开发者在调用as_view时传递字典参数,把视图方法名与HTTP请求动作进行关联映射
from rest_framework.response import Response
from rest_framework import status
from rest_framework.viewsets import ViewSet
from drfstu import baseserializer
from drfstu.models import DRFStudent
class StudentBaseViewSet(ViewSet):
def getlist(self, request):
instance = DRFStudent.objects.all()
serializer = baseserializer.StudentBaseSerializer(instance, many=True)
return Response(serializer.data)
def getone(self, request, pk):
instance = DRFStudent.objects.get(id=pk)
serializer = baseserializer.StudentBaseSerializer(instance)
return Response(serializer.data)
def gettop(self, request):
instance = DRFStudent.objects.all().order_by("-age")[:3]
serializer = baseserializer.StudentBaseSerializer(instance, many=True)
return Response(serializer.data)
def add(self, request):
serializer = baseserializer.StudentBaseSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors)
def update(self, request, pk):
instance = DRFStudent.objects.get(id=pk)
serializer = baseserializer.StudentBaseSerializer(instance,
data=request.data,
partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors)
def delete(self, request, pk):
instance = DRFStudent.objects.get(id=pk)
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
urlpatterns = [
path("stu/", views.StudentBaseViewSet.as_view({
'get': 'getlist',
'post': 'add',
})),
re_path("^stu/(?P<pk>\d+)/$", views.StudentBaseViewSet.as_view({
'get': 'getone',
'put': 'update',
'delete': 'delete'
})),
path("stu/top/", views.StudentBaseViewSet.as_view({
'get': 'gettop',
})),
]
# GenericViewSet
GenericViewSet(通用视图集)继承自GenericAPIView和ViewSetMixin
from rest_framework.response import Response
from rest_framework import status
from rest_framework.viewsets import GenericViewSet
from drfstu.baseserializer import StudentBaseSerializer
from drfstu.models import DRFStudent
class StudentBaseViewSet(GenericViewSet):
queryset = DRFStudent.objects.all()
serializer_class = StudentBaseSerializer
def getlist(self, request):
instance = self.get_queryset()
serializer = self.get_serializer(instance, many=True)
return Response(serializer.data)
def getone(self, request, pk):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
def gettop(self, request):
instance = self.get_queryset().order_by('-age')[:5]
serializer = self.get_serializer(instance, many=True)
return Response(serializer.data)
def add(self, request):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors)
def update(self, request, pk):
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors)
def delete(self, request, pk):
instance = self.get_object()
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
urlpatterns = [
path("stu/", views.StudentBaseViewSet.as_view({
'get': 'getlist',
'post': 'add',
})),
re_path("^stu/(?P<pk>\d+)/$", views.StudentBaseViewSet.as_view({
'get': 'getone',
'put': 'update',
'delete': 'delete'
})),
path("stu/top/", views.StudentBaseViewSet.as_view({
'get': 'gettop',
})),
]
# GenericViewSet+Mixins
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from drfstu.baseserializer import StudentBaseSerializer
from drfstu.models import DRFStudent
from rest_framework import mixins
class StudentBaseViewSet(GenericViewSet,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin):
queryset = DRFStudent.objects.all()
serializer_class = StudentBaseSerializer
def gettop(self, request):
instance = self.get_queryset().order_by('-age')[:5]
serializer = self.get_serializer(instance, many=True)
return Response(serializer.data)
# 避免与 self.update 方法重名,不建议用 update
def put(self, request, pk):
return self.update(request, pk, partial=True)
urlpatterns = [
path("stu/", views.StudentBaseViewSet.as_view({
'get': 'getlist',
'post': 'add',
})),
re_path("^stu/(?P<pk>\d+)/$", views.StudentBaseViewSet.as_view({
'get': 'getone',
'put': 'put',
'delete': 'delete'
})),
path("stu/top/", views.StudentBaseViewSet.as_view({
'get': 'gettop',
})),
]
# ModelViewSet 模型视图集
继承自GenericViewSet,同时还继承了5个视图扩展类(ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin)
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from drfstu.baseserializer import StudentBaseSerializer
from drfstu.models import DRFStudent
class StudentBaseViewSet(ModelViewSet):
queryset = DRFStudent.objects.all()
serializer_class = StudentBaseSerializer
def gettop(self, request):
instance = self.get_queryset().order_by('-age')[:5]
serializer = self.get_serializer(instance, many=True)
return Response(serializer.data)
def put(self, request, pk):
return self.update(request, pk, partial=True)
urlpatterns = [
path("stu/", views.StudentBaseViewSet.as_view({
'get': 'getlist',
'post': 'add',
})),
re_path("^stu/(?P<pk>\d+)/$", views.StudentBaseViewSet.as_view({
'get': 'getone',
'put': 'put',
'delete': 'delete'
})),
path("stu/top/", views.StudentBaseViewSet.as_view({
'get': 'gettop',
})),
]
# DRF的路由集
对于视图集ViewSet,除了手动指明请求方式与视图方法之间的对应关系外,还可以使用Routers来快速实现路由信息。如果是非视图集,不需要使用路由集routers
# SimpleRouter 和 DefaultRouter
# REST framework提供了两个router类,使用方式基本一致 SimpleRouter、DefaultRouter
# 区别是:SimpleRouter用于线上运营项目,DefaultRouter 本地开发,项目上线前
# DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据
from django.urls import path, re_path
from rest_framework import routers
from drfstu import views
router = routers.SimpleRouter()
# router = routers.DefaultRouter()
# prefix 该视图集的路由前缀
# viewset 视图集
# basename 路由别名的前缀
router.register('stu', views.StudentBaseViewSet, basename='stu')
urlpatterns = [
path("stu/top/", views.StudentBaseViewSet.as_view({
'get': 'gettop',
})),
] + router.urls
print("simple_router=", router.urls)
print("urlpatterns=", urlpatterns)
print('simple_router=', router.urls)
# action 装饰器自动根据视图自定义方法生成路由
action装饰器可以接收三个参数:
methods # 声明被action装饰的视图方法允许外界通过哪些HTTP请求访问,默认是get
detail # 声明被action装饰的视图方法名生成自定义路径,是否使用当前类作为路径的尾缀
# detail=True 表示将来生成url格式是<prefix>/<pk>/<url_path>/
# detail=False 表示将来生成url格式是<prefix>/<url_path>/
url_path # 声明该action的路由尾缀。默认就是视图方法名
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from drfstu.baseserializer import StudentBaseSerializer
from drfstu.models import DRFStudent
class StudentBaseViewSet(ModelViewSet):
queryset = DRFStudent.objects.all()
serializer_class = StudentBaseSerializer
# 用于告诉路由集该如何生成url路径的,是否要加上pk值等等
@action(methods=['GET'], detail=False, url_path='top')
def gettop(self, request):
instance = self.get_queryset().order_by('-age')[:5]
serializer = self.get_serializer(instance, many=True)
return Response(serializer.data)
from rest_framework import routers
router = routers.SimpleRouter()
# router = routers.DefaultRouter()
router.register('stu', views.StudentBaseViewSet, basename='stu')
# DRF权限认证
# 认证 Authentication
- 可以在配置文件中配置全局默认的认证方式/认证流程,见的认证方式:cookie、session、token
- 在drf的默认配置文件中,提供了DEFAULT_AUTHENTICATION_CLASSES配置项进行认证类的注册
# rest_framework/settings.py
REST_FRAMEWORK = {
# 配置认证方式的选项
'DEFAULT_AUTHENTICATION_CLASSES': (
# session认证,从request.user提取用户认证
'rest_framework.authentication.SessionAuthentication',
# basic认证[基于请求头,使用用户ID与密码来完成用户认证]
'rest_framework.authentication.BasicAuthentication',
)
}
- 自定义全局认证类 settings.py
# user/authentication.py
from django.contrib.auth import get_user_model
from rest_framework import authentication
# drf中所有的认证类必须直接或间接继承于authentication.BaseAuthentication类
class CustomAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
User = get_user_model()
# request._request.headers.get("键") drf从django的请求头中提取数据
user_id = request.query_params.get('user')
password = request.query_params.get('password')
if user_id is None or password is None:
return None
try:
user = User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
ret = user.check_password(password)
if ret is False:
return None
return (user, None)
# 在项目中注册并使用上面自定义的认证类 settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
"user.authentication.CustomAuthentication",
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
],
}
- 自定义局部认证类 authentication_classes
from django.http import HttpResponse
from django.views import View
from user import authentication
# 注意一定要继承 APIView !!!才有校验功能
class UserView(View):
authentication_classes = [authentication.CustomAuthentication]
def get(self, request):
user = request.user
# 没有登陆,或request.user无法获取认证信息,则request.user是匿名用户:AnonymousUser
# 已经登陆,经过中间件的处理以后,request.user的值是当前登陆用户的模型对象
print(user)
if user.id:
print(user.username, user.first_name, user.last_name)
return HttpResponse("get请求")
# 权限 Permissions
- 权限控制可以限制用户对于视图的访问和对于具有模型对象的访问
# 在APIView视图的dispatch()方法中调用initial方法中先进行视图访问权限的判断
self.check_permissions(request)
# 在GenericAPIView通过get_object()获取具体模型对象时,会进行模型对象访问权限的判断
self.check_object_permissions(self.request, obj)
- 可以在配置文件restframework/settings.py中默认的全局设置了权限管理类,源码:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
}
- 如果要在项目覆盖默认配置rest_framework/settings.py的设置,则可以在项目配置文件中,settings.py
# AllowAny 表示允许任何用户访问站点视图
# IsAuthenticated 只允许登陆用户[包括管理员]访问
# IsAdminUser 只允许管理员访问【游客、普通用户不能访问】
# IsAuthenticatedOrReadOnly 登录用户可以操作数据,但是游客只能看不能修改
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]
}
- 也可以在视图类中通过类属性permission_classes来进行局部设置
from rest_framework import permissions
from rest_framework.views import APIView
class UserView(APIView):
# permission_classes = [permissions.AllowAny]
# permission_classes = [permissions.IsAuthenticated]
# permission_classes = [permissions.IsAdminUser]
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
# 自定义权限类
需继承rest_framework.permissions.BasePermission父类,并实现以下两个任何一个方法或全部
class CustomPermission(BasePermission):
def has_permission(self, request, view):
"""
判断视图访问权限
request drf提供的HTTP请求对象
view 当前被访问的视图对象
return 必须是True或者False,True表示允许访问 False不允许访问
"""
if int(request.query_params.get("level", 0)) >= 10:
return True
def has_object_permission(self, request, view, obj):
"""
判断对象访问权限
request drf提供的HTTP请求对象
view 当前被访问的视图对象
obj 当前视图中基于get_object调用的模型对象
return 必须是True或者False,True表示允许访问 False不允许访问
"""
return True
class LevelAPIView(APIView):
permission_classes = [CustomPermission]
def get(self, request):
return Response({"msg": "VIP10->专属功能"})
# 限流 Throttling
- 可以在配置文件中进行全局配置,针对整个站点所有视图
# 注意缓存的配置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/0",
"TIMEOUT": 300,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
REST_FRAMEWORK = {
# 限流全局配置类
'DEFAULT_THROTTLE_CLASSES': [
# 'rest_framework.throttling.AnonRateThrottle', # 未登录认证的用户访问
# 'rest_framework.throttling.UserRateThrottle' # 已登录认证的用户访问
# 'rest_framework.throttling.ScopedRateThrottle' # 针对限流命名空间进行限制
],
# 访问频率的全局配置,默认是None,即不限制,支持使用 s(秒) m(分) h(时) d(天)来指明限流周期
'DEFAULT_THROTTLE_RATES': {
# 'anon': '2/day', # 针对未登录用户的访问频率进行限制
# 'user': '5/day', # 针对登录用户的访问频率进行限制
'member': '2/min', # 针对登录用户的访问频率进行限制
},
}
- 也可以在具体视图类中通过类属性throttle_classess来局部配置
class UserAPIView(APIView):
# AnonRateThrottle
# 限制未登录用户,使用IP区分用户
# 使用配置项DEFAULT_THROTTLE_RATES['anon'] 来设置频次
# UserRateThrottle
# 限制登录用户,使用User模型的id主键来区分
# 使用配置项DEFAULT_THROTTLE_RATES['user'] 来设置频次
# throttle_classes = [UserRateThrottle, AnonRateThrottle]
# ScopedRateThrottle
# 限制用户对于每个视图类的访问频次,使用ip或user id
# 使用视图类中的throttle_scope设置限流频次的变量名
# 假设是member,则可以使用配置项DEFAULT_THROTTLE_RATES['member']来设置频次
throttle_classes = [ScopedRateThrottle]
throttle_scope = 'member'
def get(self, request):
return Response("ok")
# DRF过滤查询排序
- 通过添加django-fitlter扩展模块来增强支持,使用文档 (opens new window)
pip install django-filter
# 使用第三方过滤类django-filters一定要在配置文件中配置
INSTALLED_APPS = [
# ....
'django_filters',
]
- 全局配置
# 1、在配置文件中增加过滤器类的全局设置
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend', # 过滤
'rest_framework.filters.OrderingFilter', # 排序
'rest_framework.filters.SearchFilter' # 查询
],
}
# 2、在具体的视图类中添加类属性 filterset_fields,指定可以过滤的字段
class StudentBaseViewSet(ModelViewSet):
queryset = DRFStudent.objects.all()
serializer_class = StudentBaseSerializer
filterset_fields = ['mobile', 'age'] # 用于 list 中 !!!
search_fields = ['name', 'mobile'] # 用于 list 中 !!!
ordering_fields = ['age', 'id'] # 用于 list 中 !!!
# 单个字段过滤
# http://127.0.0.1:8000/component/list/?age=18
# http://127.0.0.1:8000/component/list/?sex=1
# 多个字段过滤
# http://127.0.0.1:8000/component/list/?age=18&sex=1
# 查询
# http://127.0.0.1:8000/component/order/?search=example
# 会在 search_fields 指定的字段中搜索包含 example 的记录
# 排序
# http://127.0.0.1:8000/component/order/?ordering=-id
# -id 表示针对id字段进行倒序排序
# id 表示针对id字段进行升序排序
- 局部设置
# 在视图类中设置类属性filter_backends调用的过滤器类
from rest_framework.generics import ListAPIView
from django_filters.rest_framework import DjangoFilterBackend
class FilterAPIView(ListAPIView):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
# 在当前视图中设置过滤器、排序器、查询器
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
filterset_fields = ["sex", "age"] # 过滤
search_fields = ['name', 'mobile'] # 查询
ordering_filters = ["id", "age"] # 排序
注意
- 过滤查询排序共用配置项filter_backends,所以要么一起全局配置,要么一起局部配置
- 局部配置项 filter_backends 会自动覆盖全局配置的 DEFAULT_FILTER_BACKENDS
# DRF的分页器
# 全局配置分页器
- 在配置文件settings.py中进行全局分页配置
REST_FRAMEWORK = {
# 以page参数作为分页参数
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 以limit参数作为分页参数
'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 5, # 每页数目,如果不设置,则没有进行分配
}
- 如果在settings.py配置文件中设置了全局分页,那么凡是调用了ListModelMixin的list()都会自动分页
{
"count": 16, // 本次分页的总数据量
"next": "http://127.0.0.1:8000/component/page/?page=3", // 下一页数据所在的地址
"previous": "http://127.0.0.1:8000/component/page/", // 上一页数据所在的地址
"results": [ // 当前页数据的列表项
{
"id": 6,
"name": "xiaoming",
"sex": true,
"age": 20,
"classmate": "303",
"description": "hello world"
},
// .....
]
}
- 如果项目中只有少数部分的不需要分页,则可以在少部分的视图类中关闭分页功能
class PageAPIView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
pagination_class = None # 关闭来自全局配置的分页设置,设置当前列表视图不需要分页
# 自定义分页器 PageNumberPagination
- 创建一个自定义分页器类
from rest_framework.pagination import PageNumberPagination
class StudentPagination(PageNumberPagination):
page_size = 10 # 每页数据量
page_query_param = "page" # 地址栏代表页码的参数
page_size_query_param = "size" # 地址栏代表数据量的参数
max_page_size = 10 # 限制客户端修改的每页数据量的最大值
- 视图中使用自定义分页类
from . import paginations
class studentAPIView(ListAPIView):
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
# pagination_class的值只能是一个分页对象,
pagination_class = paginations.StudentPagination
# 访问地址:http://127.0.0.1:8000/component/student/?page=3
# 访问地址:http://127.0.0.1:8000/component/student/?page=3&size=5
# 自定义分页器 LimitOffsetPagination
- 创建一个自定义分页器类
rom rest_framework.pagination import LimitOffsetPagination
# LimitOffsetPagination,以数据库查询的limit和offset数值作为分页条件
# limit=10&offset=0 第1页
# limit=10&offset=10 第2页
class StudentLimitPagination(LimitOffsetPagination):
default_limit = 10 # 默认的每页数据量
offset_query_param = "offset" # 地址栏代表偏移量的参数,类似上面的page
limit_query_param = "limit" # 地址栏代表数据量的参数,类似上面的size
max_limit = 10 # 限制客户端修改的每页数据量的最大值
- 视图中使用自定义分页类
from . import paginations
class Student2APIView(ListAPIView):
"""数据分页: LimitOffsetPagination"""
queryset = models.Student.objects.all()
serializer_class = serializers.StudentModelSerializer
pagination_class = paginations.StudentLimitPagination
# 访问地址:http://127.0.0.1:8000/component/student2/?limit=3
# DRF异常处理
- REST framework本身在APIView提供了异常处理,也可以额外添加属于开发者自己的逻辑代码
# exception.py
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import exception_handler
"""
全局异常处理
1. 全局异常处理函数的参数列表必须是两个,分别是exc和context
2. 全局异常处理函数的返回值必须是一个Response对象
3. 全局异常处理函数必须要在settings.py文件中进行注册
4. 全局异常处理函数的优先级要高于局部异常处理函数
5. exc 本次发生的异常实例对象
6. context 本次发生时的执行上下文, 包含了本次请求的request对象
"""
def custom_exception_handler(exc, context):
# 自定义异常处理函数
print(context)
print(context['request'].method)
# 先调用drf的异常处理函数,让drf先处理
response = exception_handler(exc, context)
# 如果drf无法处理,那么我们自己处理
if response is None:
if isinstance(exc, ZeroDivisionError):
response = Response({"error": "除数不能为0"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
elif isinstance(exc, ObjectDoesNotExist):
response = Response({"error": "数据不存在"},
status=status.HTTP_404_NOT_FOUND)
return response
- 在配置文件中声明自定义的异常处理
REST_FRAMEWORK = {
# 如果未声明自定义异常的话,drf会采用默认的方式,使用自己封装的异常处理函数
# "EXCEPTION_HANDLER": "rest_framework.views.exception_handler",
"EXCEPTION_HANDLER": "Django.exceptions.custom_exception_handler", # 自定义异常处理函数
}
- 视图代码
def get(self, request):
# print(1/0) # 这句代码会抛出python异常 ZeroDivisionError
DRFStudent.objects.get(pk=60000)
return Response({"msg": "ok"})
- REST framework内置异常处理类
| 异常类 | 描述 |
|---|---|
| APIException | drf的所有异常的父类 |
| ParseError | 解析错误 |
| AuthenticationFailed | 认证失败 |
| NotAuthenticated | 尚未认证 |
| PermissionDenied | 权限拒绝 |
| NotFound | 404 未找到 |
| MethodNotAllowed | 请求方式不支持 |
| NotAcceptable | 要获取的数据格式不支持 |
| UnsupportedMediaType | 不支持的媒体格式 |
| Throttled | 超过限流次数 |
| ValidationError | 校验失败 |
# DRF接口文档
# coreapi 的使用
- 设置接口文档访问路径
pip install coreapi
INSTALLED_APPS = [
'coreapi',
]
REST_FRAMEWORK = {
# 配置自动生成接口文档的模式
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
}
- 在总路由中添加接口文档路径,访问地址:http://127.0.0.1:8000/docs/
pip install coreapi
REST_FRAMEWORK = {
# 配置自动生成接口文档的模式
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
}
- 文档描述说明的定义位置
# 单一方法的视图,可直接使用类视图的文档字符串
class StudentListView(ListAPIView):
"""
返回所有学生信息
"""
# 包含多个方法的视图,在类视图的文档字符串中,分开方法定义
from rest_framework.generics import ListCreateAPIView
class DocumentAPIView(ListCreateAPIView):
"""
get: 获取所有学生信息
post: 添加学生信息
"""
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
from rest_framework.generics import RetrieveUpdateDestroyAPIView
class Document1APIView(RetrieveUpdateDestroyAPIView):
"""
get: 查询一个学生信息
put: 更新一个学生信息
patch: 更新一个学生信息[部分字段]
delete: 删除一个学生信息
"""
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
# 对于视图集ViewSet,在类视图的文档字符串中定义,但是应使用action名称区分
from rest_framework.decorators import action
from demo.serializers import StudentLoginModelSerializer
class DocumentAPIView(ModelViewSet):
"""
list: 获取所有学生信息
create: 添加学生信息
read: 查询一个学生信息
update: 更新一个学生信息
partial_update: 更新一个学生信息[部分字段]
delete: 删除一个学生信息
login: 学生登录
"""
queryset = Student.objects.all()
def get_serializer_class(self):
if self.action == "login":
return StudentLoginModelSerializer
return StudentModelSerializer
@action(methods=["POST"], detail=False)
def login(self, request):
return Response("ok")
注意
- 视图集ViewSet中的retrieve名称,在接口文档网站中叫做read
- 参数的Description需要在模型类或序列化器类的字段中以help_text选项定义
class Student(models.Model):
...
age = models.IntegerField(default=0, verbose_name='年龄', help_text='年龄')
...
class StudentModelSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__"
extra_kwargs = {
'classmate': {
'help_text': '班级'
}
}
# drf-yasg 的使用
drf-yasg (opens new window)是生成Swagger风格的一个drf模块,Swagger在线编辑器 (opens new window)
- 设置接口文档访问路径
pip install drf-yasg
INSTALLED_APPS = [
'drf_yasg',
]
REST_FRAMEWORK = {
# 配置自动生成接口文档的模式
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
}
- 在总路由设置,访问地址:http://127.0.0.1:8000/doc/
from django.contrib import admin
from django.urls import path, include
from rest_framework.documentation import include_docs_urls
# yasg的视图配置类,用于生成api
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
# 接口文档的视图配置
schema_view = get_schema_view(
openapi.Info(
title="drf接口文档", # 站点标题,必填
default_version='v1.0,0', # api版本,必填
description="描述信息", # 站点描述
terms_of_service='htttp://www.1ge0.com/', # 团队博客网址
contact=openapi.Contact(name="zero", url="http://www.1ge0.com/", email=""),
license=openapi.License(name="开源协议名称", url="开源协议网地") # 协议
),
public=True, # 是否外部站点
# permission_classes=(rest_framework.permissions.AllowAny) # 权限类
)
urlpatterns = [
path('admin/', admin.site.urls),
path('doc/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger'),
]
- yasg提供给开发者编写视图接口的描述信息方式与前面学习的coreapi一样
class UserAPIView(ModelViewSet):
"""
list: 返回所有学生信息
create: 创建一个学生信息
read: 读取一个学生信息
update: 更新一个学生信息
partial_update: 更新一个学生信息的单个字段
delete: 删除一个学生
"""
queryset = models.UserModel.objects.all()
serializer_class = serializers.UserModelSerializer
# 代码解读
# 序列化器(serializer)的作用是把模型实例转换为可传输的数据格式,像 JSON
class ExampleSerializer(serializers.ModelSerializer):
# SerializerMethodField:自定义字段方法,给序列化结果添加额外字段
is_confirmed = serializers.SerializerMethodField()
# self 代表类的实例对象
# 在序列化模型实例时,序列化器会自动调用 get_is_confirmed 方法,把计算结果添加到序列化数据里
def get_is_confirmed(self, instance):
# 从序列化器的上下文获取当前请求的用户对象
# self.context:DRF 的序列化器有一个 context 属性,这是一个字典
# 用于在序列化器和视图之间传递额外的数据。视图通常会把请求对象、视图实例等信息放到 context 里
user = self.context.get("request").user
# instance.states 是 Django 模型关联查询的一部分
states = instance.states.all()
return states.count() > 0
# 这是一个 ListCreateAPIView,用于处理 Example 模型的列表和创建操作
class ExampleList(generics.ListCreateAPIView):
# 指定视图使用的序列化器类。它定义了如何将模型实例
# 如 Example 模型转换为 JSON 或其他格式(序列化)
# 以及如何将客户端发送的数据转换为模型实例(反序列化)
serializer_class = ExampleSerializer
# 权限控制,来确保只有经过身份验证的用户才能访问,并且用户必须是项目管理员或项目成员(只读)
# 是 自动调用 的,你不需要手动调用它
# DRF 的视图类(如 APIView、GenericAPIView 等)会在处理请求时自动检查 permission_classes 中定义的权限
permission_classes = [IsAuthenticated & (IsProjectAdmin | IsProjectStaffAndReadOnly)]
# 是一个元组,用于指定视图使用的过滤后端。它定义了哪些过滤功能可用
# DjangoFilterBackend:提供基于字段的精确过滤功能,允许客户端通过 URL 参数对数据进行过滤,例如:?text=example 会过滤 text 字段等于 example 的记录
# filters.SearchFilter:提供全文搜索功能,允许客户端通过 search 参数对指定字段进行搜索,例如:/examples/?search=example 会在 search_fields 指定的字段中搜索包含 example 的记录
# filters.OrderingFilter:提供排序功能,允许客户端通过 ordering 参数对数据进行排序,例如:/examples/?ordering=created_at 会按 created_at 字段升序排序
filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
# 是一个元组,用于指定允许排序的字段
ordering_fields = ("created_at", "updated_at", "score")
# 是一个元组,用于指定允许搜索的字段
search_fields = ("text", "filename")
# filter_backends、ordering_fields 和 search_fields 的调用是由 DRF 的 视图类 和 过滤后端 自动处理的。它们会在 请求处理过程中 被调用
# 具体来说,ListCreateAPIView 的父类 GenericAPIView 会在 get_queryset 方法返回查询集后,自动应用 filter_backends 中定义的过滤后端
model = Example
# 指定视图使用的自定义过滤器类,通常与 DjangoFilterBackend 一起使用,允许客户端通过 URL 参数对数据进行过滤
# 通过 filterset_class,你可以定义更复杂的过滤逻辑,而不仅仅是简单的字段匹配
filterset_class = ExampleFilter
@property
def project(self):
# 通过 project 属性获取当前项目,并在 get_queryset 方法中根据用户的角色返回不同的查询集
# pk 是主键(Primary Key)的缩写,通常对应数据库表中的 id 字段
# 从 URL 中提取的参数。例如,如果 URL 是 /projects/123/examples/,那么 project_id 就是 123
# 用户访问某个 URL 时,Django 会解析 URL 并将参数存储在 self.kwargs 中
return get_object_or_404(Project, pk=self.kwargs["project_id"])
def get_queryset(self):
# self.project:是一个 属性,通过 @property 装饰器定义
# self.request.user:一个非常重要的属性,它表示当前请求的用户对象。它的作用是根据用户的身份和权限来控制访问和操作
# self.request.user.is_authenticated:检查用户是否登录
# self.request.user.username:获取用户信息
# self.request.user.has_perm('example.can_edit'):权限检查
member = get_object_or_404(Member, project=self.project, user=self.request.user)
if member.is_admin():
return self.model.objects.filter(project=self.project)
# assignments__assignee=self.request.user 是一个 跨关系查询 的语法
# 它表示通过 assignments 关系查找 assignee 字段等于当前用户的记录
queryset = self.model.objects.filter(project=self.project, assignments__assignee=self.request.user)
if self.project.random_order:
queryset = queryset.order_by("assignments__id")
return queryset
# 创建操作:自动将新创建的 Example 实例与当前项目关联
def perform_create(self, serializer):
serializer.save(project=self.project)
# 批量删除:支持通过 ids 参数指定要删除的实例
def delete(self, request, *args, **kwargs):
queryset = self.project.examples
delete_ids = request.data["ids"]
if delete_ids:
queryset.filter(pk__in=delete_ids).delete()
else:
queryset.all().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
注意
Project 是一个 Django 模型类,而不是对象或数据列表。它表示数据库中的 Project 表
# ExampleFilter 的定义
import django_filters
from examples.models import Example
class ExampleFilter(django_filters.FilterSet):
# 定义一个基于 text 字段的过滤器,支持 icontains 查找(不区分大小写的包含匹配)
text = django_filters.CharFilter(lookup_expr='icontains')
# 定义一个基于 created_at 字段的过滤器
# 支持日期范围过滤(如 ?created_at_after=2023-01-01&created_at_before=2023-12-31)
created_at = django_filters.DateFromToRangeFilter()
confirmed = BooleanFilter(field_name="states", method="filter_by_state")
# 用于处理布尔值(True/False)的过滤,通常用于过滤字段值为布尔类型的数据
# field_name 参数指定要过滤的字段,method 参数指定用于过滤的 自定义方法
confirmed = BooleanFilter(field_name="states", method="filter_by_state")
# 用于处理字符串类型的过滤,通常用于过滤字段值为字符串类型的数据
# field_name 参数指定要过滤的字段,method 参数指定用于过滤的 自定义方法
label = CharFilter(method="filter_by_label")
# queryset:当前的查询集,field_name:过滤器的字段名,value:客户端传递的过滤值
def filter_by_state(self, queryset, field_name, is_confirmed: bool):
queryset = queryset.annotate(
num_confirm=Count(
expression=field_name,
filter=Q(**{f"{field_name}__confirmed_by": self.request.user})
| Q(project__collaborative_annotation=True),
)
)
if is_confirmed:
queryset = queryset.filter(num_confirm__gte=1)
else:
queryset = queryset.filter(num_confirm__lte=0)
return queryset
def filter_by_label(self, queryset: QuerySet, field_name: str, label: str) -> QuerySet:
queryset = queryset.filter(
Q(categories__label__text=label)
| Q(spans__label__text=label)
| Q(relations__type__text=label)
| Q(bboxes__label__text=label)
| Q(segmentations__label__text=label)
)
return queryset
# 指定过滤器关联的模型和字段
class Meta:
model = Example
fields = ['text', 'created_at']
# 使用
# 建了一个 ExampleFilter 的对象
# 这个对象用于对查询集(queryset)进行过滤,过滤规则由 data 参数指定,而 request 参数则提供了请求的上下文信息
# ExampleFilter 类确实没有显式定义三个参数的构造方法(__init__)
# 但它的父类 django_filters.FilterSet 提供了这些参数的默认处理逻辑
# 因此,即使 ExampleFilter 类中没有显式定义构造方法
# 你仍然可以传递 data、queryset 和 request 参数,这些参数会被父类 FilterSet 处理
ExampleFilter(data=data, queryset=self.queryset, request=self.request)
# SimpleUI
# 安装注册
- pip安装
pip install django-simpleui
- 将simpleui加入到INSTALLED_APPS里去,放在第一行,也就是django自带admin的前面
INSTALLED_APPS = [
'simpleui', # 注意这里
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
...
]
# 常用配置
- 设置语言,替换logo,关闭第三方广告,设置主题
# 更改默认语言为中文
LANGUAGE_CODE = 'zh-hans'
# 去掉默认Logo或换成自己Logo链接
SIMPLEUI_LOGO = 'https://cdn2.iconfinder.com/data/icons/ios-7-icons/50/globe-512.png'
# 隐藏右侧SimpleUI广告链接和使用分析
SIMPLEUI_HOME_INFO = False
SIMPLEUI_ANALYSIS = False
# 设置默认主题,指向主题css文件名。Admin Lte风格
SIMPLEUI_DEFAULT_THEME = 'admin.lte.css'
# 设置默认主题,指向主题css文件名。Element-ui风格
SIMPLEUI_DEFAULT_THEME = 'element.css'
# 设置默认主题,指向主题css文件名。layui风格
SIMPLEUI_DEFAULT_THEME = 'layui.css'
# 设置默认主题,指向主题css文件名。紫色风格
SIMPLEUI_DEFAULT_THEME = 'purple.css'
- 修改管理后台的名称 admin.py
admin.site.site_header = '自动化运维后台管理'
admin.site.site_title = '自动化运维后台管理'
admin.site.index_title = '自动化运维后台管理'
admin.site.register(NormalUser, UserAdmin)
- 自定义或第三方APP名和模型名修改成中文
# app.py
from django.apps import AppConfig
class App01Config(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'app01'
# 定义这个配置即可
verbose_name = '超级用户管理'
# models.py
from django.db import models
class NormalUser(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
user_type = models.IntegerField(choices=((1, '注册用户'), (2, '普通管理员'), (3, '超级管理员')), default=1)
class Meta:
# 定义这个配置
verbose_name_plural = '用户表'
def __str__(self):
return self.username
# 自定义菜单
左侧可折叠菜单是Simple UI系统默认菜单,根据已注册的应用和模型自动生成,其中父级菜单是App名,子菜单一般是所属App的各个模型名。SimpleUI甚至会自动为你分配默认图标,比如本例的tasks的应用使用了font-awsome的fa fa-tasks。在大多数情况下,Simple UI系统默认菜单不能满足需求,这时你就需要自定义菜单了,比如添加新的选项或给菜单选项分配新的图标。
# 修改settings.py
SIMPLEUI_CONFIG = {
# 是否使用系统默认菜单。
'system_keep': False,
# 用于菜单排序和过滤, 不填此字段为默认排序和全部显示。 空列表[] 为全部不显示.
'menu_display': ['任务管理', '权限认证'],
# 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时刷新展示菜单内容。
# 一般建议关闭。
'dynamic': False,
'menus': [
{
'app': 'auth',
'name': '权限认证',
'icon': 'fas fa-user-shield',
'models': [
{
'name': '用户列表',
'icon': 'fa fa-user',
'url': 'auth/user/'
},
{
'name': '用户组',
'icon': 'fa fa-th-list',
'url': 'auth/group/'
}
]
},
{
'name': '任务管理',
'icon': 'fa fa-th-list',
'models': [
{
'name': '任务列表',
# 注意url按'/admin/应用名小写/模型名小写/'命名。
'url': '/admin/tasks/task/',
'icon': 'fa fa-tasks'
},
]
},
]
}
# 自定义首页
SimpleUI默认首页由快捷链接和最近动作组成,我们可以将其隐藏,并将其链接到其它url
# settings.py
# 隐藏首页的快捷操作和最近动作
SIMPLEUI_HOME_QUICK = False
SIMPLEUI_HOME_ACTION = False
# 修改左侧菜单首页设置
SIMPLEUI_HOME_PAGE = 'https://www.baidu.com' # 指向页面
SIMPLEUI_HOME_TITLE = '百度欢迎你!' # 首页标题
SIMPLEUI_HOME_ICON = 'fa fa-code' # 首页图标
# 设置右上角Home图标跳转链接,会以另外一个窗口打开
SIMPLEUI_INDEX = 'https://www.baidu.com'
# 自定义成自己设计的首页
实际应用中后台首页通常是控制面板,需要用图表形式展示各种关键数据,这时就需要重写首页了。这里主要有两种实现方法。第一种是重写simpleui自带的home.html, 另一种自己编写一个控制面板的页面,然后设置首页指向它, 个人倾向于第二种, 因为它完全不涉及改动simpleui的源码。
我们现在开始使用Django编写一个用于显示控制面板的页面,用于在首页显示注册用户数量及任务数量。
- URL路由及对应的视图函数如下所示:
# app01/urls.py
urlpatterns = [
path('dashboard/', dashboard, name='dashboard'),
]
# app01/views.py
def dashboard(request):
user_count = User.objects.count()
book_count = Book.objects.count()
context = {'user_count': user_count, 'book_count': book_count}
return render(request, 'dashboard.html', context)
# app01/urls.py
urlpatterns = [
path('dashboard/', dashboard, name='dashboard'),
]
# app01/views.py
def dashboard(request):
user_count = User.objects.count()
book_count = Book.objects.count()
context = {'user_count': user_count, 'book_count': book_count}
return render(request, 'dashboard.html', context)
- 模板文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>控制面板</title>
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Theme style -->
<link rel="stylesheet"
href="https://adminlte.io/themes/AdminLTE/bower_components/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://adminlte.io/themes/AdminLTE/dist/css/AdminLTE.min.css">
</head>
<body>
<div class="wrapper">
<!-- Main content -->
<section class="content">
<div class="container-fluid">
<!-- Small boxes (Stat box) -->
<div class="row">
<div class="col-sm-3">
<!-- small box -->
<div class="small-box bg-info">
<div class="inner">
<h3>{{ user_count }}</h3>
<p>用户总数</p>
</div>
<div class="icon">
<i class="ion ion-bag"></i>
</div>
<a href="#" class="small-box-footer">更多信息 <i class="fas fa-arrow-circle-right"></i></a>
</div>
</div>
<!-- ./col -->
<div class="col-sm-3">
<!-- small box -->
<div class="small-box bg-success">
<div class="inner">
<h3>{{ book_count }}</h3>
<p>图书总数</p>
</div>
<div class="icon">
<i class="ion ion-stats-bars"></i>
</div>
<a href="#" class="small-box-footer">更多信息 <i class="fas fa-arrow-circle-right"></i></a>
</div>
</div>
<!-- ./col -->
</div>
</div>
</section>
</div>
</body>
- 修改settings.py
# 修改首页设置, 指向新创建的控制面板
SIMPLEUI_HOME_PAGE = '/app01/dashboard/'
SIMPLEUI_HOME_TITLE = '控制面板!'
SIMPLEUI_HOME_ICON = 'fa fa-eye'
← 基本介绍