Django REST framework完全入门
Django REST framework
一个强大灵活的Django
工具包,提供了便捷的REST API
开发框架
我们用传统的django
也可以实现REST风格的api
,但是顶不住Django REST framework
实现起来太简单了
什么REST API风格
主要需要注意这几个:
- api部署的域名, 主域名或者专有域名
- 版本, 通过url地址或者请求头accept
- 路径, 只能有名词, 不能有动词
- http请求动词, get, post, update, delete
- 状态, 200, 201, 204, 400, 401, 403, 404
- 返回json格式数据
例子(仅作参考):
功能 | 路径 | 请求方式 | 响应状态码(成功) | 响应体(成功) |
---|---|---|---|---|
获取所有的书籍 | /books |
get | 200 OK | 全部书籍 |
创建书籍 | /books |
post | 201 Created | 已经创建的书籍 |
获取单个书籍 | /books/{id} |
get | 200 OK | 单个书籍 |
修改书籍 | /books/{id} |
put | 201 Created | 已经修改的单个书籍 |
删除书籍 | /books/{id} |
delete | 204 No Content | 无 |
详细的可以看这里:RESTful 架构详解
关于返回什么,可以看这里:通用设计规则
简单使用流程
在我看来,Django RST framework
的主要工作就是对传统django
中的view
和form
在进行了通用性设计。由于使用REST API,我们可以统一使用一样的请求方式,请求参数和请求体,而响应体和状态码也是确定。所以Django RST framework
的复用性极高,而且Django RST framework
提供了从简单到复杂的封装类,我们只需要直接使用这些类或继承这些类重写部分方法,就可以享受到便利性和可扩展性。
为了显示Django RST framework
简单易用,用展示封装程度比较高的类实现一个五个基本功能(get一个、get全部、post创建、put修改单个、delete删除单个)
-
安装
Django RST framework
$pip install djangorestframework
-
修改setting文件
在settings.INSTALLED_APPS
中添加rest_framework
注:添加这个app可以为我们提供测试页面, 假如是使用postman等工具进行测试的话可以不用添加
-
创建model
from django.db import models class Author(models.Model): aid = models.AutoField(primary_key=True) name = models.CharField(max_length=32, verbose_name="姓名") phone = models.CharField(max_length=11, verbose_name="手机号码") address = models.CharField(max_length=63, verbose_name="地址") def __str__(self): return self.name
-
执行迁移命令
$python manage.py makemigrations $python manage.py migrate
-
定义序列化器
在app目录下创建一个serializers.py
,在里面定义序列化器:from rest_framework.serializers import ModelSerializer from rest_test.models import Author class AuthorSerialize(ModelSerializer): class Meta: model = Author fields = "__all__"
-
使用视图集
在views.py
中,导入并使用之前的模型和序列化器:from rest_framework.viewsets import ModelViewSet from rest_test.serializers import AuthorSerialize from rest_test.models import Author class AuthorModelViewSet(ModelViewSet): queryset = Author.objects.all() serializer_class = AuthorSerialize
-
定义路由规则
在根路由中定义urlpatterns
:"""djangotest URL Configuration """ from django.contrib import admin from django.urls import path from rest_test import views from rest_framework.routers import SimpleRouter urlpatterns = [ path('admin/', admin.site.urls), ] router = SimpleRouter() # 添加自己的url router.register('authors', views.AuthorModelViewSet, basename="authors") urlpatterns += router.urls
-
启动django,并访问
/authors
我们就可以看到这样的页面,可以对数据进行增删改查了:
序列化器
序列化器的主要作用就是进行数据转换,即序列化(python的数据类型转换为json等数据类型)和反序列化(json等数据类型转换为python的数据类型)
在反序列化的过程中我们还可以手动验证数据是否合法, 可以与model相结合,完成对数据库的操作
所以说,序列化器和django
的Form
非常像,所以假如会django
的Form
的话,应该很好理解
普通的序列化器
在定程度上可以类比
dango.forms.Form
定义起来非常简单,字段名称对应的是model中的字段名,字段类型和model中的字段有一定的对应关系,参数是一些约束条件和描述信息, 见: 字段和参数
from rest_framework import serializers
class AuthorSerialize(serializers.Serializer):
aid = serializers.IntegerField(read_only=True,label="作者id")
name = serializers.CharField(max_length=32, label="姓名")
phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
address = serializers.CharField(max_length=63, label="地址")
字段和参数
字段
这里的参数指的是字段自带的参数,不是像
required
这样都可以用的参数
仅列出常用的
全部字段都是rest_framework.serializers
下的
字段名称 | 描述 | 参数 | 备注 |
---|---|---|---|
BooleanField |
布尔值 | 无 | 无论有无指定,默认值都为False , NullBooleanField 可以接收None 的值 |
CharField |
文本 | max_length min_length allow_blank (空字符串是否为有效值)trim_whitespace (True时修剪前后的空白,默认为True |
|
EmailField |
电子邮箱 | max_length min_length allow_blank (默认为False ) |
|
RegexField |
文本,通过正则匹配 | regex (正则表达式) max_length min_length allow_blank (默认为False ) |
|
SlugField |
regex 为[a-zA-Z0-9_-]+ 的RegexField 字段 |
就比RegexField 少了regex 参数 |
|
URLField |
//<host>/<path> 格式的RegexField |
同SlugField |
|
UUIDField |
uuid 字段 |
format |
该字段的format 参数指定的是uuid 的格式,见UUIDField |
IntegerField |
整数 | max_value min_value |
|
FloatField |
小数 | max_value min_value |
|
DateTimeField |
时间 年月日时分秒 | format (序列化的时间格式) input_formats (反序列化的时间格式) default_timezone (时区) |
参数
和
form
一样,这里指的是通用的参数,不是字段自带的参数
参数 | 描述 |
---|---|
read_only |
只读,不能反序列化,如主键,默认是False |
Write_only |
只写,不能序列化,只在更新和创建数据时使用,默认是False |
required |
反序列化必须提供的字段,默认为True |
default |
默认值,在没有提供值时使用,默认不设置 |
allow_null |
允许值为None (一般情况下,如果向序列化字段传递None 是会抛出异常的),默认是False |
validators |
指定验证器列表 |
error_message |
错误代码到错误消息的字典 |
label |
提供可读性高的字段名称 |
help_text |
对字段进行详细描述 |
全部字段和参数可以查看官方文档:Serializer fields (中文)、Serializer fields (英文)
序列化操作
书接上回,我们已经在serializers.py
中定义了一个AuthorSerialize
,下面就利用该序列化器进行序列化,即python的转换为json等数据。
序列化一个
步骤:
- 导入
AuthorSerialize
和Author
模型 - 使用
get
获取Author
的数据 - 将数据传给
AuthorSerialize
的instance参数
- 使用
.data
属性(是一个静态属性)获得序列化后的数据
注意:假如你的数据库中没有数据,应该提前将数据创建好,可以用SQL命令也可以用
django ORM
或借助其他工具
使用django shell执行命令:
$ python manage.py shell
Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from rest_test.serializers import AuthorSerialize
>>> from rest_test.models import Author
>>>
>>> a = Author.objects.get(pk=4)
>>> t = AuthorSerialize(instance=a)
>>> t.data
{'aid': 4, 'name': '张三', 'phone': '1388888888', 'address': '广东省广州市xxxxx'}
>>>
暂时用命令行的方式序列化
序列化多个
序列化器一般是不能序列化多个数据的,需要我们指定参数many=True
。同样的,我们先使用django shell
执行命令:
$ python manage.py shell
Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from rest_test.serializers import AuthorSerialize
>>> from rest_test.models import Author
>>>
>>> a = Author.objects.all()
>>> t = AuthorSerialize(instance=a, many=True)
>>>
>>> t.data
[OrderedDict([('aid', 4), ('name', '张三'), ('phone', '138888888888'), ('address', '广东省广州市xxxx')]),
OrderedDict([('aid', 5), ('name', '李四'), ('phone', '1388888888'), ('address', '广东省广州市xxxxx')]),
OrderedDict([('aid', 6),('name', '王五'), ('phone', '1388888888'), ('address', '广东省广州市xxxxx')])]
可以看到,data
的值是一个OrderedDict
数据类型
外键序列化
由于外键在序列化时不知道是使用主键、__str__
或整个主键的数据中的一个,所以需要我们手动指定不同的字段实现。
假如我们添加了一个model:
class Author(models.Model):
aid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32, verbose_name="姓名")
phone = models.CharField(max_length=11, verbose_name="手机号码")
address = models.CharField(max_length=63, verbose_name="地址")
def __str__(self):
return self.name
class Books(models.Model):
bid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32, verbose_name="书名")
description = models.CharField(max_length=128, verbose_name="书籍描述")
author = models.ForeignKey(Author, on_delete=models.CASCADE)
def __str__(self):
return f"{self.author.name}: 《{self.name}》"
子表序列化
子表序列化比较简单,因为字段定义子表哪边,所以可以通过字段,只需要确定返回主表的哪些值即可。
-
返回外键的主键
PrimaryKeyRelatedField
定义序列化器:class BookSerialize(serializers.Serializer): bid = serializers.IntegerField(read_only=True) name = serializers.CharField(max_length=32, label="书名") description = serializers.CharField(max_length=128, label="书籍描述") author = serializers.PrimaryKeyRelatedField(read_only=True)
PrimaryKeyRelatedField必须指定read_only为True,或指定queryset=Books.objects.all(),否则会报错
使用
django shell
测试:$ python manage.py shell Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> from rest_test.serializers import BookSerialize >>> from rest_test.models import Books >>> >>> b = Books.objects.all() >>> s = BookSerialize(instance=b, many=True) >>> s.data [OrderedDict([('bid', 1), ('name', 'book1'), ('description', 'very good'), ('author', 4)]), OrderedDict([('bid', 2), ('name', 'book2'), ('description', '好书推荐'), ('author', 4)])] >>>
-
返回外键的
__str__
方法StringRelatedField
修改author的字段类型,让它返回的是主表的__str__
方法:class BookSerialize(serializers.Serializer): bid = serializers.IntegerField(read_only=True) name = serializers.CharField(max_length=32, label="书名") description = serializers.CharField(max_length=128, label="书籍描述") author = serializers.StringRelatedField(read_only=True)
可以不设置
read_only
验证:
$ python manage.py shell Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> from rest_test.serializers import BookSerialize >>> from rest_test.models import Books >>> >>> b = Books.objects.all() >>> s = BookSerialize(instance=b, many=True) >>> s.data [OrderedDict([('bid', 1), ('name', 'book1'), ('description', 'very good'), ('author', '张三')]), OrderedDict([('bid', 2), ('name', 'book2'), ('description', '好书推荐'), ('author', '张三')])]
-
返回整个主表的数据
只需要字段的值改为主表的序列化器即可:class AuthorSerialize(serializers.Serializer): aid = serializers.IntegerField(read_only=True) name = serializers.CharField(max_length=32, label="姓名") phone = serializers.CharField(min_length=11, max_length=11, label="手机号码") address = serializers.CharField(max_length=63, label="地址") class BookSerialize(serializers.Serializer): bid = serializers.IntegerField(read_only=True) name = serializers.CharField(max_length=32, label="书名") description = serializers.CharField(max_length=128, label="书籍描述") author = AuthorSerialize(read_only=True)
验证:
$ python manage.py shell Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> from rest_test.serializers import AuthorSerialize, BookSerialize >>> from rest_test.models import Author, Books >>> >>> b = Books.objects.all() >>> s = BookSerialize(instance=b, many=True) >>> s.data [OrderedDict([('bid', 1), ('name', 'book1'), ('description', 'very good'), ('author', OrderedDict([('aid', 4), ('name', '张三'), ('phone', '138888888888'), ('address', '广东省广州市xxxxx')]))]), OrderedDict([('bid', 2), ('name', 'book2'), ('description', '好书推荐'), ('author', OrderedDict([('aid', 4), ('name', '张三'), ('phone', '138888888888'), ('address', '广东省广州市xxxxx')]))])]
主表序列化
由于主表不能直接访问子表,但是我们知道django
为我们提供了表名(小写)_set
管理器,这样我们就可以通过该管理器访问子表了。
假如指定了
related_name
参数的话,该管理器的名称就是related_name
的值
如可以改成这样:author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="author")
另,关于管理器,可以查看django的文档:“反向” 关联和管理器
为了演示,使用默认的管理器
与子表类似,我们需要指定返回什么值(__str__
或主键),使用的是相同的字段,但是要指定many参数!!
-
__str__
StringRelatedField
注意
many
参数序列化器:
class AuthorSerialize(serializers.Serializer): aid = serializers.IntegerField(read_only=True) name = serializers.CharField(max_length=32, label="姓名") phone = serializers.CharField(min_length=11, max_length=11, label="手机号码") address = serializers.CharField(max_length=63, label="地址") books_set = serializers.StringRelatedField(read_only=True, many=True)
演示:
$ python manage.py shell Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> from rest_test.serializers import AuthorSerialize >>> from rest_test.models import Author >>> >>> a = Author.objects.get(pk=4) >>> t = AuthorSerialize(instance=a) >>> t.data {'aid': 4, 'name': '张三', 'phone': '138888888888', 'address': '广东省广州市xxxxx', 'books_set': ['张三: 《book1》', '张三: 《book2》']}
-
主键
PrimaryKeyRelatedField
注意
many
参数序列化器:
class AuthorSerialize(serializers.Serializer): aid = serializers.IntegerField(read_only=True) name = serializers.CharField(max_length=32, label="姓名") phone = serializers.CharField(min_length=11, max_length=11, label="手机号码") address = serializers.CharField(max_length=63, label="地址") books_set = serializers.PrimaryKeyRelatedField(read_only=True,many=True)
演示:
$ python manage.py shell Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> from rest_test.serializers import AuthorSerialize >>> from rest_test.models import Author >>> >>> a = Author.objects.get(pk=4) >>> >>> t = AuthorSerialize(instance=a) >>> t.data {'aid': 4, 'name': '张三', 'phone': '138888888888', 'address': '广东省广州市xxxxx', 'books_set': [1, 2]} >>>
反序列化操作
上面演示了如何将model序列化成 字符串或OrderedDict
,而反序列化化就是将json等数据,变成序列化器中的字段的数据类型,就好比我们使用form
处理数据一样。
一般操作
此部分还不涉及将数据保存到数据库
流程:
- 获得数据
- 将数据传入到序列化器的
data
参数中 - 调用返回对象的
is_valid()
方法
由于还未讲到视图,所以先用一个字典数据代替从浏览器传过来的数据。
$python manage.py shell
Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from rest_test.serializers import AuthorSerialize, BookSerialize
>>> from rest_test.models import Author, Books
>>>
>>> data = {
... "name": "龙傲天",
... "phone": "18888888888",
... "address": "xxx省xx市",
... }
>>>
>>> result = AuthorSerialize(data=data)
>>> result.is_valid(raise_exception=True)
True
# ################ 这里故意用一个错误的数据 #############################
>>> data = {
... "name": "龙傲天",
... "phone": "188888888889",
... "address": "xxx省xx市",
... }
>>> result = AuthorSerialize(data=data)
>>> result.is_valid(raise_exception=True)
# 报错
rest_framework.exceptions.ValidationError: {'phone': [ErrorDetail(string='Ensure this field has no more than 11 characters.', code='max_length')]}
>>>
is_valid
和django form
的is_valid
一样,可以检验数据是否符合我们在序列化器中定义的一样,检验成功是返回True
,否则返回False
。- 假如指定
raise_exception
为True
,那么当数据不合法时,抛出异常,我们可以通过error_messages
参数提示信息(见: 自定义错误信息)。 - 假如不想要抛出异常,我们也可以通过
.errors
获取错误信息(这是一个OrderedDict
的子类对象)如这里的result.errors
。
利用序列化器进行创建和更新数据
终于讲到如何将数据保存到数据库了。
用过django ModelForm
的都知道,只要将数据传入到form
中,假如数据通过,就可以通过.save()
方法将数据保存到数据库,指定instance
参数可一个更新数据。而序列化器的使用方式样是这样,不过我们现在用的是叫较为低级的序列化器,需要我们自定义创建数据和更新数据的逻辑。
创建数据
使用步骤:
- 获得数据
- 将数据传入到序列化器的
data
参数中 - 调用返回对象的
is_valid()
方法 - 为序列化器定义
create
方法 - 调用
save
方法,执行创建数据的函数(会调用create
函数)
这里的create
函数需要我们自定义,在序列化器中:
class AuthorSerialize(serializers.Serializer):
aid = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=32, label="姓名")
phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
address = serializers.CharField(max_length=63, label="地址")
books_set = serializers.StringRelatedField(read_only=True, many=True)
def create(self, validated_data):
"""
创建数据
:param validated_data: 已经通过验证的数据
:return: Author instance
"""
author = Author.objects.create(**validated_data)
# 不要忘记返回数据了
return author
可以说非常简单,下面有django shell
测试一下:
$python manage.py shell
Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from rest_test.serializers import AuthorSerialize
>>>
>>> data = {
... "name": "龙傲天",
... "phone": "18888888888",
... "address": "xxx省xx市",
... }
>>> result = AuthorSerialize(data=data)
>>> result.is_valid(raise_exception=True)
True
>>> result.save()
<Author: 龙傲天>
用PyCharm自带的Database工具,我们可以看到的确已经保存到了数据库中:
更新数据(全部)
PUT请求方式是更新全部数据
使用步骤:
- 获得数据
- 将数据传入到序列化器的
data
参数中, 要更新的数据传入instance
参数 - 调用返回对象的
is_valid()
方法 - 为序列化器定义
update
方法 - 调用
save
方法,执行创建数据的函数(会调用update
函数)
只要序列化器中同时传入data
和instance
参数,它就会调用更新的方法(update
)
注意:
data
参数传入数据,instance
参数传入待更新的model对象
class AuthorSerialize(serializers.Serializer):
aid = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=32, label="姓名")
phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
address = serializers.CharField(max_length=63, label="地址")
books_set = serializers.StringRelatedField(read_only=True, many=True)
def update(self, instance, validated_data):
"""
更新数据
由于传入的不是QuerySet对象,所以不能用update方法
:param instance: Author对象
:param validated_data: 已经验证的数据
:return: instance
"""
instance.name = validated_data.get("name")
instance.phone = validated_data.get("phone")
instance.address = validated_data.get("address")
instance.save() # 不要忘记保存了
return instance
$ python manage.py shell
Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from rest_test.serializers import AuthorSerialize
>>> from rest_test.models import Author
>>>
>>> author = Author.objects.get(pk=10)
>>>
>>> data = {
... "name": "龙傲天",
... "phone": "18888888888",
... "address": "广东省广州市",
... }
>>> result = AuthorSerialize(data=data, instance=author)
>>> result.is_valid(raise_exception=True)
True
>>> result.save()
<Author: 龙傲天>
>>>
更新数据(部分)
PATCH请求方式是更新部分数据
上面我们只更新了地址的信息,却把其它不用改的信息也一并穿了进去,这显然不是我们预期的,但由于序列化器的字段的required
默认为True
,除非我们设置read_only=True
或指定一个默认值,否则会报错,但设置了这些又会影响到我们正常创建数据的验证。
所以,序列化器提供了一个参数:partial
用来说明这是更新部分信息的。在使用时和上面的一样,只是多了个参数而已。不过我们需要修改一下update
函数, 因为值可能为None
, 会报错,可以指定get
的默认值,也可以用反射的方法:
class AuthorSerialize(serializers.Serializer):
aid = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=32, label="姓名")
phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
address = serializers.CharField(max_length=63, label="地址")
books_set = serializers.StringRelatedField(read_only=True, many=True)
def update(self, instance, validated_data):
"""
更新数据
:param instance: Author对象
:param validated_data: 已经验证的数据
:return: instance
"""
for field in validated_data:
if not hasattr(instance, field):
continue
setattr(instance, field, validated_data.get(field))
instance.save() # 不要忘记保存了
return instance
来试一下吧:
$ python manage.py shell
Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from rest_test.serializers import AuthorSerialize
>>> from rest_test.models import Author
>>>
>>> data = {
... "address": "广东省广州市xxxxx",
... }
>>> author = Author.objects.get(pk=10)
>>> result = AuthorSerialize(data=data, instance=author, partial=True)
>>> result.is_valid(raise_exception=True)
True
>>> result.save()
<Author: 龙傲天>
Model的序列化器
DRF
有一个model序列化器:ModelSerializer
, 其在一定程度上与dango.forms.ModelForm
十分相似
在上面使用的例子就用到了该序列化器
说到底,它就是将干了这几件事:
- 我们的model字段根据对应关系转化为serializers中的字段
- 将model字段的约束条件变为serializers字段的约束条件
- 内置了
create
方法和update
方法
简单使用
简直是简简单单:
class AuthorModelSerialize(serializers.ModelSerializer):
class Meta:
model = Author
fields = "__all__"
Meta类
从例子中看出,Meta
类是关键,它决定着 要序列化的字段、用哪个模型、错误信息、约束条件等重要的参数。
下面我们看看Meta类可以指定什么内容:
还是那句话,真的和
ModelForm
十分相似
model
指定用哪个Modelfields
使用模型中的哪些字段,推荐利用元组显式指定,但可以将值指定为__all__
(表示全部字段)
不能和exclude
一起使用,否则报错:Cannot set both ‘fields’ and ‘exclude’ options on serializer xxx.exclude
不使用模型中的哪些字段
不能和fields
一起使用,否则报错:Cannot set both ‘fields’ and ‘exclude’ options on serializer xxx.depth
指定关联深度,是一个数字
注意,关联字段默认返回的是主键,即rest_framework.serializers.PrimaryKeyRelatedField
read_only_fields
只读字段,主键默认只读extra_kwargs
指定额外的参数,该参数可以传给字段
例子:
class AuthorModelSerialize(serializers.ModelSerializer):
class Meta:
model = Author # 指定使用Author这个模型
# 指定字段,注意:books_set
fields = ("aid", "name", "phone", "address", "books_set")
# 不使用模型中的哪些字段
# 不能一起使用,这里注释掉
# exclude = ("phone", )
depth = 1 # 关联深度为1
# 设置只读字段
read_only_fields = ("aid", "books_set")
# 设置额外参数,该参数是给字段的
# 注意格式
extra_kwargs = {
"name": {
"read_only": True
}
}
我们同样用django shell
查看:
$ python manage.py shell
Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from rest_test.serializers import AuthorModelSerialize
>>> from rest_test.models import Author
>>>
>>> a = Author.objects.all()
>>> res = AuthorModelSerialize(instance=a, many=True)
>>> res
AuthorModelSerialize(instance=<QuerySet [<Author: 张三>, <Author: 李四>, <Author: 王五>, <Author: 龙傲天>]>, many=True):aid = IntegerField(read_only=True) name = CharField(label='姓名', read_only=True) address = CharField(label='地址', max_length=63) books_set = NestedSerializer(many=True, read_only=True): bid = IntegerField(read_only=True) name = CharField(label='书名', max_length=32) description = CharField(label='书籍描述', max_length=128) author = PrimaryKeyRelatedField(queryset=Author.objects.all())
extra_kwargs
由于
ModelSerializer
直接用model的字段类型,肯定不能很好的做好条件约束
因此DRF
提供了extra_kwargs
属性, 并且错误信息也通过该参数进行自定制
class AuthorModelSerialize(serializers.ModelSerializer):
class Meta:
model = Author # 指定使用Author这个模型
# 指定字段,注意:books_set
fields = ("aid", "name", "phone", "address", "books_set")
extra_kwargs = {
"phone": {
"min_length": 11,
"max_length": 11
}
}
像普通Serialize那样编写
我们可以自定义一些额外的字段或不用extra_kwargs
属性而直接model的字段进行定义:
class AuthorModelSerialize(serializers.ModelSerializer):
phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
# write_only只反序列化,不入库
note = serializers.CharField(max_length=128, label="额外的字段,不入库", write_only=True)
class Meta:
model = Author # 指定使用Author这个模型
# 指定字段,注意:books_set
fields = ("aid", "name", "address", "books_set")
指定外键返回类型
外键默认返回主键,但通过serializer_related_field
属性指定:
class AuthorModelSerialize(serializers.ModelSerializer):
serializer_related_field = serializers.StringRelatedField
# 返回 __str__
class Meta:
model = Author
fields = ("aid", "name", "address", "books_set")
验证数据
这部分内容也是和Model一样
验证一个字段
和form
一样,只要在序列化器重定义一个validate_xxx
方法(xxx
指的是字段名)即可验证单个字段,如下面的例子:
class AuthorSerialize(serializers.Serializer):
aid = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=32, label="姓名")
phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
address = serializers.CharField(max_length=63, label="地址")
books_set = serializers.StringRelatedField(read_only=True, many=True)
def validate_name(self, value:str):
# 1. 判读
if not value.startswith("lcz"):
# 抛出ValidationError异常
raise serializers.ValidationError("必须以lcz开头", "startError")
# 2.返回结果
return value
ValidationError
是一个异常,有两个参数detail
和code
,前者接收异常的描述信息,后者接收异常名(默认为"invalid"
)
该方法调用is_valid
时自动执行
记得返回value
验证一下:
>>> data = {
... "name": "xxx",
... "phone": "12345678910",
... "note": "test",
... "address": "广东省广州市xxxxx1",
... }
>>> result = AuthorSerialize(data=data)
>>> result.is_valid()
False
>>> result.errors
{'name': [ErrorDetail(string='必须以lcz开头', code='startError')]}
验证多个字段
需要重写validate
方法:
class AuthorSerialize(serializers.Serializer):
aid = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=32, label="姓名")
phone = serializers.CharField(min_length=11, max_length=11, label="手机号码")
address = serializers.CharField(max_length=63, label="地址")
books_set = serializers.StringRelatedField(read_only=True, many=True)
def validate(self, attrs):
"""
:param attrs: 外界传入的需要校验的字典
:return: attrs
"""
# 1. 判断name和address(随便举个例子)
if attrs["name"].startswith("lcz") and attrs.startswith("广东省"):
# 2.返回结果
return attrs
# 否则抛出异常
raise serializers.ValidationError("只有lcz开头且地址是广东省的才能通过验证")
自定义验证器
分两步:
- 定义
使用验证器时,传入的数据已经转换为python的数据类型了def check_bpub_date(date): if date.year < 2015: raise serializers.ValidationError("日期不能小于2015年") return date
- 使用
通过validators
参数指定def check_pub_date(date): if date.year < 2015: raise serializers.ValidationError("日期不能小于2015年") return date class BookSerialize(serializers.Serializer): pub_date = serializers.DateTimeField(validators=[check_pub_date]
注:
ModelSerialize
通过extra_kwargs
属性指定
自定义错误信息
- 内置错误通过
error_messages
参数指定 - 手动抛出的异常,通过异常信息指定
错误信息是可以用一个函数专门处理的,详情请看: 异常处理
如:
from rest_framework import serializers
class AuthorSerialize(serializers.Serializer):
aid = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=32, label="姓名")
phone = serializers.CharField(min_length=11, max_length=11, label="手机号码",
error_messages={"min_length": "号码太短了", "max_length": "号码太长了"})
address = serializers.CharField(max_length=63, label="地址")
books_set = serializers.StringRelatedField(read_only=True, many=True)
和使用ValidationError
的值定义为错误信息:
def check_bpub_date(date):
if date.year < 2015:
raise serializers.ValidationError("日期不能小于2015年")
return date
视图
Django REST framework
的视图种类比序列化器还多,从一级视图到视图集,每一个都是在之前的视图基础上进行了封装,至于用哪个,全凭自己的喜好了。
详情见下图:
Django REST framework
的视图都是用CBV
的写法
由于REST API
的url是固定的,所以可以把视图分为 列表视图和详情视图,可能有点抽象,举个例子:
- 列表视图:
GET /books
(获取所有的书籍);POST /books
(创建书籍) - 详情视图:
GET /books/{id}
(获取单个书籍 );PUT /books/{id}
(修改书籍);DELETE /books/{id}
(删除书籍)
可以感受出来了吧,对数据做增删改查需要两个路由5个函数, 因此我们可以将由于视图类分为 列表视图 和 详情视图两个视图类,不过我们可以利用 视图集 将视图类变为一个
request和responses
在django
中任何视图必然至少接收一个request
,返回一个response
,Django REST framework
也不例外。
request
DRF
的Request
类扩展了django
默认的HttpReques
t,它主要有两个属性:
Request.data
得到请求体的数据(表单数据和JSON数据都行)Request.query_params
得到查询数据(任何HTTP
方法类型可能包括查询参数,而不仅仅是GET
请求)
除此外还有关于认证等属性,由于与
django
原来的request
类似, 故这里不展开
responses
Response
的签名:Response(data, status=None, template_name=None, headers=None, content_type=None)
一般我们只需要用到前两个参数即可:
data
:response
的数列化数据status
:response
的状态码。默认是200
template_name
:HTMLRenderer
选择要使用的模板名称headers
: `响应头,是一个字典content_type
:response
的内容类型。通常由渲染器自行设置
常用状态码
你可以在
rest_framework.status
中找到所有状态码
代码变量 | 对应状态码 | 说明 |
---|---|---|
HTTP_200_OK |
200 | 请求成功 |
HTTP_201_CREATED |
201 | 该请求已成功,并因此创建了一个新的资源, 一般为POST 或PUT 成功后返回 |
HTTP_204_NO_CONTENT |
204 | 服务器成功处理了请求,但不需要返回任何实体内容, 一般为DELETE 成功后返回 |
HTTP_400_BAD_REQUEST |
400 | 语义有误,当前请求无法被服务器理解 或 请求参数有误 |
HTTP_401_UNAUTHORIZED |
401 | 当前请求需要用户验证 |
HTTP_403_FORBIDDEN |
403 | 服务器已经理解请求,但是拒绝执行它 |
HTTP_404_NOT_FOUND |
404 | 请求失败,请求所希望得到的资源未被在服务器上发现 |
HTTP_500_INTERNAL_SERVER_ERROR |
500 | 服务器遇到了不知道如何处理的情况 |
HTTP_502_BAD_GATEWAY |
502 | 服务器作为网关需要得到一个处理这个请求的响应,但是得到一个错误的响应 |
HTTP_503_SERVICE_UNAVAILABLE |
503 | 服务器没有准备好处理请求。 常见原因是服务器因维护或重载而停机 |
一级视图APIView
rest_framework.views.APIView
是django.views.generic.base.View
的子类,用起来和普通的传统的继承View的写法差不多
列表视图
列表视图的写法:
- 定义一个类,继承
APIView
- 定义方法(
get
,post
) - 从
request
中获取数据 - 利用 序列化器 序列化数据或检验数据入库
- 返回(除非抛出异常,否则返回的都是
data
的数据)
from rest_framework.views import APIView
from rest_framework import status
from rest_framework.response import Response
from rest_test.serializers import AuthorModelSerialize
from rest_test.models import Author
class AuthorListView(APIView):
def get(self, request):
"""
获取全部数据
:param request:
:return:
"""
authors = Author.objects.all()
# instance指定要序列化的对象
# 多个数据用many=True
serialize = AuthorModelSerialize(instance=authors, many=True)
return Response(serialize.data, status=status.HTTP_200_OK)
def post(self, request):
"""
创建数据
:param request:
:return:
"""
serialize = AuthorModelSerialize(data=request.data)
# raise_exception=True,假如报错,会自动处理
# 返回状态码:400,且数据的形式:{"phone": ["Ensure this field has no more than 11 characters."]}
serialize.is_valid(raise_exception=True)
serialize.save()
return Response(serialize.data, status=status.HTTP_201_CREATED)
别忘了写路由规则:
from django.contrib import admin
from django.urls import path
from rest_test import views
urlpatterns = [
path('admin/', admin.site.urls),
# 同样调用.as_view方法
path("authors/", views.AuthorListView.as_view())
]
详情视图
在原来的基础上多了主键,用来操作单个数据:
- 同样继承
APIView
- 定义
get
put
delete
四个方法 - 使用
get_object_or_404
取得要操作的对象(不用我们处理异常) - 返回数据
from rest_framework.views import APIView
from rest_framework import status
from rest_framework.response import Response
from rest_test.serializers import AuthorModelSerialize
from rest_test.models import Author
from django.shortcuts import get_object_or_404
class AuthorDetailView(APIView):
def get(self, request, pk):
# 这是django的内置方法,不会的可以查一下
author = get_object_or_404(Author, pk=pk)
serialize = AuthorModelSerialize(instance=author)
return Response(serialize.data, status=status.HTTP_200_OK)
def put(self, request, pk):
author = get_object_or_404(Author, pk=pk)
# 兼容patch方法的部分更新,一般我们都不会在定义个patch方法
serialize = AuthorModelSerialize(instance=author, data=request.data, partial=True)
serialize.is_valid(raise_exception=True)
serialize.save()
return Response(serialize.data, status.HTTP_201_CREATED)
def delete(self, request, pk):
author = get_object_or_404(Author, pk=pk)
author.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
路由:
from django.contrib import admin
from django.urls import path
from rest_test import views
urlpatterns = [
path('admin/', admin.site.urls),
path("authors/", views.AuthorListView.as_view()),
path("authors/<int:pk>/", views.AuthorDetailView.as_view())
]
二级视图GenericAPIView
一级视图已经可以很好地帮我们完成任务了,但是上面地代码明显有冗余部分:
如,详情视图调用3次get_object_or_404
,所以Django REST framework
有更好地方法定义。
介绍
GenericAPIView
是APIView
的子类,它将我们用到的序列化器和模型已经获取model对象的方法进行了集成,即添加了常用的行为和属性。虽然我们也可以做到这样,但是它已经做好了,那么我们只需要了解规则就可以直接使用了。
GenericAPIView
有4个重要属性和3个重要方法:
- 属性
1.queryset
: 通用的数据集,通常是XXX.objects.all()
2.serializer_class
: 通用的序列化器
3.lookup_field
: 主键的名称,默认是pk
,主键,请注意与lookup_url_kwarg
区分
4.lookup_url_kwarg
:url
参数中的标识,如:/books/1
若:lookup_url_kwarg
为books_id
,那么1
这个值就要用books_id
接收 - 行为(方法)
1.get_queryset
: 获取queryset
的数据集
2.get_serializer
: 获取serializer_class
序列化器对象
3.get_object
: 根据lookup_field
获取单个对象
列表视图
需要用到queryset()
、serializer_class()
、get_queryset()
、get_serializer()
from rest_framework.generics import GenericAPIView
from rest_framework import status
from rest_framework.response import Response
from rest_test.serializers import AuthorModelSerialize
from rest_test.models import Author
class AuthorListView(GenericAPIView):
queryset = Author.objects.all()
serializer_class = AuthorModelSerialize
def get(self, request):
serialize = self.get_serializer(instance=self.get_queryset(), many=True)
return Response(serialize.data, status=status.HTTP_200_OK)
def post(self, request):
data = request.data
serialize = self.get_serializer(data=data)
serialize.is_valid(raise_exception=True)
serialize.save()
return Response(serialize.data, status=status.HTTP_201_CREATED)
路由如一级视图, 保持不变
详情视图
4个属性和3个方法,都可以用,随便发挥。
from rest_framework.generics import GenericAPIView
from rest_framework import status
from rest_framework.response import Response
from rest_test.serializers import AuthorModelSerialize
from rest_test.models import Author
class AuthorDetailView(GenericAPIView):
queryset = Author.objects.all()
serializer_class = AuthorModelSerialize
lookup_url_kwarg = "author_id"
def get(self, request, author_id):
# 相当于get_object_or_404
instance = self.get_object()
serialize = self.get_serializer(instance=instance)
return Response(serialize.data, status=status.HTTP_200_OK)
def put(self, request, author_id):
data = request.data
instance = self.get_object()
serialize = self.get_serializer(instance=instance, data=data, partial=True)
serialize.is_valid(raise_exception=True)
serialize.save()
return Response(serialize.data, status.HTTP_201_CREATED)
def delete(self, request, author_id):
instance = self.get_object()
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
由于指定了
lookup_url_kwarg
,路由改变
from django.contrib import admin
from django.urls import path
from rest_test import views
urlpatterns = [
path('admin/', admin.site.urls),
path("authors/", views.AuthorListView.as_view()),
path("authors/<int:author_id>/", views.AuthorDetailView.as_view())
]
与Mixin类结合
二级视图的另一特点就是可以与Minx类相结合。
python的
Mixin
类通常只是为子类类提供某些功能,而不自己实例化的类
Django REST framework
有这几个Mixin
类可以为我们提供通用的列表视图和详情视图解决方法:
类名 | 方法 | 作用 |
---|---|---|
ListModelMixin |
list |
查询所有的数据 |
CreateModelMixin |
create |
创建单个对象 |
RetrieveModelMixin |
retrieve |
获取单个对象 |
UpdateModelMixin |
update |
更新单个对象 |
DestroyModelMixin |
destroy |
删除单个对象 |
这些类都在
rest_framework.mixins
中
由上表可知:
- 视图列表:
ListModelMixin
+CreateModelMixin
- 详情视图:
RetrieveModelMixin
+UpdateModelMixin
+DestroyModelMixin
除此之外,你还可以根据需求任意组合
使用起来也特别简单:
- 继承
GenericAPIView
和对应的Mixin
- 定义方法(
get
,post
,put
,delete
) - 返回
Mixin
类中的处理方法(记得传入request
等参数)
例子:
- 列表视图
from rest_framework.generics import GenericAPIView from rest_framework.mixins import ListModelMixin, CreateModelMixin from rest_test.serializers import AuthorModelSerialize from rest_test.models import Author class AuthorListView(GenericAPIView, ListModelMixin, CreateModelMixin): queryset = Author.objects.all() serializer_class = AuthorModelSerialize def get(self, request): return self.list(request=request) def post(self, request): return self.create(request=request)
- 详情视图
from rest_framework.generics import GenericAPIView from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin from rest_test.serializers import AuthorModelSerialize from rest_test.models import Author class AuthorDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin): queryset = Author.objects.all() serializer_class = AuthorModelSerialize lookup_url_kwarg = "author_id" def get(self, request, author_id): return self.retrieve(request, author_id) def put(self, request, author_id): return self.update(request, author_id) def delete(self, request, author_id): return self.destroy(request, author_id)
虽然
Mixin
类已经写好了处理逻辑,但是它是通过调用其它方法的形式实现的,所以我们可以重写部分方法,可以使Mixin类更加灵活。
如:DestroyModelMixin
类,这是源码:
class DestroyModelMixin:
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
重写perform_destroy
方法就可以实现自订制了,具体有哪些方法可以查看源码,这里不展开了
三级视图xxxAPIView
Django REST framework
的视图简直跟套娃一样,一层套一层
所谓的三级视图,就是将GenericAPIView
和一个Mixin
类继承到一个子类中,实现列表视图和详情视图的某个方法。
有一说一,如果没有大量自定义的行为, 确实可以使用三级视图快速构建一个项目
常见的三级视图:
类名称 | 父类 | 提供方法 | 作用 |
---|---|---|---|
CreateAPIView |
GenericAPIView CreateModelMixin |
post |
创建单个对象 |
ListAPIView |
GenericAPIView ListModelMixin |
get |
查询所有的数据 |
RetrieveAPIView |
GenericAPIView RetrieveModelMixin |
get |
获取单个对象 |
DestroyAPIView |
GenericAPIView DestroyModelMixin |
delete |
删除单个对象 |
UpdateAPIView |
GenericAPIView UpdateModelMixin |
put |
更新单个对象 |
这些类在
rest_framework.generics
中
使用:
from rest_framework.generics import CreateAPIView, ListAPIView, RetrieveAPIView, UpdateAPIView, DestroyAPIView
from rest_test.serializers import AuthorModelSerialize
from rest_test.models import Author
class AuthorListView(ListAPIView, CreateAPIView):
# 列表视图
queryset = Author.objects.all()
serializer_class = AuthorModelSerialize
class AuthorDetailView(RetrieveAPIView, UpdateAPIView, DestroyAPIView):
# 详情视图
queryset = Author.objects.all()
serializer_class = AuthorModelSerialize
lookup_url_kwarg = "author_id"
视图集
视图集就是将整个列表视图和详情视图变成一个类,然后我们只需要指定queryset
serializer_class
lookup_field
lookup_url_kwarg
即可。
特点:
- 可以将一组相关的操作,放在一个类中进行完成
- 处理方法变化了:
get
全部 –>list
post
–>create
get
单个 –>retrieve
put
–>update
delete
–>destroy
视图集很重要的一点是可以在
urls
中做路由映射
常见的视图集:
类名称 | 父类 | 作用 |
---|---|---|
ViewSet |
APIView ViewSetMixin |
可以做路由映射 |
GenericViewSet |
GenericAPIView ViewSetMixin |
可以使用4个属性 3个方法, 其他同上 |
ModelViewSet |
GenericAPIView 和5个mixin 类 |
集成所有的增删改查功能, 其他同上 |
ReadOnlyModelViewSet |
GenericAPIView RetrieveModelMixin ListModelMixin |
可以做路由映射, 集成单个和所有数据的功能, 可以使用4个属性 3个方法 |
这些类在
rest_framework.viewsets
中
使用视图集
下面介绍四中视图集类的使用方式
-
ViewSet
视图,用一个类即可定义全部方法:from rest_framework.viewsets import ViewSet from django.shortcuts import get_object_or_404 from rest_framework import status from rest_framework.response import Response from rest_test.serializers import AuthorModelSerialize from rest_test.models import Author class AuthorViewSet(ViewSet): def list(self, request): authors = Author.objects.all() serialize = AuthorModelSerialize(instance=authors, many=True) return Response(serialize.data, status=status.HTTP_200_OK) def create(self, request): data = request.data serialize = AuthorModelSerialize(data=data) serialize.is_valid(raise_exception=True) serialize.save() return Response(serialize.data, status=status.HTTP_201_CREATED) def retrieve(self, request, author_id): author = get_object_or_404(Author, pk=author_id) serialize = AuthorModelSerialize(instance=author) return Response(serialize.data, status=status.HTTP_200_OK) def update(self, request, author_id): data = request.data author = get_object_or_404(Author, pk=author_id) serialize = AuthorModelSerialize(instance=author, data=data, partial=True) serialize.is_valid(raise_exception=True) serialize.save() return Response(serialize.data, status=status.HTTP_201_CREATED) def destroy(self, request, author_id): author = get_object_or_404(Author, pk=author_id) author.delete() return Response(status=status.HTTP_204_NO_CONTENT)
做路由映射,由于列表视图和详情视图的url不一样,所以需要做两次映射:
from django.contrib import admin from django.urls import path from rest_test import views urlpatterns = [ path('admin/', admin.site.urls), path("authors/", views.AuthorViewSet.as_view({"get": "list", "post": "create"})), path("authors/<int:author_id>/", views.AuthorViewSet.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})) ]
路由映射的意思就是,这个url的哪个请求方式,去哪个方法处理
路由中的as_view方法: 参数是一个字典, 键为请求方式, 值为对应的处理函数
如:path("authors/", views.AuthorViewSet.as_view({"get": "list", "post": "create"}))
意为:authors/
的get
方法去list
函数那里处理,而post
方法去create
函数那里处理 -
GenericViewSet
下面3个类都继承了
GenericAPIView
,操作起来更简单from rest_framework import status from rest_framework.response import Response from rest_test.serializers import AuthorModelSerialize from rest_test.models import Author from rest_framework.viewsets import GenericViewSet class AuthorViewSet(GenericViewSet): queryset = Author.objects.all() serializer_class = AuthorModelSerialize lookup_url_kwarg = "author_id" def list(self, request): authors = self.get_queryset() serialize = self.get_serializer(instance=authors, many=True) return Response(serialize.data, status=status.HTTP_200_OK) def create(self, request): data = request.data serialize = self.get_serializer(data=data) serialize.is_valid(raise_exception=True) serialize.save() return Response(serialize.data, status=status.HTTP_201_CREATED) def retrieve(self, request, author_id): author = self.get_object() serialize = self.get_serializer(instance=author) return Response(serialize.data, status=status.HTTP_200_OK) def update(self, request, author_id): data = request.data author = self.get_object() serialize = self.get_serializer(instance=author, data=data, partial=True) serialize.is_valid(raise_exception=True) serialize.save() return Response(serialize.data, status=status.HTTP_201_CREATED) def destroy(self, request, author_id): author = self.get_object() author.delete() return Response(status=status.HTTP_204_NO_CONTENT)
路由使用
ViewSet
的代码 -
ReadOnlyModelViewSet
只读的视图集from rest_framework.viewsets import ReadOnlyModelViewSet from rest_test.serializers import AuthorModelSerialize from rest_test.models import Author class AuthorReadOnlyViewSet(ReadOnlyModelViewSet): queryset = Author.objects.all() serializer_class = AuthorModelSerialize lookup_url_kwarg = "author_id"
改变路由
from django.contrib import admin from django.urls import path from rest_test import views urlpatterns = [ path('admin/', admin.site.urls), path("authors/", views.AuthorReadOnlyViewSet.as_view({"get": "list"})), path("authors/<int:author_id>/", views.AuthorReadOnlyViewSet.as_view({"get": "retrieve"})) ]
-
ModelViewSet
模型字符集,把列表视图和详情视图已经集成在一起了
注意:部分更新的函数是partial_update
from rest_framework.viewsets import ModelViewSet from rest_test.serializers import AuthorModelSerialize from rest_test.models import Author class AuthorViewSet(ModelViewSet): queryset = Author.objects.all() serializer_class = AuthorModelSerialize lookup_url_kwarg = "author_id"
改变路由
from django.contrib import admin from django.urls import path from rest_test import views urlpatterns = [ path('admin/', admin.site.urls), path("authors/", views.AuthorViewSet.as_view({"get": "list", "post": "create"})), path("authors/<int:author_id>/", views.AuthorViewSet.as_view( {"get": "retrieve", "put": "update", "delete": "destroy", "patch": "partial_update"})) ]
自定义额外方法
我们还可以定义一些方法,让视图集可以处理更多请求。
如: 获取id大于4的作者:
- 定义视图集
from rest_framework.viewsets import ReadOnlyModelViewSet from rest_test.serializers import AuthorModelSerialize from rest_test.models import Author # 为了看起来更简洁,使用ReadOnlyModelViewSet class AuthorReadOnlyViewSet(ReadOnlyModelViewSet): queryset = Author.objects.all() serializer_class = AuthorModelSerialize lookup_url_kwarg = "author_id" def gt_id(self, request): queryset = self.filter_queryset(self.get_queryset()) authors = queryset.filter(pk__gt=4) serialize = self.get_serializer(instance=authors, many=True) return Response(serialize.data, status=status.HTTP_200_OK)
- 写路由映射关系
from django.contrib import admin from django.urls import path from rest_test import views urlpatterns = [ path('admin/', admin.site.urls), path("authors/", views.AuthorReadOnlyViewSet.as_view({"get": "list"})), path("authors/<int:author_id>/", views.AuthorReadOnlyViewSet.as_view({"get": "retrieve"})), # 注意这里 !!!!!!!! get请求方式,找gt_id方法 path("authors/gt4/", views.AuthorReadOnlyViewSet.as_view({"get": "gt_id"})) ]
注意:假如使用Django REST framework
的方式定义路由的话,不能像上面那样写,要这样
from rest_framework.viewsets import ReadOnlyModelViewSet
from rest_test.serializers import AuthorModelSerialize
from rest_test.models import Author
from rest_framework.decorators import action
class AuthorReadOnlyViewSet(ReadOnlyModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorModelSerialize
lookup_url_kwarg = "author_id"
# 生成路由规则: 前缀/方法名/ detail=True时,需要主键的值且url为:前缀/{pk}/方法名/
@action(methods=['GET'], detail=False)
def gt_id(self, request):
queryset = self.filter_queryset(self.get_queryset())
authors = queryset.filter(pk__gt=7)
serialize = self.get_serializer(instance=authors, many=True)
return Response(serialize.data, status=status.HTTP_200_OK)
至于路由如何写,见下文的路由部分
总结一下
捋一下思路
- 一级视图只是继承了
View
,用法和原生Django
十分相似,几乎所有方法都要我们写。 - 二级视图将序列化器和模型数据定义在了类属性中,并且提供了获取单个模型的方法(
get_object
),简化了部分操作。最重要的是它可以与Mixin
类结合,能够减少的部分工作,但是仍然需要自定义方法,然后再方法中调用Mixin
类的方法。 - 三级视图则是父类直接提供方法,我们继承该类就可以使用了。
- 这三种视图定义方法,有一个明显的缺点 (可能吧) : 我们必须要定义多个视图类(列表视图和详细视图,要添加额外方法的话,可能还要增加视图数量)
- 视图集可以在路由中进行路由映射,因此可以把所有处理方法都定义在同一个类里面,而且它也提供了从简单到复杂的视图集类,方便我们的使用
- 至于在实际开发过程中,要用哪个类,全凭自己选择吧,最多多写几段代码而已
路由
SimpleRouter
一般使用:
router = SimpleRouter()
- 调用
register
方法 urlpatterns += router.urls
from django.contrib import admin
from django.urls import path
from rest_test import views
from rest_framework.routers import DefaultRouter, SimpleRouter
urlpatterns = [
path('admin/', admin.site.urls),
]
router = SimpleRouter()
router.register("authors", views.AuthorReadOnlyViewSet, basename="authors")
# 是authors不是authors/
urlpatterns += router.urls
print(urlpatterns)
"""
[<URLResolver <URLPattern list> (admin:admin) 'admin/'>,
<URLPattern '^authors/$' [name='authors-list']>,
<URLPattern '^authors/(?P<author_id>[^/.]+)/$' [name='authors-detail']>,
<URLPattern '^authors/$' [name='authors-list']>,
<URLPattern '^authors/(?P<author_id>[^/.]+)/$' [name='authors-detail']>]
"""
DefaultRouter
该路由可以在SimpleRouterd
的基础上生成可选的.json
样式格式后缀的路由和根路由。
from django.contrib import admin
from django.urls import path
from rest_test import views
from rest_framework.routers import DefaultRouter, SimpleRouter
urlpatterns = [
path('admin/', admin.site.urls),
]
router = DefaultRouter()
router.register("authors", views.AuthorReadOnlyViewSet, basename="authors")
# 是authors不是authors/
urlpatterns += router.urls
print(urlpatterns)
"""
[<URLResolver <URLPattern list> (admin:admin) 'admin/'>,
<URLPattern '^authors/$' [name='authors-list']>,
<URLPattern '^authors\.(?P<format>[a-z0-9]+)/?$' [name='authors-list']>,
<URLPattern '^authors/gt_id/$' [name='authors-gt-id']>,
<URLPattern '^authors/gt_id\.(?P<format>[a-z0-9]+)/?$' [name='authors-gt-id']>,
<URLPattern '^authors/(?P<author_id>[^/.]+)/$' [name='authors-detail']>,
<URLPattern '^authors/(?P<author_id>[^/.]+)\.(?P<format>[a-z0-9]+)/?$' [name='authors-detail']>,
<URLPattern '^authors/$' [name='authors-list']>,
<URLPattern '^authors\.(?P<format>[a-z0-9]+)/?$' [name='authors-list']>,
<URLPattern '^authors/(?P<author_id>[^/.]+)/$' [name='authors-detail']>,
<URLPattern '^authors/(?P<author_id>[^/.]+)\.(?P<format>[a-z0-9]+)/?$' [name='authors-detail']>,
<URLPattern '^$' [name='api-root']>, <URLPattern '^\.(?P<format>[a-z0-9]+)/?$' [name='api-root']>]
"""
限制访问
这部分为限制访问的内容,由认证、权限和限流三个部分组成。
Authentication
此部分讲的是认证内容,即如何确定你是你??
有两种配置方式:
- 在
settings
中配置全局的
如:REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', ) }
- 在视图中配置本视图的
from rest_framework.authentication import SessionAuthentication, BasicAuthentication class BookInfoModelViewSet(ModelViewSet): authentication_classes = (SessionAuthentication, BasicAuthentication) pass
一般来说我们常用有这几种验证方式:
BasicAuthentication
基于HTTP的认证方式,要求验证的时候会弹出一个框来,让你输入账号和密码,一般只在开发测试时使用SessionAuthentication
基于Session的认证,这里应该用的时django自带的AUTH组件TokenAuthentication
基于Token的HTTP认证方案,即生成一个令牌,作为标识,请求头带上Authorization: Token token的值
即可JSON Web Token Authentication
基于jwt
的认证方式
注:可以多个认证方式同时使用
下面将说如何使用
BasicAuthentication
全局配置:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
'rest_framework.authentication.BasicAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
# 'rest_framework.permissions.IsAuthenticated', # 普通用户
# 'rest_framework.permissions.AllowAny', # 所有用户
'rest_framework.permissions.IsAdminUser', # 管理员户
)
}
DEFAULT_PERMISSION_CLASSES
是权限,后面会讲到,可以先看看注释
假如我们要求有一定权限才能登录的话,那么它就会弹出如下面这样的对话框:
这里登录用的是django
的用户账号(就是登录django admin
的那种账号),是可以扩展的
SessionAuthentication
使用
SessionAuthentication
首先就是需要注意CSRF
的问题
全局配置(只换来认证器):
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
# 'rest_framework.permissions.IsAuthenticated', #普通用户
# 'rest_framework.permissions.AllowAny', #所有用户
'rest_framework.permissions.IsAdminUser', # 管理员户
)
}
登录页面需要我们自己写,很简单,可以看我这篇文章Django auth。但这里我们是学习DRF
,那么就用一个REST API
接口登录:
如果我们将登录看作是创建一个在线用户,那么logout就是删除一个在线用户了。
# views.py
from rest_framework.views import APIView
from rest_framework.permissions import AllowAny
from django.contrib.auth import authenticate, login, logout
class SessionView(APIView):
permission_classes = (AllowAny, )
# 必须写AllowAny(即每个人都可以访问),不然连登录的权限都没有!!
def post(self, request):
username = request.data.get("username")
password = request.data.get("password")
user = authenticate(request, username=username, password=password)
if user is not None:
# 登录,写入session信息
login(request, user)
return Response({"detail": "success!"}, status=status.HTTP_201_CREATED)
else:
return Response({"detail": "password or username error!"}, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request):
logout(request)
return Response({"detail": "logout success!!"}, status=status.HTTP_204_NO_CONTENT)
然后再添加路由
urlpatterns = [
path('admin/', admin.site.urls),
path("session/", views.SessionView.as_view())
]
访问session/
页面,方式POST
请求登录即可。
TokenAuthentication
- 配置settings:
INSTALLED_APPS = [ "rest_framework.authtoken" ] # ... REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ( 'rest_framework.authentication.TokenAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( # 'rest_framework.permissions.IsAuthenticated', #普通用户 # 'rest_framework.permissions.AllowAny', #所有用户 'rest_framework.permissions.IsAdminUser', # 管理员用户 ) }
- 执行迁移命令,这时会生成一个
authtoken_token
表$python manage.py migrate
- 编写登录视图
from rest_framework.views import APIView from rest_framework.permission import AllowAny from django.contrib.auth import authenticate from rest_framework.authtoken.models import Token class TokenView(APIView): permission_classes = (AllowAny,) def post(self, request): username = request.data.get("username") password = request.data.get("password") user = authenticate(request, username=username, password=password) if user is not None: # 删除原有的Token old_token = Token.objects.filter(user=user) old_token.delete() # 创建新的Token token = Token.objects.create(user=user) return Response({"code": 0, "msg": "login success!", "username": user.username, "token": token.key}, status=status.HTTP_201_CREATED) else: return Response({"detail": "password or username error!"}, status=status.HTTP_400_BAD_REQUEST) def delete(self, request): if request.user.is_authenticated: user = request.user old_token = Token.objects.filter(user=user) old_token.delete() return Response({"detail": "logout success!!"}, status=status.HTTP_204_NO_CONTENT)
- 配置路由
from django.contrib import admin from django.urls import path from rest_test import views urlpatterns = [ path('admin/', admin.site.urls), path("token/", views.TokenView.as_view()) ]
- 将用户名和密码利用
POST
方式提交给token
后就会返回token
获得token
后,将token
放在请求头即可。
如:获取token
为:323ce6f331c7aa65836ad48169037f001ce7e18f
那么请求头就为:Authorization: Token 323ce6f331c7aa65836ad48169037f001ce7e18f
JSON Web Token Authentication
-
安装
djangorestframework-simplejwt
,这是目前官网推荐的python jwt库$pip install djangorestframework-simplejwt
-
配置验证器为:
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ( 'rest_framework_simplejwt.authentication.JWTAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', # 普通用户 # 'rest_framework.permissions.AllowAny', # 所有用户 # 'rest_framework.permissions.IsAdminUser', # 管理员用户 ) }
-
配置jwt验证器
# settings.py import datetime SIMPLE_JWT = { # token有效时长 'ACCESS_TOKEN_LIFETIME': datetime.timedelta(minutes=30), # token刷新后的有效时间 'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1) }
-
定义路由规则
由于simplejwt
已经写好了视图,我们只需要直接拿来用即可:from django.contrib import admin from django.urls import path from rest_framework_simplejwt.views import ( TokenObtainPairView, TokenRefreshView, TokenVerifyView ) urlpatterns = [ path('admin/', admin.site.urls), # 获取Token的接口 path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), # 刷新Token有效期的接口 path('api/refresh/', TokenRefreshView.as_view(), name='token_refresh'), # 验证Token的有效性 path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'), ]
-
将用户名和密码提交到
POST api/token/
返回的信息包括refresh
和access
两个字段。
其中refresh
是用于刷新token
的(每个token
都是有时间限制的,过了时间就失效了)
access
是用于后续的请求时验证身份的。 -
将
access
的值放入请求头中:
如:获取的access
为eyJ0eiJIUiJ9.eyJ0b2tlzZXJjF9.n5FTesf9i4X3t7X0JEQ
,
那么,请请求头就是:Authorization: Bearer eyJ0eiJIUiJ9.eyJ0b2tlzZXJjF9.n5FTesf9i4X3t7X0JEQ
上面的路由:
api/token/verify/
可以检验token
,需要POST access
的值
api/refresh/
可以刷新token
,需要POST refresh
的值
关于更多
simple-jwt
的信息,请查看官方文档:Simple JWT
permission
权限可以是能不能让用户访问到页面的关键,不过你得先通过了认证。
指定权限
通用可以时全局和视图的
- 全局(在
settings
中定义):REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', # 普通用户 ) }
- 视图(在视图中定义):
from rest_framework.permissions import AllowAny from rest_framework.viewsets import ModelViewSet class BookInfoModelViewSet(ModelViewSet): permission_classes = (AllowAny,) # ...
常用内置权限
-
rest_framework.permissions.AllowAny
不管谁都可以 -
rest_framework.permissions.IsAuthenticatedOrReadOnly
允许经过身份验证的用户执行任何请求,匿名用户可以仅仅可以读取(GET
) -
rest_framework.permissions.IsAuthenticated
仅允许经过身份验证的用户执行任何请求(即已经登录的用户才能访问) -
rest_framework.permissions.IsAdminUser
仅允许request.user.is_staff
为True
的用户执行任何请求(说明这是一个管理员账号) -
rest_framework.permissions.DjangoModelPermissions
仅允许用户通过身份验证并分配了相关模型权限的情况下的用户执行任何请求特别注意:此权限只能应用于具有
queryset
属性集的视图
DjangoModelPermissions
由于内置的权限(只有增删改查)粒度不够细,不足以应付复杂的需求,所有这里演示一些使用我们自定义的权限
关于django
的权限问题可以查看此文:权限
要使用自定义模型权限,请继承DjangoModelPermissions
并设置perms_map
属性
DjangoModelPermissions
的部分源码:
perms_map = {
'GET': [],
'OPTIONS': [],
'HEAD': [],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
}
例子:
# views.py
class CustomPermClass(DjangoModelPermissions):
# GET请求权限, can_publish
perms_map = {"GET": ['%(app_label)s.can_publish']}
class AuthorViewSet(ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorModelSerialize
lookup_url_kwarg = "author_id"
permission_classes = (CustomPermClass,)
# models.py
class Books(models.Model):
bid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32, verbose_name="书名")
description = models.CharField(max_length=128, verbose_name="书籍描述")
author = models.ForeignKey(Author, on_delete=models.CASCADE)
def __str__(self):
return f"{self.author.name}: 《{self.name}》"
class Meta:
permissions = (("can_publish", "能够出版"),)
另:
DRF
有与Django
的标准对象权限框架相关联的类, 见: DjangoObjectPermissions
限流
即让请求在一段时间内只能访问多少次。
- 全局定义
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.AnonRateThrottle', # 匿名用户 'rest_framework.throttling.UserRateThrottle' # 认证用户 ], 'DEFAULT_THROTTLE_RATES': { 'anon': '2/minute', # 匿名用户每分钟2次 'user': '3/minute' # 认证用户每分钟3次 } }
- 视图中定义
from rest_framework.throttlingimport AnonRateThrottle from rest_framework.viewsets import ModelViewSet class BookInfoModelViewSet(ModelViewSet): throttle_classes = (AnonRateThrottle, ) # ...
- 可选限流
# settings.py REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.ScopedRateThrottle', ], 'DEFAULT_THROTTLE_RATES': { 'downloads': '3/minute', # downloads 自定义的 'uploads': '5/minute' # uploads 自定义的 } }
# views.py class TestView(APIView): throttle_scope = "uploads" def get(self,request): return Response("testing....")
筛选返回数据
分页
使用默认的分页器
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 2,
}
- PAGE_SIZE指的是每页多少条数据
其默认生成的数据是这样的:
{
"count": 9,
"next": "//127.0.0.1:8000/authors/?limit=2&offset=6",
"previous": "//127.0.0.1:8000/authors/?limit=2&offset=2",
"results": [
{
"aid": 8,
"name": "张三",
"phone": "13888888888",
"address": "广东省广州市xxxxx",
"books_set": []
},
{
"aid": 9,
"name": "李四",
"phone": "13888888888",
"address": "广东省广州市xxxxx",
"books_set": []
}
]
}
//127.0.0.1:8000/authors/?limit=2&offset=6
中的limit
指取多少条数据,offset
指偏移量,即跳过多少条数据
默认情况下limit
是没有限制的,也就是说你指定1000000都行,为了限度这些行为,我们可以自定义一个分页器
自定义一个分页器
只需要继承rest_framework.pagination.PageNumberPagination
,里面我们可以通过自定义类属性的形式,自定义分页器:
常用的属性
page_size
页面大小的数值page_size_query_param
可以指定页面大小max_page_size
指示允许的最大请求页面大小
还有些没有列出来,可以查看官方文档。
#自定义分页对象
class MyPageNumberPagination(PageNumberPagination):
#1,默认的大小
page_size = 3
#2,前端可以指定页面大小
page_size_query_param = 'page_size'
#3,页面的最大大小
max_page_size = 5
# 视图集
class BookInfoModelViewSet(ModelViewSet):
# ...
pagination_class = MyPageNumberPagination
# 链接可以是,?page=4 或者 ?page=4&page_size=100
排序
这个功能可以让数据按照指定的字段进行排序
from rest_framework.filters import OrderingFilter
class BookInfoModelViewSet(ModelViewSet):
# ....
# 局部排序
filter_backends = (OrderingFilter, )
# 指定排序字段
ordering_fields = ['id', 'btitle','bread']
# 查询格式: ?ordering=-bread,id
过滤
过滤功能可以根据文档配置,进行过滤返回的数据
这不是
Django REST framework
的组件,需要我们安装django-filter
- 安装
django-filter
$pip install django-filter
- 将
django-filter
配置到INSTALLED_APPS
INSTALLED_APPS = [ # ... 'django_filters', ]
- 配置
filter
全局:REST_FRAMEWORK = { # ... 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] }
视图:
from django_filters.rest_framework import DjangoFilterBackend class BookInfoModelViewSet(ModelViewSet): # .... filter_backends = (DjangoFilterBackend, ) filterset_fields = ('id', 'btitle',"is_delete") # 参数格式:?id=1&is_delete=True
除此外还可以向ORM的__
方法一样指定参数,但是要实现这个功能需要自定义FilterSet
有更加复杂定义方法 (
FilterSet
), 详情见: 官方文档
异常处理
我们可以通过配置,指定某些函数处理程序中的异常信息
步骤:
-
自定义一个异常处理函数:
# myapp.my_exception.py from rest_framework.views import exception_handler from rest_framework.response import Response from django.db import DatabaseError def custom_exception_handler(exc, context): #1 调用系统方法,处理了APIException的异常,或者其子类异常 response = exception_handler(exc, context) #2 判断response是否有值 if response is not None: response.data['status_code'] = response.status_code else: if isinstance(exc, DatabaseError): response = Response("数据库大出血") else: response = Response("其他异常!") return response
-
将处理函数配置到
REST_FRAMEWORK
配置中REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'myapp.my_exception.custom_exception_handler' }
生成API文档
该功能可以生成一个文档
操作流程:
- 安装扩展
$pip install coreapi
- 路由规则,在跟路由下:
from rest_framework.documentation import include_docs_urls urlpatterns = [ # ... path('^docs/', include_docs_urls(title='我的API文档')) # title是标题, ]
- 添加备注信息
配置视图集的__doc__
和序列化器的help_text
或label
class CourseViewSet(viewsets.ModelViewSet): """ retrieve: 返回指定course信息 list: 返回course列表 update: 更新course信息 destroy: 删除course记录 create: 创建course记录 partial_update: 更新部分字段 """ #在view中的资源类下,说明注释信息 class Course(models.Model): name = models.CharField(max_length=64,verbose_name='课程名称',help_text='课程名称') ... #在model或者serializer中添加help_text字段。它会显示在api文档的字段描述信息中
- 打开
//127.0.0.1:8008/docs/
即可查看文档