REST
框架中的序列化器的工作方式与Django
Form
和ModelForm
类非常相似。
REST
提供了一个Serializer
类,可以将对象
中的数据序列化成json
,同时还可以将json
数据反序列化成对象
第零篇、rest_framework
的配置
# 1.创建一个 `django`项目,并生成一个`app`,命名为`my_app`
# 在 ALLOWED_HOSTS 允许所有
ALLOWED_HOSTS = ['*']
# 2. 安装 djangorestframework
# 我这里安装的 `restframework` 是 `3.11`版本,后续的用法以及源码都基于此版本
pip install djangorestframework
# 3.将 restframework 配置到 `django`的`app`中
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'my_app.apps.MyAppConfig',
'rest_framework',
]
# 4.数据初始化
python manage.py migrate
第一篇、rest_framework
的序列化
与反序列化
流程与用法
这里以简单的数据模型来讲解,只是为了弄明白 rest_framework
的源码流程,所以不涉及复杂关系,在第二篇中单独讲解 关系模型
1. REST序列化
注意:在 rest_framework
中序列化器不仅适用于django
的model
数据而是对所有的对象都适用,在这里我们只以django
的model
作为数据对象。
使用 rest_framework
的序列化的流程
-
1.创建指定模型的序列化器(继承
serializers.Serializer
)- 指明需要序列化的字段信息()
id = serializers.IntegerField() name = serializers.CharField() size = serializers.IntegerField()
-
2.在View类中,生成序列化器对象
# instance 就是我们要序列化的对象,也可以传入query_set book = Book.objects.first() book_serializer = BookSerializer(instance=book) # 如果传入的instance是复数,则需要 many=True books = Book.objects.all() books_serializer = BookSerializer(instance=books, many=True)
-
3.从序列化器对象中取出序列化的数据
data = book_serializer.data
example1. 简单的数据序列化使用
# models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32, verbose_name='书名')
size = models.IntegerField(verbose_name='字数')
# views.py
# 这里使用了 `rest_framework` 的 `Response`,也可以使用 django 提供的JsonResponse
# from django.http import JsonResponse
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from my_app.models import Book
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField()
size = serializers.IntegerField()
class BookView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk', None) # 从 url 路径中获取 pk 字段信息
book = Book.objects.filter(pk=pk).first()
book_serializer = BookSerializer(instance=book)
data = book_serializer.data
return Response(data)
# urls.py
from django.contrib import admin
from django.urls import path
from my_app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('api/book//', views.BookView.as_view()),
]
使用postman,访问http://127.0.0.1:8000/api/book/1/
的get
方法
返回的结果如下:
{
"id": 1,
"name": "斗破苍穹",
"size": 3450000
}
example 2. 使用source
更改字段名称
source
指定实际的数据获取来源(字段
或者函数
)
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField(source='name') # 绑定 Book 中的 name 字段
size = serializers.IntegerField()
指明title
的数据来源
与name
字段
返回结果如下
{
"id": 1,
"title": "斗破苍穹", // 这里 name 改成了 title
"size": 3450000
}
example 3. 使用source
获取函数返回值
我们有时不仅需要 model
中字段数据
,也需要函数数据
,借助source
可以将函数
进行序列化
# models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32, verbose_name='书名')
size = models.IntegerField(verbose_name='字数')
category_choices = (
(1, '仙侠修真'),
(2, '历史'),
(3, '都市言情'),
)
# `django` 会自动生成`get_category_display()函数`
category = models.IntegerField(choices=category_choices, default=1)
def big_data(self):
"""是否大于300万字"""
return self.size > 3000000
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField()
size = serializers.IntegerField()
category = serializers.IntegerField()
category_display = serializers.CharField(source='get_category_display')
is_big_data = serializers.BooleanField(source='big_data')
序列化后的返回内容:
{
"id": 1,
"name": "斗破苍穹",
"size": 3450000,
"category": 1,
"category_display": "仙侠修真",
"is_big_data": true
}
2. REST反序列化简单使用
反序列化就是将json
数据转成数据对象
反序列化使用流程:
- 1.创建序列化器
- 指定序列器的字段以及字段约束
- 重写
update()
与create()
方法
-
- 生成序列化器对象
- 传入
data
参数,instance
参数可选,如果不传入,则生成新的对象,如果传入instance
参数,则在传入对象基进行更新
book_serializer = BookSerializer(instance=book, data=data)
3.通过
is_valid()
对传入的数据进行验证4.执行
save()
方法将数据反序列化成对象
example 1. 数据反序列化(创建对象)
# models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32, verbose_name='书名')
size = models.IntegerField(verbose_name='字数')
category_choices = (
(1, '仙侠修真'),
(2, '历史'),
(3, '都市言情'),
)
category = models.IntegerField(choices=category_choices, default=1)
# views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from my_app.models import Book
class BookSerializer(serializers.Serializer):
# 必须添加 read_only=True,否则无法进行`create`与`update`操作
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()
size = serializers.IntegerField()
category = serializers.IntegerField()
def create(self, validated_data):
"""
返回一个 instance 实例对象
"""
name = validated_data.get('name', None)
size = validated_data.get('size', 0)
category = validated_data.get('category', 2)
book = Book.objects.create(
name=name, size=size, category=category
)
book.save() # 更新到数据库中
return book
class BookView(APIView):
def post(self, request, *args, **kwargs):
data = request.data # 从 request 获取POST传过来的值
# 没有传入 instance 参数,在 save() 中执行 create(),
book_serializer = BookSerializer(data=data)
if book_serializer.is_valid(): # 验证数据是否合法
# 将 json 数据转化成 对象,实际调用我们重写的 `create()`方法
instance = book_serializer.save()
return Response(book_serializer.data)
return Response(book_serializer.errors)
使用postman
发送post
请求,如下数据:
{
"name":"凡人修仙传",
"size":"4256732",
"category":"1"
}
返回结果如下:
{
"id": 4,
"name": "凡人修仙传",
"size": 4256732,
"category": 1
}
可以看出,我们创建了新的 book
对象,在数据库中的 id
为 4
example 2. 数据反序列化(更新对象)
前端发送json
数据,在数据库中进行更新。
全部更新对应 put
请求,部分更新对应patch
请求,这里我们以put
请求为例。
对对象进行更新,需要实现序列化器的update()
方法,并且在创建序列化器时,传入instance
实例对象。
# views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from my_app.models import Book
class BookSerializer(serializers.Serializer):
# 必须添加 read_only=True,否则无法进行`create`与`update`操作
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()
size = serializers.IntegerField()
category = serializers.IntegerField()
def create(self, validated_data):
"""
返回一个 instance 实例对象
"""
name = validated_data.get('name', None)
size = validated_data.get('size', 0)
category = validated_data.get('category', 2)
book = Book.objects.create(
name=name, size=size, category=category
)
book.save() # 更新到数据库中
return book
def update(self, instance, validated_data):
"""
对 instance 实例对象进行数据更新
"""
for name, value in validated_data.items():
setattr(instance, name, value)
instance.save() # 将数据更新到数据库中
return instance
class BookView(APIView):
def post(self, request, *args, **kwargs):
"""用于创建对象"""
data = request.data # 从 request 获取POST传过来的值
# 没有传入 instance 参数,在 save() 中执行 create(),
book_serializer = BookSerializer(data=data)
if book_serializer.is_valid():
instance = book_serializer.save()
return Response(book_serializer.data)
return Response(book_serializer.errors)
def put(self, request, *args, **kwargs):
"""用于更新资源的部分信息"""
data = request.data # 从 request 获取传过来的值
pk = kwargs.get('pk', None) # 从 `url` 路径中获取`pk`值
book = Book.objects.get(pk=pk)
book_serializer = BookSerializer(instance=book, data=data)
if book_serializer.is_valid():
instance = book_serializer.save() # 更新后的实例对象 instance
return Response(book_serializer.data) # 将更新后的实例进行序列化返回的 json 数据
return Response(book_serializer.errors)
# urls.py
from django.contrib import admin
from django.urls import path
from my_app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('api/book//', views.BookView.as_view()),
]
我们对 id
为 4
的book进行更新,使用postman
对http://127.0.0.1:8000/api/book/4/
,发送put
请求,内容如下:
{
"name":"凡人修仙传之都市篇",
"size":"4256732",
"category":3
}
由于我们在 BookSerializer
中定义的id
字段具有read_only=True
约束,所有我们在发送的json
数据中可以不填id
信息,但是其它的信息必须填写。
返回内容:
{
"id": 4,
"name": "凡人修仙传之都市篇",
"size": 4256732,
"category": 3
}
总结:对反序列化而言,我们要做的就是实现序列化器的update()
与create()
方法,然后调用save()
,save()
内部会帮我们分发到update()
或者create()
save()
源码流程:
查看源码的技巧:对断言assert
部分不用太关注,抓住主要的逻辑
def save(self, **kwargs):
# 1.传入自己补充的信息,将补充的信息放入 `validated_data` 中
validated_data = dict(
list(self.validated_data.items()) +
list(kwargs.items())
)
# 如果没有传入 `instance`,则调用 `update`方法
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
# 否则调用 `create` 方法
else:
self.instance = self.create(validated_data)
return self.instance
3. REST反序列化,字段约束
反序列化数据时,使用is_valid()
对传入的data
数据进行验证。
字段约束
类似于 django
的约束条件
如果发生任何验证错误,则.errors
属性将包含代表所得错误消息
example 1. 字段缺少展示
如果上述的update
传入时,size
信息并没有改动,我们不传入size
{
"name":"凡人修仙传之都市篇",
"category":3
}
则返回信息:
{
"size": [
"This field is required."
]
}
这是因为我们定义的序列化器,如果没有指定required
为False
,则必须传入值,或者指定默认值。
改成如下,则不再报错,可以在更新或者生成时,不传入size
属性
class BookSerializer(serializers.Serializer):
# 必须添加 read_only=True,否则无法进行`create`与`update`操作
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()
size = serializers.IntegerField(required=False)
category = serializers.IntegerField()
3.1 所有的字段共有的约束条件以及默认值:read_only=False
,write_only=False
,required=None
,allow_null=False
,validators=None
-
read_only
表示是否只读属性,一般用于id
,表示只能看,不能改
,就算我们传入了id
信息,如:
返回的结果中,{ "id":5, "name":"凡人修仙传之都市篇", "category":3 }
id
依然不会改变,这是因为在create
和update
中,read_only=True
的信息根本不会被封装到validated_data
中
如果我们对某字段使用了read_only=True
,还想要在 create
和update
中对数据进行修改,可以在 save()
方法中,将信息传入
pk = kwargs.get('pk',None) # 从 url 中获取`pk`信息
book_serializer.save(id=pk)
备注:这里只是为了演示save()
从外部获取数据,但是不建议更改id
字段
-
write_only
表示只写属性,一般用于一些保密的信息,例如password
,只能用于创建或更新,但是返回时,不对该字段信息进行序列化。
例如我们对上述的name
字段添加write_only
约束,则更新后返回的信息中不再具有name
{
"id": 4,
"size": 4256732,
"category": 3
}
-
required
表示字段是否必填,如果required = False
,则可以不传入该字段信息。 -
allow_null
表示传入的数据是否可以为空。 -
validators
(验证器列表),可以传入多个函数对象(callback
),通过函数对字段进行自定义约束。
example 2. 使用字段的validators
,实现自定义约束
实现自定义约束函数,返回结果只能有两种
- 返回一个验证后的数据 ,表示验证通过
- 抛出
ValidationError
异常,表示验证不通过
from rest_framework.exceptions import ValidationError
def sensitive_word_filter(value):
"""
敏感字过滤
返回验证通过的数据,不通过则抛出 `ValidationError` 异常
抛出 `ValidationError` 由 `is_valid()`接收
"""
sensitive_words = ['*****', '***', '****']
if value in sensitive_words:
raise ValidationError(f'{value}是敏感词')
return value
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
# 注意这里传入的函数对象,而不是 sensitive_word_filter()
name = serializers.CharField(validators=[sensitive_word_filter, ])
size = serializers.IntegerField(required=False)
category = serializers.IntegerField()
3.2 特殊字段的独有约束条件
和django
一样,除了共有的约束信息,rest_framework
中不同的字段,有不同的约束条件
-
CharField
:min_length
,max_length
,trim_whitespace
,allow_blank
-
EmailField
: 自动验证是否符合邮箱
格式 -
IntegerField
,FloatField
:max_value
,min_value
-
DateTimeField
:format
序列化时的输出格式 ,input_formats
反序列化的输入字符串格式列表,可以使用多个格式;default_timezone
,设置时区
example 3. 使用DateTimeField
的独有约束,实现自定义时间的输入
,输出
格式
# models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32, verbose_name='书名')
size = models.IntegerField(verbose_name='字数')
category_choices = (
(1, '仙侠修真'),
(2, '历史'),
(3, '都市言情'),
)
category = models.IntegerField(choices=category_choices, default=1)
# 这里 created 是后续加上去的,必须要有 null=True, blank=True 才能迁移成功
created = models.DateTimeField(null=True, blank=True)
# views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from my_app.models import Book
from my_app.utils.validators import sensitive_word_filter
format = '%Y-%m-%d %H:%M:%S'
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
# 注意这里传入的函数对象,而不是 sensitive_word_filter()
name = serializers.CharField(validators=[sensitive_word_filter, ])
size = serializers.IntegerField(required=False)
category = serializers.IntegerField()
# 指定输入与输出的字符串格式
created = serializers.DateTimeField(format=format, input_formats=[format, ])
def create(self, validated_data):
"""
返回一个 instance 实例对象
"""
name = validated_data.get('name', None)
size = validated_data.get('size', 0)
category = validated_data.get('category', 2)
book = Book.objects.create(
name=name, size=size, category=category
)
book.save() # 更新到数据库中
return book
def update(self, instance, validated_data):
for name, value in validated_data.items():
setattr(instance, name, value)
instance.save()
return instance
class BookView(APIView):
def get(self, request, *args, **kwargs):
"""获取指定 pk 的 `book` 信息"""
pk = kwargs.get('pk', None)
book = Book.objects.get(pk=pk)
book_serializer = BookSerializer(instance=book)
return Response(book_serializer.data)
def put(self, request, *args, **kwargs):
"""用于更新资源信息"""
data = request.data # 从 request 获取传过来的值
pk = kwargs.get('pk', None)
book = Book.objects.get(pk=pk)
# 传入了instance 对象,`.save()`执行更新操作
book_serializer = BookSerializer(instance=book, data=data)
if book_serializer.is_valid():
instance = book_serializer.save() # 更新后的实例对象 instance
return Response(book_serializer.data) # 将更新后的实例进行序列化返回的 json 数据
return Response(book_serializer.errors)
# urls.py
from django.contrib import admin
from django.urls import path
from my_app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('api/book//', views.BookView.as_view()),
]
对 http://127.0.0.1:8000/api/book/3/
发送put
请求,请求内容:
{
"name":"雪中悍刀行",
"category":3,
"created":"2013-11-04 05:23:49"
}
返回内容:
{
"id": 3,
"name": "雪中悍刀行",
"size": 4651209,
"category": 3,
"created": "2013-11-04 05:23:49"
}
如果请求输入的datetime 格式可能有多种,我们也可以在input_formats
设置多个
3.3 自定义验证函数
在 3.1 中使用在字段上validators
可以实现自定义函数验证,除了这一种方法外,我们还可以使用另一种验证方式。
创建.validate_
方法可以指定自定义字段级验证
如下,为username
绑定自定义函数约束/验证
example 4. .validate_
函数级验证
# models.py
from django.db import models
class UserInfo(models.Model):
username = models.CharField(max_length=16)
password = models.CharField(max_length=64)
email = models.CharField(max_length=64)
created_date = models.DateField()
money = models.IntegerField()
from rest_framework.views import APIView
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from my_app.models import UserInfo
format = '%Y-%m-%d'
input_formats = ['%Y-%m-%d', '%Y %m %d', '%Y/%m/%d', '%Y.%m.%d']
class UserInfoSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
username = serializers.CharField(max_length=16)
password = serializers.CharField(max_length=64, write_only=True)
email = serializers.EmailField(max_length=64)
created_date = serializers.DateField(format=format, input_formats=input_formats)
money = serializers.FloatField(min_value=0)
def validate_username(self, value):
if not value.startswith('中国公民_'):
raise ValidationError('用户名称必须以`中国公民_`开头')
return value
def create(self, validated_data):
user = UserInfo()
for key, value in validated_data.items():
setattr(user, key, value)
user.save()
return user
class UserView(APIView):
def post(self, request, *args, **kwargs):
data = request.data
user_serializer = UserInfoSerializer(data=data)
if user_serializer.is_valid():
user_serializer.save()
return Response(user_serializer.data)
return Response(user_serializer.errors)
from django.contrib import admin
from django.urls import path
from my_app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('api/book//', views.BookView.as_view()),
path('api/user//', views.UserView.as_view()),
]
发送post
请求,创建时,username
必须以中国公民_
开头'
validate_
函数约束与validators
约束的区别:
最主要的是validate_
只能给某一个字段使用,而validators
适用于多个字段。
我们一般把通用型的字段验证放在validators
中,以便供更多的字段适用。
3.4 对象级验证
3.1 - 3.3 都是对一个字段
进行验证,但在特殊的情况下,我们可能涉及到多个字段之间的关系验证,这就需要我们直接对 data
进行验证。
重写 Serializer
的validate()
方法,可以直接对data
进行处理。
- 抛出异常
ValidationError
说明验证失败 - 返回处理后的
dict
格式的数据,说明验证成功
class MySerializer(serializers.Serializer):
def validate(self, data):
if data['start'] > data['end']:
raise ValidationError('开始时间必须小于结束时间')
return data
4. REST多个对象的序列化
与反序列化
前面的讲解中,我们都是使用的单个对象的获取,但是在实际工作中,我们更多的是一次获取多个对象,并进行分页。
example 1.通过 get
获取多个对象数据 以及通过post
创建多个对象
# models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32, verbose_name='书名')
size = models.IntegerField(verbose_name='字数')
category_choices = (
(1, '仙侠修真'),
(2, '历史'),
(3, '都市言情'),
)
category = models.IntegerField(choices=category_choices, default=1)
created = models.DateTimeField(null=True, blank=True)
# views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from my_app.models import Book, UserInfo
from rest_framework.exceptions import ValidationError
format = '%Y-%m-%d %H:%M:%S'
input_formats = ['%Y-%m-%d %H:%M:%S', '%Y %m %d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y.%m.%d %H:%M:%S']
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()
size = serializers.IntegerField()
category = serializers.IntegerField()
created = serializers.DateTimeField(format=format, input_formats=input_formats)
def create(self, validated_data):
"""
返回一个 instance 实例对象
"""
book = Book(validated_data)
book.save() # 更新到数据库中
return book
def validate_category(self, value):
"""对类别 category 进行约束验证"""
allow_category = [1, 2, 3]
if value not in allow_category:
raise ValidationError('类别不符合')
return value
class BooksView(APIView):
def get(self, request, *args, **kwargs):
books = Book.objects.all()
book_serializers = BookSerializer(instance=books, many=True)
return Response(book_serializers.data)
def post(self, request, *args, **kwargs):
data = request.data
book_serializers = BookSerializer(data=data, many=True) # 这里传入的是多个对象,必须使 `many = True`
if book_serializers.is_valid():
book_serializers.save()
return Response(book_serializers.data)
return Response(book_serializers.errors)
# urls.py
from django.contrib import admin
from django.urls import path
from my_app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('api/books/', views.BooksView.as_view()),
]
发送 get
请求,获取全部的books
数据
[
{
"id": 1,
"name": "斗破苍穹",
"size": 3450000,
"category": 1,
"created": null
},
{
"id": 2,
"name": "大主宰",
"size": 3982365,
"category": 1,
"created": null
},
{
"id": 3,
"name": "雪中悍刀行",
"size": 4651209,
"category": 3,
"created": "2013-11-04"
},
{
"id": 4,
"name": "凡人修仙传之都市篇",
"size": 4256732,
"category": 3,
"created": null
}
]
发送 post
请求进行创建,如果数据中有一个对象的数据验证失败,则本次创建失效
当 many = True
时,我们创建的序列化器的实例对象并不是我们当前的BookSerializer
对象,而是 ListSerializer
对象,但是我们调用 ListSerializer
对象的 is_valid()
以及 save()
方法时候,好像都是BookSerializer
在起作用,这是因为 ListSerializer
内部将传入的集合对象进行迭代遍历,然后再交给
BookSerializer
处理
如下是ListSerializer
源码
# ListSerializer 类
def create(self, validated_data):
# validated_data 是一个集合对象
return [
# 这里的 `child` 就是`BookSerializer`实例对象
self.child.create(attrs) for attrs in validated_data
]
example 2.自定义ListSerializer
的某些功能
- 第一步 继承
ListSerializer
,并重写部分功能
class MyListSerializer(serializers.ListSerializer):
def create(self, validated_data):
# do something
return super().create(validated_data)
- 第二步,在使用序列化类时,指定
list_serializer_class
属性
class BookSerializer(serializers.Serializer):
...
class Meta:
list_serializer_class = BookListSerializer
这样我们在执行 save()
方法时,会调用MyListSerializer
的create()
方法
5. REST序列化
与反序列化
的源码流程
5.1 序列化
源码流程
查看源码
的一些技巧
- 1.找到主要的
入口
点,一步步理出脉络
,而不是从import ……·
一行行看起 - 2.对于有继承关系的类,每次执行
方法
都从顶层类
找起
class A:
def step_1(self):
print('A.step_1')
self.step_2()
def step_2(self):
print('A.step_2')
self.step_3()
def step_3(self):
print('A.step_3')
class B(A):
def step_2(self):
print('B.step_2')
self.step_3()
class C(B):
def step_3(self):
print('C.step_3')
if __name__ == '__main__':
c = C()
c.step_1()
打印结果如下:
A.step_1
B.step_2
C.step_3
这里虽然第一步执行了A.step_1
,在A.step_1
中执行self.step_2()
并不会直接执行A.step_2
,而是按照继承关系,先从c
对象查找step_2()
方法,没有的话就继续往上查找,找到B
的step_2
,所以先执行B.step_2
,每一步都是如此,从顶层查找
,底层的方法只会被覆盖。
- 3.抓住主要的逻辑 对于
assert
这些验证,或者抛出异常的部分,可以不用太关注。
例如完整的save()
方法如下
def save(self, **kwargs):
assert not hasattr(self, 'save_object'), (
'Serializer `%s.%s` has old-style version 2 `.save_object()` '
'that is no longer compatible with REST framework 3. '
'Use the new-style `.create()` and `.update()` methods instead.' %
(self.__class__.__module__, self.__class__.__name__)
)
assert hasattr(self, '_errors'), (
'You must call `.is_valid()` before calling `.save()`.'
)
assert not self.errors, (
'You cannot call `.save()` on a serializer with invalid data.'
)
# Guard against incorrect use of `serializer.save(commit=False)`
assert 'commit' not in kwargs, (
"'commit' is not a valid keyword argument to the 'save()' method. "
"If you need to access data before committing to the database then "
"inspect 'serializer.validated_data' instead. "
"You can also pass additional keyword arguments to 'save()' if you "
"need to set extra attributes on the saved model instance. "
"For example: 'serializer.save(owner=request.user)'.'"
)
assert not hasattr(self, '_data'), (
"You cannot call `.save()` after accessing `serializer.data`."
"If you need to access data before committing to the database then "
"inspect 'serializer.validated_data' instead. "
)
validated_data = dict(
list(self.validated_data.items()) +
list(kwargs.items())
)
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
这里面最重要的就只有三段
,我们主要理解这三段就好,后面有时间再研究那些assert
验证
- 4.
见名猜意
,我们根据方法的名称先假设作用
,把整个走一下,如果能走的通,说明我们的假设
应该问题不大,然后再去验证我们的假设对不对。
备注:这里的源码分析,我们也是将方法中的主要逻辑
代码片段贴出来,没有全部展示。
当我们执行序列化时。通过book_serializer.data
来获取数据
- 第一步: 执行
Serializer
的data
,Serializer
的data
调用父类BaseSerializer
的data
.
在BaseSerializer
的data
中,主要执行以下片段
# 1.具有`instance`实例对象,则将实例对象序列化
if self.instance is not None and not getattr(self, '_errors', None):
self._data = self.to_representation(self.instance)
# `_validated_data` 是我们传入的`data`参数经`is_valid()`验证后的数据
# 2.没有`instance`实例,但是传入了`data`数据,并验证通过,将`data`数据序列化
elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
self._data = self.to_representation(self.validated_data)
- 第二步:执行
to_representation()
函数
这里Serializer
具有to_representation()
函数,所以直接调用
注意:这里的参数instance
既可以是数据对象
,也可以是validated_data
字典数据
def to_representation(self, instance):
"""
Object instance -> Dict of primitive datatypes.
"""
# 创建有序的 dict
# 这样返回的`json`数据顺序就会和我们创建的`Serializer`中声明的一致
ret = OrderedDict()
# 获取可读的字段,如果字段约束条件中`write_only = True`,则在这里无法获取,也就不会进行序列化
fields = self._readable_fields
# fields 就是我们声明的字段 例如:name = serializers.CharField()
for field in fields:
try:
# 如果`instance`中是数据对象
# 从我们提供的属性名称/方法名称,如:`name`,`get_choice_display`
# 通过反射 获取属性值
# 如果`instance`是 字典数据,也就是`validated_data`
# 则直接返回 instance[attr]
attribute = field.get_attribute(instance)
except SkipField:
continue
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
if check_for_none is None:
ret[field.field_name] = None
else:
# 执行各个字段的 `to_representation()`方法
# 主要是将数据转化为各个字段的格式
# 例如 `IntegetField` 中,执行 int(value)
# 在 `CharField`中,执行 str(value)
# 在 `DateTimeField`中,则根据我们传入的`format`参数,对数据进行字符串格式化
ret[field.field_name] = field.to_representation(attribute)
return ret
从上面看,rest_framework
的数据序列化主要都是在to_representation
中完成,如果我们要自定义数据序列化的格式,则可以重写to_representation
from collections import OrderedDict
from rest_framework import serializers
class UpperKeySerializer(serializers.Serializer):
……
def to_representation(self, instance):
"""
重写了`to_representation`,返回结果中的`key`,全部返回`大写`
"""
origin_ret = super().to_representation(instance)
ret = OrderedDict()
for field_name, value in origin_ret.items():
ret[field_name.upper] = value
return ret
这里有一个注意点,我们执行post
操作时,创建序列化器对象时,并没有传入instance
,而是传入了data = data
,并执行is_valid()
验证通过,但是序列化
出来的结果中包含id
属性,这明显是直接将instance
序列化了。
这是因为我们执行save()
操作,在save()
中将create()
创建的instance
赋值给了self
.所以self.instance
为真,to_representation
也就直接作用于该instance
,而不是我们传入的data = data
.
5.2反序列化
源码流程
执行反序列化
主要有两步:
book_serailizer.is_valid()
-
book_serailizer.save()
其中save()
方法已经讲解过,就是调用create
或者update
方法。
反序列化的源码主要在数据约束 / 验证上比较复杂,这里主要讨论这一部分。
-
第一步,执行
Serializer
类的is_valid()
def is_valid(self, raise_exception=False): # 是否有`_validated_data` 是判断是否执行过`is_valid()`的依据 # 如果存在`_validated_data` ,说明已经验证过,直接返回;防止多次调用`is_valid` if not hasattr(self, '_validated_data'): try: # `is_valid`主要就是执行这一行 # `initial_data` 就是我们传入的`data = data`参数,在` __init__()`中被初始化为`initial_data` self._validated_data = self.run_validation(self.initial_data) except ValidationError as exc: self._validated_data = {} self._errors = exc.detail else: self._errors = {} if self._errors and raise_exception: raise ValidationError(self.errors) return not bool(self._errors)
从这里可以看出,如果
is_valid()
执行成功,则会产生_validated_data
数据,在create
与update
中传入的validated_data
实际就是这里的_validated_data
第二步,执行
Serializer
类的run_validation()
def run_validation(self, data=empty):
# 1.验证传入的`data`是否为空
(is_empty_value, data) = self.validate_empty_values(data)
if is_empty_value:
return data
# 2.将数据转化为`内部值`
# 这里进行了第一种验证
# 这里的 `data` 是我们传入的字典,那么返回出来的 `value`应该也是字典
value = self.to_internal_value(data)
try:
# 这里进行了第二种验证,添加验证器
self.run_validators(value)
# 这里进行了第三种验证,也就是我们前面`3.4`章节讲的对象级验证,钩子函数就埋在这
value = self.validate(value)
assert value is not None, '.validate() should return the validated data'
except (ValidationError, DjangoValidationError) as exc:
raise ValidationError(detail=as_serializer_error(exc))
return value
上面的第三种验证,在我们的3.4
已经讲过,这里不再讲解,第二种验证是自定义验证器类
,比较复杂,一般用不到,这里下一步着重讲解to_internal_value
- 第三步,
Serailizer
类实现了to_internal_value()
,我们直接查看
def to_internal_value(self, data):
"""
这里主要执行字段级别的验证
对`data`字典的验证在`run_validators(value)`与 `validate(value)`完成
"""
# 创建一个有序字典
ret = OrderedDict()
errors = OrderedDict()
# 获取可写 / 可反序列化的字段对象
# 例如我们将`id`设置为`read_only = True`,则这里不会获取`id`字段
fields = self._writable_fields
for field in fields:
# 第一种字段约束
# 这里执行自定义函数验证
# 我们`3.3`章节`validate_`的钩子埋在这
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
try:
# 第二种字段约束
# 这里执行字段的基础验证
# 对应我们的`3.1`, `3.2`字段约束部分
# 3.1中 `required ` , `validators ` ……
# 3.2中`CharField`的 `max_length`,`IntegerField`的`max_value`等都是在 `field.run_validation`中完成
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = get_error_detail(exc)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
6. REST ModelSerializer
使用
对于 rest_framework
的序列化器,所有的基础功能都是基于Serializer
来实现的,除此之外rest_framework
还提供了与Django
模型定义紧密映射的序列化程序类ModelSerializer
。
ModelSerializer
继承了Serializer
,它主要有以下三种新的功能:
- 它将根据模型自动为您生成一组字段,这也是它最主要的作用。
- 它将自动为序列化器生成验证器,例如unique_together验证器。
- 它包括简单的默认实现
create()
和update()
6.1 ModelSerializer
自动生成字段
# models.py
from django.db import models
class UserInfo(models.Model):
username = models.CharField(max_length=16)
password = models.CharField(max_length=32)
money = models.FloatField()
category_choices = (
(1, '普通用户'),
(2, 'VIP'),
)
category = models.IntegerField(choices=category_choices)
example 1 使用fields
自动生成所有字段
from rest_framework.serializers import ModelSerializer
class UserSerializer(ModelSerializer):
class Meta:
model = UserInfo
fields = '__all__'
使用pycharm
编辑器自带的Python Console
进入shell
界面
[ shell
中自动导入了django
环境,可以直接使用django
中的model
对象,否则会报ImproperlyConfigured
错误 ]
在 shell
中执行以下
>>> from my_app.models import UserSerializer
>>> user_serializer = us = UserSerializer()
>>> user_serializer
打印结果如下:
UserSerializer():
id = IntegerField(label='ID', read_only=True)
username = CharField(max_length=16)
password = CharField(max_length=32)
money = FloatField()
category = ChoiceField(choices=((1, '普通用户'), (2, 'VIP')))
可以看出,ModelSerializer
自动为我们创建了字段,并添加了字段约束,像read_only=True
会自定为 django
的主键添加,而max_length
也是从UserInfo
中获取的。这样可以为我们节省很多时间。
生成的UserSerializer
实际与我们自己前面所写的Serializer
功能是一模一样
的
example 2 自动生成部分字段
class UserSerializer(ModelSerializer):
class Meta:
model = UserInfo
fields = ['id', 'username', 'password'] # `fields` 可以为`__all__` 或者一个列表
example 3 使用exclude
属性设置为要从序列化器中排除的字段列表。
class UserSerializer(ModelSerializer):
class Meta:
model = UserInfo
exclude = ['category', 'money']
注意:从3.3.0版开始,必须提供属性fields
或exclude
之一。
6.2 使用extra_kwargs
设置关键字参数
使用 ModelSerializer
可以方便的帮我们生成字段,并且根据model
自动添加了约束,如:id
字段的read_only = True
,CharField
的max_length
等。
但是有的时候我们还是需要自己设置关键字参数,例如给password
添加write_only = True
,给money
字段添加min_value = 0.0
一种方式是重写这些字段
class UserSerializer(ModelSerializer):
password = serializers.CharField(write_only=True, max_length=32)
money = serializers.FloatField(min_value=0.0)
class Meta:
model = UserInfo
fields = '__all__'
除了上面的方法,也可以通过extra_kwargs
为指定的字段
添加额外的关键字参数
class UserSerializer(ModelSerializer):
class Meta:
model = UserInfo
fields = '__all__'
extra_kwargs = {
# `ModelSerializer` 生成的'password'已经有`max_length = 32`,这里不需要再设置。
'password': {'write_only': True},
'money': {'min_value': 0.0}
}
6.3 ModelSerializer
设置关系深度depth
当我们的model
模型中涉及与其它表的关系时,使用ModelSerializer
生成的关系一般使用主键
表示
# models.py
from rest_framework.serializers import ModelSerializer
from django.db import models
class User(models.Model):
username = models.CharField(max_length=16)
detail = models.CharField(max_length=256)
class Article(models.Model):
title = models.CharField(max_length=64)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class ArticleSerializer(ModelSerializer):
class Meta:
model = Article
在 Django
的shell
界面执行以下:
>>> from my_app.models import ArticleSerializer
>>> article_ser = ArticleSerializer()
>>> article_ser
打印内容如下
ArticleSerializer():
id = IntegerField(label='ID', read_only=True)
title = CharField(max_length=64)
user = PrimaryKeyRelatedField(queryset=User.objects.all())
序列化后的数据如下:
[
{
"id": 1,
"title": "枭臣",
"user": 2
},
{
"id": 2,
"title": "斗破苍穹",
"user": 1
}
]
这里的user
以主键代替User
表
当设置depth = 1
后
class ArticleSerializer(ModelSerializer):
class Meta:
model = Article
depth = 1 # depth 是整数,表示向下关系深度,一般不建议在3层以上
[
{
"id": 1,
"title": "枭臣",
"user": {
"id": 2,
"username": "更俗",
"detail": "更俗,原为起点中文网白金作家,2010年转投纵横中文网成为其签约作家。"
}
},
{
"id": 2,
"title": "斗破苍穹",
"user": {
"id": 1,
"username": "天蚕土豆",
"detail": "天蚕土豆,本名李虎。1989年12月28日出生于四川,中国内地网络小说作家、85后著名作家、浙江省网络作家协会副主席..."
}
}
]
注意:使用depth
后,关系嵌套为只读
。也就是这里的user
为只读属性。
第二篇、rest_framework序列化器关系
。
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=64)
create = models.DateTimeField('发布时间')
visitors = models.IntegerField('阅读人数')
user = models.ForeignKey(User, on_delete=models.CASCADE)
在上面的模型中,对于简单的字段,我们已经知道如何序列化,但是对user
知道关系
字段,我们用专门的序列化字段
可选。
-
PrimaryKeyRelatedField
, 可以通过关系的主键来表示关系对象。推荐使用 -
StringRelatedField
,使用__str__
方法值来表示关系对象。 -
HyperlinkedRelatedField
,通过超链接来表示关系对象。 -
SlugRelatedField
,使用目标上的字段来表示关系对象。
7.1 PrimaryKeyRelatedField
使用
from django.db import models
from rest_framework.serializers import ModelSerializer
class User(models.Model):
username = models.CharField(max_length=16)
detail = models.CharField(max_length=256)
class Article(models.Model):
title = models.CharField(max_length=64)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class ArticleSerializer(ModelSerializer):
# 重写 `user`字段
user = PrimaryKeyRelatedField(queryset=User.objects.filter(pk__in=[1, 2]))
class Meta:
model = Article
fields = '__all__'
序列化后的结果为:
[
{
"id": 1,
"user": 2,
"title": "楚臣"
},
{
"id": 2,
"user": 2,
"title": "官场之风流人生"
},
{
"id": 3,
"user": 2,
"title": "枭臣"
},
{
"id": 4,
"user": 1,
"title": "斗破苍穹"
},
{
"id": 5,
"user": 1,
"title": "武动乾坤"
}
]
这里我们使用了PrimaryKeyRelatedField
,所以就是使用user
的主键来表示user
对象。
注意:PrimaryKeyRelatedField
等关系
字段的关键字参数。
- 共有的基础设置, 3.1的
read_only=False
,write_only=False
,required=None
,allow_null=False
,validators=None
。
这里比较常用的就是read_only = True
,将关系设置为只读属性。 -
many = False
,如果我们的关系对象为多个,则需要设置many = True
,这样序列化的字段会表示为列表
class UserSerializer(ModelSerializer):
# `article_set` 是`User` 的反向关系字段
# 在 `Django`中,多对一的反向字段都是以 `_set`
# 如果我们不明确指定,`ModelSerializer`不会自动创建
# 我们在创建 / 更新`User`对象时,不需要`article`参数,所以`read_only = True`
article_set = PrimaryKeyRelatedField(read_only = True,many = True)
class Meta:
model = User
fields = '__all__'
[
{
"id": 1,
"article_set": [
5,
6
],
"username": "天蚕土豆",
"detail": "天蚕土豆,本名李虎。1989年12月28日出生于四川,中国内地网络小说作家、85后著名作家、浙江省网络作家协会副主席..."
},
{
"id": 2,
"article_set": [
2,
3,
4
],
"username": "更俗",
"detail": "更俗,原为起点中文网白金作家,2010年转投纵横中文网成为其签约作家。"
}
]
-
queryset
,用于创建
或者更新
的字段约束,例如ArticleSerializer
中,除了常规的字段约束验证,user
必须在queryset
中。
例如以下数据就不通过:
{
"user": 3,
"title": "楚臣2"
}
queryset
与read_only = True
必须二选一。
7.2 StringRelatedField
使用
class User(models.Model):
username = models.CharField(max_length=16)
detail = models.CharField(max_length=256)
def __str__(self):
return self.username
class Article(models.Model):
title = models.CharField(max_length=64)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class ArticleSerializer(ModelSerializer):
user = StringRelatedField(read_only=True)
class Meta:
model = Article
fields = '__all__'
序列化的结果:
[
{
"id": 1,
"user": "更俗",
"title": "楚臣"
},
{
"id": 2,
"user": "更俗",
"title": "官场之风流人生"
},
{
"id": 3,
"user": "更俗",
"title": "枭臣"
},
{
"id": 4,
"user": "天蚕土豆",
"title": "斗破苍穹"
},
{
"id": 5,
"user": "天蚕土豆",
"title": "武动乾坤"
}
]
7.3 HyperlinkedRelatedField
使用
# urls.py
from django.contrib import admin
from django.urls import path
from my_app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('api/user//', views.UserView.as_view(), name='user'),
path('api/articles/', views.ArticlesView.as_view()),
]
class ArticleSerializer(ModelSerializer):
user = HyperlinkedRelatedField(read_only=True, view_name='user')
class Meta:
model = Article
fields = '__all__'
class ArticlesView(APIView):
def get(self, request, *args, **kwargs):
quertset = Article.objects.all()
# 使用`HyperlinkedRelatedField`必须传入环境参数`context={'request': request}`
articles_serializer = ArticleSerializer(instance=quertset, many=True, context={'request': request})
return Response(articles_serializer.data)
返回的结果如下
[
{
"id": 1,
"user": "http://127.0.0.1:8000/api/user/2/",
"title": "楚臣"
},
{
"id": 2,
"user": "http://127.0.0.1:8000/api/user/2/",
"title": "官场之风流人生"
},
{
"id": 3,
"user": "http://127.0.0.1:8000/api/user/2/",
"title": "枭臣"
},
{
"id": 4,
"user": "http://127.0.0.1:8000/api/user/1/",
"title": "斗破苍穹"
},
{
"id": 5,
"user": "http://127.0.0.1:8000/api/user/1/",
"title": "武动乾坤"
}
]
使用 HyperlinkedRelatedField
建议设置read_only = True
关键字参数:
-
view_name
: 视图函数名称 -
lookup_field
: 生成url
,从关系对象中的取数据的字段,默认值为pk
-
lookup_url_kwarg
: 将取出的数据匹配url
路径的字段,默认值与lookup_field
相同。
1⃣️.当 url
中的路径字段不为pk
时,需要指定lookup_field
……
path('api/user//', views.UserView.as_view(), name='user'),
上面反向生成url
需要填补username
,而lookup_field
默认为pk
,这个系统会从