【2020Python修炼记】Django Rest Framework-2-drf序列化组件
【目录】
一、什么是序列化器
二、最简单的 serializers.Serializer 序列化器的使用
2.1 定义序列化器
2.2 创建Serializer对象
2.3 序列化器的使用
2.3.1 序列化
2.3.2 反序列化
三、模型序列化器 ModelSerializer 的使用
一、什么是序列化器
作用:
1. 序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串 2. 反序列化,把客户端发送过来的数据,经过request以后变成字典,序列化器可以把字典转成模型 3. 反序列化,完成数据校验功能
二、最简单的 serializers.Serializer 序列化器的使用
准备工作——
=创建子应用 sers
python manage.py startapp sers
=准备模型表(记得操作-数据库迁移,以及新增基础数据)
from django.db import models # Create your models here. class Student(models.Model): # 模型字段 name = models.CharField(max_length=100,verbose_name="姓名",help_text="提示文本:账号不能为空!") sex = models.BooleanField(default=True,verbose_name="性别") age = models.IntegerField(verbose_name="年龄") class_null = models.CharField(max_length=5,verbose_name="班级编号") description = models.TextField(verbose_name="个性签名") class Meta: db_table="tb_student" verbose_name = "学生" verbose_name_plural = verbose_name
2.1 定义序列化器
Django REST framework中的Serializer使用类来定义,须继承自rest_framework.serializers.Serializer
注意:serializer不是只能为数据库模型类定义,也可以为非数据库模型类的数据定义。serializer是独立于数据库之外的存在。
from rest_framework import serializers # 声明序列化器,所有的序列化器都要直接或者间接继承于 Serializer # 其中,ModelSerializer是Serializer的子类,ModelSerializer在Serializer的基础上进行了代码简化 class StudentSerializer(serializers.Serializer): """学生信息序列化器""" # 1. 需要进行数据转换的字段 id = serializers.IntegerField() name = serializers.CharField() age = serializers.IntegerField() sex = serializers.BooleanField() description = serializers.CharField() # 2. 如果序列化器集成的是ModelSerializer,则需要声明调用的模型信息 # 3. 验证代码 # 4. 编写添加和更新模型的代码
常用字段类型:
字段 字段构造方式 BooleanField BooleanField() NullBooleanField NullBooleanField() CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) EmailField EmailField(max_length=None, min_length=None, allow_blank=False) RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False) SlugField SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+ URLField URLField(max_length=200, min_length=None, allow_blank=False) UUIDField UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose'
如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
2)'hex'
如"5ce0e9a55ffa654bcee01238041fb31a"
3)'int'
- 如:"123456789012312313134124512351145145114"
4)'urn'
如:"urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
IPAddressField IPAddressField(protocol=’both’, unpack_ipv4=False, **options) IntegerField IntegerField(max_value=None, min_value=None) FloatField FloatField(max_value=None, min_value=None) DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置 DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None) DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None) TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None) DurationField DurationField() ChoiceField ChoiceField(choices) choices与Django的用法相同 MultipleChoiceField MultipleChoiceField(choices) FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) ListField ListField(child=, min_length=None, max_length=None) DictField DictField(child=) 选项参数:
参数名称 作用 max_length 最大长度 min_lenght 最小长度 allow_blank 是否允许为空 trim_whitespace 是否截断空白字符 max_value 最小值 min_value 最大值 通用参数:
参数名称 说明 read_only 表明该字段仅用于序列化输出,默认False write_only 表明该字段仅用于反序列化输入,默认False required 表明该字段在反序列化时必须输入,默认True default 反序列化时使用的默认值 allow_null 表明该字段是否允许传入None,默认False validators 该字段使用的验证器 error_messages 包含错误编号与错误信息的字典 label 用于HTML展示API页面时,显示的字段名称 help_text 用于HTML展示API页面时,显示的字段帮助提示信息
【总结】 1、字段属性 title=serializers.CharField(max_length=32,min_length=2,read_only=True) price=serializers.DecimalField(max_digits=5, decimal_places=2) publish=serializers.CharField(max_length=32,write_only=True) =1、CharField 必须要有的字段 max_length =2、DecimalField 必须要有的字段 max_digits decimal_places =3、关于 read_only/write_only ===序列化/反序列化 【序列化】 就是将对象转化方便传输和存储字节序列,例如json.dumps就是序列化(狭义的序列化,将字典转化为json字符串), 这样得到的json字符串不仅直接可以在其他语言使用(跨平台比较好),而且可以在前后端进行传输交互(drf序列化器) 【反序列化】 恰恰相反,而是将字节序列转化为对象,json.loads是将json字符串转化为字典, 是狭义的反序列化(因为在python, 一切皆对象,字典是dict( )), 而在项目中,前端传过来的序列化数据通过反序列化得到对象,进一步可以通过ORM操作,存入数据库。 === read_only/write_only 【read_only】表明该字段仅用于序列化输出,默认 False read_only 是只用于读,不能写(可以理解为只能从后台到前台,后台操作后直接传前台), 不能对字段进行修改操作(也就是不和数据库进行交互),用在序列化字段里。 例如数据表中,gender只用于传到前端展示。而不用于存到数据库 【write_only】表明该字段仅用于反序列化输入,默认 False write_only是只写不能读,可以理解为只能前台到后台, 在后台做了逻辑操作后直接存数据库,在反序列化字段使用。 在数据表中,password就是反序列化,不需要传前台, 而re_password是自定义反序列化字段,仅用作后台和密码进行比较, 然后把结果返回前台,所以不存到数据库,在全局钩子使用时要pop掉 【解说版2】 read_only 该字段仅用于 序列化输出(后端--》前端) (read_only=True) 即 后端往前端 传送数据时(序列化),该字段只用于显示在前端; 如果从前端往后端输入数据时(反序列化),该字段则可以不传值(这需要提前给该字段增加 null=True 属性,允许字段在反序列化输入时 可以为空,否则数据库会报错) write_only 该字段仅用于 反序列化输入 (前端--》后端) (write_only=True) 从前端往后端输入数据时(反序列化),该字段必须传值 后端往前端 传送序列化数据时(序列化),该字段不会显示在前端 用于 反序列化时必填,写入数据库的字段,但是不想后面在前端显示 (一般若不想让字段显示在前端,是直接注释字段;但是这样可能会在反序列化时出问题,因此使用 write_only=True 来控制字段 )
2.2 创建Serializer对象
定义好Serializer类后,就可以创建Serializer对象了。
Serializer的构造方法为:
Serializer(instance=None, data=empty, **kwarg)
说明:
1)用于序列化时,将模型类对象传入instance参数
2)用于反序列化时,将要被反序列化的数据传入data参数
3)除了instance和data参数外,在构造Serializer对象时,还可通过context参数额外添加数据,如
serializer = AccountSerializer(account, context={'request': request})
通过context参数附加的数据,可以通过Serializer对象的context属性获取。
- 使用序列化器的时候一定要注意,序列化器声明了以后,不会自动执行,需要我们在视图中进行调用才可以。
- 序列化器无法直接接收数据,需要我们在视图中创建序列化器对象时把使用的数据传递过来。
- 序列化器的字段声明类似于我们前面使用过的表单系统。
- 开发restful api时,序列化器会帮我们把模型数据转换成字典.
- drf提供的视图会帮我们把字典转换成json,或者把客户端发送过来的数据转换字典.
2.3 序列化器的使用
序列化器的使用分两个阶段:
- 在客户端请求时,使用序列化器可以完成对数据的 反序列化(前端-----》后端)。
- 在服务器响应时,使用序列化器可以完成对数据的 序列化 (后端-----》前端)。
2.3.1 序列化
2.3.1.1 基本使用
1) 先查询出一个学生对象
from students.models import Student student = Student.objects.get(id=3)
2) 构造序列化器对象
from .serializers import StudentSerializer serializer = StudentSerializer(instance=student)
3)获取序列化数据
通过data属性可以获取序列化后的数据
serializer.data
完整视图代码:
from django.views import View from students.models import Student from .serializers import StudentSerializer from django.http.response import JsonResponse class StudentView(View): """使用序列化器序列化转换单个模型数据""" def get(self,request,pk): # 获取数据 student = Student.objects.get(pk=pk) # 数据转换[序列化过程] serializer = StudentSerializer(instance=student) print(serializer.data) # 响应数据 return JsonResponse(serializer.data)
4)如果要被序列化的是包含多条数据的查询集QuerySet,可以通过添加many=True参数补充说明
"""使用序列化器序列化转换多个模型数据"""
def get(self,request):
# 获取数据
student_list = Student.objects.all()
# 转换数据[序列化过程]
# 如果转换多个模型对象数据,则需要加上many=True
serializer = StudentSerializer(instance=student_list,many=True)
print( serializer.data ) # 序列化器转换后的数据
# 响应数据给客户端
# 返回的json数据,如果是列表,则需要声明safe=False #默认safe=True代表只能序列化字典对象,safe=False代表可以序列化字典以外的对象
return JsonResponse(serializer.data,safe=False)
2.3.1.1 高级使用—— source 和 serializers.SerializerMethodField()
的用法
source 用法
source的用法1 —— 修改字段前端显示的名字,真实的字段名是title,想让其在前端显示为name,则可以改写如下: 注意 设定的前端显示名 不能与真实的字段名相同,否则会报错 name=serializers.CharField(max_length=32,min_length=2,read_only=True,source='title') source的用法2 —— 若表模型中 定义有方法,则可以将方法执行的返回值 传给source 例如 -1- models.py的book模型中,有个test方法 def test(self): # python是强类型语言,不支持字符串和数字直接相加 return self.title+str(self.price) -2- 则在序列化器类中的 需要序列化字段中 加上一个字段用于接收方法执行的返回值 test_result=serializers.CharField(source='test') source的用法3 ——支持 xx.yy 的跨表操作 例如 -1- models.py --两张一对多关系的publish表模型和 book表模型 book表模型中,加上关联字段 publish = models.ForeignKey(to='Publish',null=True,on_delete=models.CASCADE) publish表模型 class Publish(models.Model): name=models.CharField(max_length=32) addr=models.CharField(max_length=32) def __str__(self): return self.name book表模型的序列化器类-- source='publish.addr' publish_addr=serializers.CharField(max_length=32,source='publish.addr') # objects.name
serializers.SerializerMethodField()
的用法
# =1、需要指定使用SerializerMethodField()的字段
# =2、搭配定义一个方法,规定 get_字段名,方法内写返回给字段的数据### 取出图书的出版社详细信息(id,name,addr) class BookSerializer(serializers.Serializer): id = serializers.IntegerField(required=False) name = serializers.CharField(max_length=32,min_length=2,source='title') price = serializers.DecimalField(max_digits=5, decimal_places=2)
# =1、需要指定使用SerializerMethodField()的字段 publish = serializers.SerializerMethodField() # =2、搭配定义一个方法,规定 get_字段名,方法内写返回给字段的数据 def get_publish(self,obj): dic={'name':obj.publish.name,'addr':obj.publish.addr} return dic # 这里返回什么,就序列化显示什么
2.3.2 反序列化
=1 数据验证
使用序列化器进行反序列化时,需要对数据进行验证后,才能获取验证成功的数据或保存成模型类对象。
在获取反序列化的数据前,必须调用is_valid()方法进行验证,验证成功返回True,否则返回False。
验证失败,可以通过序列化器对象的errors属性获取错误信息,返回字典,包含了字段和字段的错误。如果是非字段错误,可以通过修改REST framework配置中的NON_FIELD_ERRORS_KEY来控制错误字典中的键名。
验证成功,可以通过序列化器对象的validated_data属性获取数据。
在定义序列化器时,指明每个字段的序列化类型和选项参数,本身就是一种验证行为。
如我们前面定义过的BookInfoSerializer
# models.py
class BookInfoSerializer(serializers.Serializer): """图书数据序列化器""" id = serializers.IntegerField(label='ID', read_only=True) btitle = serializers.CharField(label='名称', max_length=20) bpub_date = serializers.DateField(label='发布日期', required=False) bread = serializers.IntegerField(label='阅读量', required=False) bcomment = serializers.IntegerField(label='评论量', required=False) image = serializers.ImageField(label='图片', required=False)
通过构造序列化器对象,并将要反序列化的数据传递给data构造参数,进而进行验证
# views.py from booktest.serializers import BookInfoSerializer data = {'bpub_date': 123} serializer = BookInfoSerializer(data=data) serializer.is_valid() # 返回False serializer.errors # {'btitle': [ErrorDetail(string='This field is required.', code='required')],
'bpub_date': [ErrorDetail(string='Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].', code='invalid')]} serializer.validated_data # {} data = {'btitle': 'python'} serializer = BookInfoSerializer(data=data) serializer.is_valid() # True serializer.errors # {} serializer.validated_data # OrderedDict([('btitle', 'python')])
is_valid()方法还可以在验证失败时抛出异常serializers.ValidationError,可以通过传递raise_exception=True参数开启,
REST framework接收到此异常,会向前端返回HTTP 400 Bad Request响应。
# Return a 400 response if the data was invalid. serializer.is_valid(raise_exception=True)
=2 局部钩子/全局钩子/ 自定义验证器
如果觉得这些还不够,需要再补充定义验证行为,可以使用以下三种方法:
1) 局部钩子 —— validate_字段名
对
<field_name>
字段进行验证,如# serializer.py
class BookInfoSerializer(serializers.Serializer): """图书数据序列化器""" ... def validate_btitle(self, value): if 'django' not in value.lower(): raise serializers.ValidationError("图书不是关于Django的") return value测试 views.py
from booktest.serializers import BookInfoSerializer data = {'btitle': 'python'} serializer = BookInfoSerializer(data=data) serializer.is_valid() # False serializer.errors # {'btitle': [ErrorDetail(string='图书不是关于Django的', code='invalid')]}
2) 全局钩子—— validate
在序列化器中需要同时对多个字段进行比较验证时,可以定义validate方法来验证,如
class BookInfoSerializer(serializers.Serializer): """图书数据序列化器""" ... def validate(self, attrs): bread = attrs['bread'] bcomment = attrs['bcomment'] if bread < bcomment: raise serializers.ValidationError('阅读量小于评论量') return attrs测试
from booktest.serializers import BookInfoSerializer data = {'btitle': 'about django', 'bread': 10, 'bcomment': 20} s = BookInfoSerializer(data=data) s.is_valid() # False s.errors # {'non_field_errors': [ErrorDetail(string='阅读量小于评论量', code='invalid')]}
3) 自定义验证器方法—— validators
在字段中添加validators选项参数,也可以补充验证行为,如
# serializer.py def about_django(value): if 'django' not in value.lower(): raise serializers.ValidationError("图书不是关于Django的") class BookInfoSerializer(serializers.Serializer): """图书数据序列化器""" id = serializers.IntegerField(label='ID', read_only=True) btitle = serializers.CharField(label='名称', max_length=20, validators=[about_django]) bpub_date = serializers.DateField(label='发布日期', required=False) bread = serializers.IntegerField(label='阅读量', required=False) bcomment = serializers.IntegerField(label='评论量', required=False) image = serializers.ImageField(label='图片', required=False)测试:
from booktest.serializers import BookInfoSerializer
data = {'btitle': 'python'} serializer = BookInfoSerializer(data=data) serializer.is_valid() # False serializer.errors # {'btitle': [ErrorDetail(string='图书不是关于Django的', code='invalid')]}
=3 保存数据
1\前面的验证数据成功后,我们可以使用序列化器来完成数据反序列化的过程.这个过程可以把数据转成模型类对象.
可以通过实现create()和update()两个方法来实现。
class BookInfoSerializer(serializers.Serializer): """图书数据序列化器""" ... def create(self, validated_data): """新建""" return BookInfo(**validated_data) def update(self, instance, validated_data): """更新,instance为要更新的对象实例""" instance.btitle = validated_data.get('btitle', instance.btitle) instance.bpub_date = validated_data.get('bpub_date', instance.bpub_date) instance.bread = validated_data.get('bread', instance.bread) instance.bcomment = validated_data.get('bcomment', instance.bcomment) return instance如果需要在返回数据对象的时候,也将数据保存到数据库中,则可以进行如下修改
class BookInfoSerializer(serializers.Serializer): """图书数据序列化器""" ... def create(self, validated_data): """新建""" return BookInfo.objects.create(**validated_data) def update(self, instance, validated_data): """更新,instance为要更新的对象实例""" instance.btitle = validated_data.get('btitle', instance.btitle) instance.bpub_date = validated_data.get('bpub_date', instance.bpub_date) instance.bread = validated_data.get('bread', instance.bread) instance.bcomment = validated_data.get('bcomment', instance.bcomment) instance.save() return instance实现了上述两个方法后,在反序列化数据的时候,就可以通过save()方法返回一个数据对象实例了
book = serializer.save()
2\如果创建序列化器对象的时候,没有传递instance实例,则调用save()方法的时候,create()被调用,相反,如果传递了instance实例,则调用save()方法的时候,update()被调用。
# views.py from db.serializers import BookInfoSerializer data = {'btitle': '封神演义'} serializer = BookInfoSerializer(data=data) serializer.is_valid() # True serializer.save() # <BookInfo: 封神演义> from db.models import BookInfo book = BookInfo.objects.get(id=2) data = {'btitle': '倚天剑'} serializer = BookInfoSerializer(book, data=data) serializer.is_valid() # True serializer.save() # <BookInfo: 倚天剑> book.btitle # '倚天剑'
附加说明
1) 在对序列化器进行save()保存时,可以额外传递数据,这些数据可以在create()和update()中的validated_data参数获取到
# request.user 是django中记录当前登录用户的模型对象 serializer.save(owner=request.user)
2)默认序列化器必须传递所有required的字段,否则会抛出验证异常。但是我们可以使用 partial 参数来允许部分字段更新
# Update `comment` with partial data serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
【总结-重难点】
1、定义序列化器——序列化/反序列化 read_only / write_only
2、创建Serializer对象——三大参数 instance / data / context
3、使用序列化器——
序列化— many=True / safe=False
反序列化— validators=[ ] / partial 参数 / is_valid() / source / serializers.SerializerMethodField()
【练习代码笔记】
models.pyfrom django.db import models # Create your models here. class Book(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=32,null=True) price = models.DecimalField(max_digits=5, decimal_places=2) # publish = models.CharField(max_length=32) publish = models.ForeignKey(to='Publish',null=True,on_delete=models.CASCADE) class Meta: db_table = "tb_book" verbose_name = "书籍" verbose_name_plural = verbose_name # 复数 # source的用法2 def test(self): # python是强类型语言,不支持字符串和数字直接相加 return self.title+str(self.price) # source的用法3 class Publish(models.Model): name=models.CharField(max_length=32) addr=models.CharField(max_length=32) # 若想让前端显示为字段具体内容,而不是对象,则需要重写该字段的 __str__方法 # 也可以在序列化器类的序列化字段中,在该字段的属性 使用 source='publish.name' def __str__(self): return self.name # 1、db_column 指定了对应的字段名 # db_table 指定了对应的表名 # # 2、verbose_name / verbose_name_plural 指定在admin管理界面中显示中文; # verbose_name表示单数形式的显示, # verbose_name_plural表示复数形式的显示; # 中文的单数和复数一般不作区别。 # 3、help_text="字段提示文本,如'请输入密码'等等"urls.py"""drftest URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/2.2/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path,include,re_path from test2_sers import views urlpatterns = [ path('admin/', admin.site.urls), # 把子应用test1 中的路由文件加载到总路由文件中 path('student/',include('test1.urls')), # <1>【接口1:get-查询所有】 朝该地址发送 get请求,会拿到所有数据/ # <3>【接口3:新增一个 Book--------》post path('books/',views.Book.as_view()), # <2>【接口2:get-查询单个】 re_path('^books/(?P<id>\d+)',views.BookDetail.as_view()), ] # -----------------------------------------》 # 【-总结-】 # 【关于 模型表.as_view() 】 # 所有基于类的视图都必须继承View类,该类用来处理视图关联到URL的操作,具体分析如下: # # 由于django的URL解析器期望发送request以及相关参数给一个可调用的函数,而不是一个类, # 所以基于类的视图有一个as_view()类方法(该方法继承自父类View),调用该方法会返回URL解析器所期望的函数, # 该函数会在请求到达与关联模式匹配的URL时调用,就像调用视图函数一个样子。查看源码会发现调用该函数首先会创建一个MyView类的实例,然后 # # 1、调用self.setup()方法,用于初始化一系列属性。 # 2、之后调用self.dispatch(),该方法查看request以确定本次请求是GET还是POST等,如果定义了匹配方法,则将请求转发给匹配方法; # 如果没有,则引发HttpResponseNotAllowed。本例中将调用我们自定义的get与post方法来响应请求 # # ps:基于面向对象的知识,上述方法,如果子类MyView未重写,则去父类View中寻找子应用的views.pyfrom django.shortcuts import render # Create your views here. from django.views import View from rest_framework.views import APIView from rest_framework.request import Request from rest_framework.response import Response from test2_sers import models from test2_sers.serializers2 import BookSerializer # from app应用名.序列化器类的py文件 import 自定义序列化器 ''' 序列化器的使用--写五个接口 -<1>【接口1:查询所有 Book--------》get -<2>【接口2:查询单个 BookDetail--》get -<3>【接口3:新增一个 Book--------》post -<4>【接口4:删除一个 BookDetail--》delete -<5>【接口5:修改一个 BookDetail--》put ''' # CBV # 1、获取多条数据 class Book(APIView): def get(self, request, *args, **kwargs): # \\取出所有数据(queryset对象) res = models.Book.objects.all() # \\创建序列化器对象—— BookSerializer对象 # = 借助序列化器——实例化 自定义的序列化器类,得到序列化器对象 ser = BookSerializer(instance=res, many=True) # print(type(ser)) # <class 'rest_framework.serializers.ListSerializer'> # \\ 拿到了数据(queryset对象),通过序列化器对象处理后,得到的是字典 # ser.data # 数据字典再经过 drf 的 Response,返回序列化好的json格式字符串,传给前端 return Response(ser.data) # 3\新增单个——使用最基础的序列化器,若要新增 必须重写create方法;若要修改,则必须重写 update方法 def post(self,request): # 关于post请求的数据格式-解说,见下方【总结】 # post 提交的数据 都在request.data,且是个字典形式 # <QueryDict: {'title': ['走去乡下过日子'], 'price': ['25'], 'publish': ['新世界出版社']}> print(request.data) # return Response('get it') # 使用序列化器进行反序列化时,需要对数据进行验证后,才能获取验证成功的数据或保存成模型类对象。 ser=BookSerializer(data=request.data) # 将需要反序列化的数据传给 data if ser.is_valid(): # 校验数据是否合法 ser.save() # 保存到数据库中----》运行,无法保存(因为未重写create/update方法)---》解决方法:在序列化器里 重写create方法 return Response(ser.data) # 保存成功 返回新增的当前对象 else: # 没有校验通过的错误信息 return Response(ser.errors) class BookDetail(APIView): # 2、获取单个数据对象 # def get(self, request, id, *args, **kwargs): # 传参方式,两种都可以 def get(self, request, id): res = models.Book.objects.all().filter(id=id).first() # 单个,去掉many=True # 加many=True和不加,ser不是同一个对象o~~ ser = BookSerializer(instance=res) # print(type(ser)) # <class 'test2_sers.serializers2.BookSerializer'> return Response(ser.data) # 4\修改单个对象 def put(self,request,id): # 通过id取到对象 res={'code':100,'msg':''} try: book=models.Book.objects.get(id=id) # 不要犯错只写id ser=BookSerializer(instance=book,data=request.data) ser.is_valid(raise_exception=True) ser.save() res['msg']='修改成功' res['result']=ser.data except Exception as e: res['code']=101 res['msg']=str(e) return Response(res) # 5\删除单个 def delete(self,request,id): response={'code':100,'msg':'删除成功'} # 可根据需求 增加判断条件 models.Book.objects.filter(id=id).delete() return Response(response) # 【总结】 # ----------------------------------- # 1、序列化器的作用——drf的序列化/反序列化 # =1. # 序列化, 序列化器会把模型对象转换成字典, 经过drf的Response以后变成json字符串 # -Book - -序列化器 - -->字典 - -通过drf: Response - -》json格式字符串 - -->传给前端 # =2. # 反序列化, 把客户端发送过来的数据, 经过request以后变成字典, 序列化器可以把字典转成模型 # json格式数据 - --drf:Request -->字典 - --序列化器 - --》Book # =3. # 反序列化, 完成数据校验功能 # ----------------------------------- # 2、序列化器对象的参数说明 # ser=BookSerializer(instance=res,many=True) # =1、instance=数据(queryset对象) # 序列化时,将模型类对象传入instance参数 # =2、many=True # = 如果结果是多条数据(多个对象),则要加上 many=True # 如果是单个对象,则不写 # =3、其他参数 # data # 反序列化时,将要被反序列化的数据传入data参数 # eg: serializer = Serializer(instance=None, data=empty, **kwarg) # context # 在构造Serializer对象时,还可通过context参数额外添加数据 # eg: serializer = AccountSerializer(account, context={'request': request}) # ----------------------------------- # 拓展: # 接口的幂等性 # post 请求不是接口幂等的,每次请求都是不一样的,就算是请求同一数据 # put get 请求是接口幂等的,即多次操作得到的结果和单次操作的结果 是一样的 # ----------------------------------- # 【post请求的数据格式】 # 一.HttpRequest.body # 1.1 当浏览器基于http协议的GET方法提交数据时(没有请求体一说),数据会按照k1=v1&k2=v2&k3=v3的格式放到url中,然后发送给django,django会将这些数据封装到request.GET中,注意此时的请求体request.body为空、无用 # # 1.2 当浏览器基于http协议的POST方法提交数据时,数据会被放到请求体中发送给django,django会将接收到的请求体数据存放于HttpRequest.body属性中. # 但该属性的值为Bytes类型(套接字数据传输都是bytes类型),而通常情况下直接处理Bytes、并从中提取有用数据的操作是复杂而繁琐的,好在django会对它做进一步的处理与封装以便我们更为方便地提取数据,具体如何处理呢? # # 当前端采用POST提交数据时,数据有三种常用编码格式,编码格式不同Django会有不同的处理方式 # # 编码格式1:application/x-www-form-urlencoded,是form表单默认编码格式 # # 编码格式2:multipart/form-data,上传文件专用格式 # # 编码格式2:application/json,提交jason格式字符串 # # #====》I: 当POST数据的编码格式为application/x-www-form-urlencoded时《==== # HttpRequest.body中的数据格式为b'a=1&b=2&c=3' # django会将其提取出来封装到request.POST中 # request.FILES此时为空 # # 如: # print(request.body) # b'a=1&b=2&c=3' # print(request.POST) # <QueryDict: {'a': ['1'],'b': ['2'],'c': ['3']} # print(request.FILES) # <MultiValueDict: {}> # # # #====》II: 当POST数据的编码格式为multipart/form-data时《==== # 详见:https://my.oschina.net/cnlw/blog/168466?fromerr=aQL9sTI2 # # HttpRequest.body中的数据格式为b'------WebKitFormBoundaryKtcwuksQltpNprep\r\nContent-Disposition: form-data;......',注意,文件部分数据是流数据,所以不在浏览器中显示是正常的 # django会将request.body中的非文件数据部分提取出来封装到request.POST中 # 将上传的文件部分数据专门提取出来封装到request.FILES属性中 # # 如: # print(request.body) # 不要打印它,打印则报错,因为它是数据流 # print(request.POST) # <QueryDict: {'a': ['1'],'b': ['2'],'c': ['3']} # print(request.FILES) # <MultiValueDict: {'head_img': [<InMemoryUploadedFile: 11111.jpg (image/jpeg)>]}> # # 强调: # 1、毫无疑问,编码格式2的数据量要大于编码格式1,如果无需上传文件,还是推荐使用更为精简的编码格式1 # 2、FILES will only contain data if the request method was POST and the <form> that posted to the request had enctype="multipart/form-data". Otherwise, FILES will be a blank dictionary-like object. # # #===》III: 当POST数据的编码格式为application/json时《==== # 此时在django后台,request.POST和request.FILES中是没有值的,都放到request.body中了,需要用json.loads对其进行反序列化 # # 如: # print(request.body) # b'{"a":1,"b":2,"c":3}' # print(request.POST) # <QueryDict: {}> # print(request.FILES) # <MultiValueDict: {}> # # # 1.3 如何设定POST提交数据的编码格式 # 前端往后台POST提交数据,常用技术有form表单和ajax两种 # form表单可以设置的数据编码格式有:编码格式1、编码格式2 # ajax可以设置的数据编码格式有:编码格式1、编码格式2、编码格式3 # # form表单可以通过属性enctype进行设置编码格,如下 # 编码格式1(默认的编码格式):enctype="application/x-www-form-urlencoded" # 编码格式2(使用form表单上传文件时只能用该编码):enctype="multipart/form-data"子应用的serializers.py# 序列化器类---用于序列化Book表 # 导入序列化器 from rest_framework import serializers # 导入异常-校验 from rest_framework.exceptions import ValidationError from test2_sers import models # 自定义校验规则,可以添加到序列化字段的validators属性 def check(data): if len(data)>10: raise ValidationError('最长不能超过10') else: return data class BookSerializer(serializers.Serializer): # 1、列出要序列化的字段 id = serializers.IntegerField(required=False) # required=False 可以不传值 # title=serializers.CharField(max_length=32,min_length=2,read_only=True) # source的用法1 ——修改字段前端显示的名字,真实的字段名是title,想让其在前端显示为name,则可以改写如下: name=serializers.CharField(max_length=32,min_length=2,read_only=True,source='title') price=serializers.DecimalField(max_digits=5, decimal_places=2) # 2、为字段增加校验规则 validators=[check,] # write_only=True 序列化时 不显示 # publish=serializers.CharField(max_length=32,write_only=True,validators=[check,]) # source的用法3 使用.的方法 获取字段数据 publish_addr=serializers.CharField(max_length=32,source='publish.addr') # objects.name # source的用法2 返回表模型中 方法的执行返回结果 test_result=serializers.CharField(source='test') # 为什么要重写create?——新增数据 # 因为继承了 serializers.Serializer,若想保存 新增的目标模型表的数据 就需要重写create def create(self, validated_data): res=models.Book.objects.create(**validated_data) print(res) return res # 重写 update方法 ——更新数据 def update(self, instance, validated_data): # instance 修改的对象,例如 book对象; validated_data 通过校验的反序列化的数据 instance.title=validated_data.get('title') instance.price=validated_data.get('price') instance.publish=validated_data.get('publish') instance.save() # 这个save() 不是drf的,是对象自己的 return instance # 一定要返回修改的数据对象 # 局部钩子: validate_字段名 # 需要带一个data,data就是该字段的数据 # 例如 给 title字段增加一个局部钩子 def validate_title(self,data): if data.startswith('sb'): raise ValidationError('不能以sb开头') else: return data # 全局钩子 def validate(self, attrs): # attrs 是全部数据 # title=attrs['title'] # 字典取值 可以用key 也可以用 get title=attrs.get('title') publish=attrs.get('publish') if title==publish: raise ValidationError('书名不能跟出版社同名') else: return attrs # 【总结】 # # 1、字段属性 # title=serializers.CharField(max_length=32,min_length=2,read_only=True) # price=serializers.DecimalField(max_digits=5, decimal_places=2) # publish=serializers.CharField(max_length=32,write_only=True) # # =1、CharField 必须要有的字段 # max_length # # =2、DecimalField 必须要有的字段 # max_digits # decimal_places # # =3、关于 read_only/write_only # ===序列化/反序列化 # 【序列化】 就是将对象转化方便传输和存储字节序列,例如json.dumps就是序列化(狭义的序列化,将字典转化为json字符串), # 这样得到的json字符串不仅直接可以在其他语言使用(跨平台比较好),而且可以在前后端进行传输交互(drf序列化器) # # 【反序列化】 恰恰相反,而是将字节序列转化为对象,json.loads是将json字符串转化为字典, # 是狭义的反序列化(因为在python, 一切皆对象,字典是dict( )), # 而在项目中,前端传过来的序列化数据通过反序列化得到对象,进一步可以通过ORM操作,存入数据库。 # # === read_only/write_only # 【read_only】表明该字段仅用于序列化输出,默认 False # read_only 是只用于读,不能写(可以理解为只能从后台到前台,后台操作后直接传前台), # 不能对字段进行修改操作(也就是不和数据库进行交互),用在序列化字段里。 # 例如数据表中,gender只用于传到前端展示。而不用于存到数据库 # # 【write_only】表明该字段仅用于反序列化输入,默认 False # write_only是只写不能读,可以理解为只能前台到后台, # 在后台做了逻辑操作后直接存数据库,在反序列化字段使用。 # 在数据表中,password就是反序列化,不需要传前台, # 而re_password是自定义反序列化字段,仅用作后台和密码进行比较, # 然后把结果返回前台,所以不存到数据库,在全局钩子使用时要pop掉 # # 【解说版2】 # read_only # 该字段仅用于 序列化输出(后端--》前端) # (read_only=True) # 即 后端往前端 传送数据时(序列化),该字段只用于显示在前端; # 如果从前端往后端输入数据时(反序列化),该字段则可以不传值(这需要提前给该字段增加 null=True 属性,允许字段在反序列化输入时 可以为空,否则数据库会报错) # # write_only # 该字段仅用于 反序列化输入 (前端--》后端) # (write_only=True) # 从前端往后端输入数据时(反序列化),该字段必须传值 # 后端往前端 传送序列化数据时(序列化),该字段不会显示在前端 # # 用于 反序列化时必填,写入数据库的字段,但是不想后面在前端显示 # (一般若不想让字段显示在前端,是直接注释字段;但是这样可能会在反序列化时出问题,因此使用 write_only=True 来控制字段 ) # 2、source的用法 # source的用法1 # —— 修改字段前端显示的名字,真实的字段名是title,想让其在前端显示为name,则可以改写如下: # 注意 设定的前端显示名 不能与真实的字段名相同,否则会报错 # name=serializers.CharField(max_length=32,min_length=2,read_only=True,source='title') # # source的用法2 # —— 若表模型中 定义有方法,则可以将方法执行的返回值 传给source # 例如 # -1- models.py的book模型中,有个test方法 # def test(self): # # python是强类型语言,不支持字符串和数字直接相加 # return self.title+str(self.price) # # -2- 则在序列化器类中的 需要序列化字段中 加上一个字段用于接收方法执行的返回值 # test_result=serializers.CharField(source='test') # # source的用法3 ——支持 xx.yy 的跨表操作 # 例如 # -1- models.py --两张一对多关系的publish表模型和 book表模型 # book表模型中,加上关联字段 # publish = models.ForeignKey(to='Publish',null=True,on_delete=models.CASCADE) # # publish表模型 # class Publish(models.Model): # name=models.CharField(max_length=32) # addr=models.CharField(max_length=32) # def __str__(self): # return self.name # # book表模型的序列化器类-- source='publish.addr' # publish_addr=serializers.CharField(max_length=32,source='publish.addr') # objects.name #
三、模型序列化器 ModelSerializer 的使用
1、模型序列化器 ModelSerializer 的使用
如果我们想要使用序列化器对应的是Django的模型类,DRF为我们提供了ModelSerializer模型类序列化器来帮助我们快速创建一个Serializer类。
ModelSerializer与常规的Serializer相同,但提供了:
- 基于模型类自动生成一系列字段
- 基于模型类自动为Serializer生成validators,比如unique_together
- 包含默认的create()和update()的实现
1 原来用的Serilizer跟表模型没有直接联系, 模型类序列化器ModelSerilizer,跟表模型有对应关系
2 使用——模板
class BookModelSerializer(serializers.ModelSerializer): class Meta: model=表模型 # 跟哪个表模型建立关系 fields=[字段,字段] # 序列化的字段,反序列化的字段 fields='__all__' # 所有字段都序列化,反序列化 exclude=[字段,字段] # 排除哪些字段(不能跟fields同时使用) read_only_fields=['price','publish'] # 序列化显示的字段 write_only_fields=['title'] # 反序列化需要传入的字段 extra_kwargs ={'title':{'max_length':32,'write_only':True}} depth=1 # 了解,跨表1查询,最多建议写3 # 重写某些字段 publish = serializers.CharField(max_length=32,source='publish.name') # 局部钩子,全局钩子,跟原来完全一样
3 新增,修改
-统统不用重写create和update方法了,在ModelSerializer中重写了create和update
2、serializers.SerializerMethodField()的用法
用法跟在 serializers.Serializer 里的一样
class BookModelSerializer(serializers.ModelSerializer): class Meta: model = models.Book fields = '__all__'
# 注意 不要写在了Meta类中!! publish = serializers.SerializerMethodField() def get_publish(self,obj): dic={'name':obj.publish.name,'addr':obj.publish.addr} return dic
3、序列化器类的嵌套——跨表获取数据
# 3 模型序列化器类的嵌套 class PublishModelSerializer(serializers.ModelSerializer): class Meta: model=models.Publish # fields='__all__' fields=['name','addr'] class BookModelSerializer(serializers.ModelSerializer): class Meta: model = models.Book fields = '__all__' # publish = serializers.SerializerMethodField() # def get_publish(self,obj): # dic={'name':obj.publish.name,'addr':obj.publish.addr} # return dic # 序列化类的嵌套——会按照嵌套的序列化类的规则,序列化目标字段 publish = PublishModelSerializer()
4、Response的应用——自定义响应体类
comResponse.pyclass CommonResponse(): def __init__(self): self.code=100 # 在调用该方法的序列化器类中,若code值变了,则修改;没有则新增/按默认值 self.msg='' @property # 将方法包装成数据属性,使用不需加括号,直接通过 对象.属性 def get_dic(self): return self.__dict__子应用-views.py# # 自定义Response类 # 数据操作的响应信息 写起来有些繁琐,可以自己定义响应体返回的数据的类,实例化得到对象 返回即可 # # 封装2---也可以自己封装一个response,继承drf 的 Response # 封装1 # 新建python文件 comResponse.py,创建 CommonResponse类 # =1= 需要使用该类 先导入 from myModelSerializer.comResponse import CommonResponse class BookDetail(APIView): def get(self, request, id): res = models.Book.objects.all().filter(id=id).first() ser = BookSerializer(instance=res) return Response(ser.data) def put(self, request, id): # =2= 实例化响应类 得到对象 res=CommonResponse() try: book = models.Book.objects.get(id=id) ser = BookSerializer(instance=book, data=request.data) ser.is_valid(raise_exception=True) ser.save() # res['msg'] = '修改成功' # res['result'] = ser.data # =3= 对象.属性 获取响应信息 res.code=100 res.msg='修改成功' res.result=['hello','hi'] # 新增的数据/按照字典新增的方法 except Exception as e: # res['code'] = 101 # res['msg'] = str(e) res.code=101 res.msg='未知错误' return Response(res.get_dic) def delete(self,request,id): response = {'code': 100, 'msg': '删除成功'} models.Book.objects.filter(id=id).delete() return Response(response) # ------------------------------------------------------ # Response 属性 # 响应状态码 status/content/等 from rest_framework.status import HTTP_201_CREATED from rest_framework.settings import DEFAULTS # 默认的drf控制界面的模板显示形式--JSON/浏览器 # 局部配置-drf控制界面的模板显示形式,粒度更小,仅用于局部;其余的未配置的则使用settings.py里的全局配置 from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer # 局部配置-step1 class BookDetail(APIView): renderer_classes=[BrowsableAPIRenderer,] # 局部配置-step2 def get(self, request, id): res = models.Book.objects.all().filter(id=id).first() ser = BookSerializer(instance=res) return Response(ser.data) def put(self, request, id): res=CommonResponse() try: book = models.Book.objects.get(id=id) ser = BookModelSerializer(instance=book, data=request.data) ser.is_valid(raise_exception=True) ser.save() res.msg='成功' res.result=['sdsasdf','asdfa'] except Exception as e: print(str(e)) res.msg = '未知错误' res.code = 101 return Response(data=res.get_dic,status=HTTP_201_CREATED) # Response(data=res.get_dic,status=HTTP_201_CREATED) 读源码/status.py def delete(self,request,id): response = {'code': 100, 'msg': '删除成功'} models.Book.objects.filter(id=id).delete() return Response(response)
【总结-练习代码】
models.pyfrom django.db import models # Create your models here. class Book(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=32,null=True) price = models.DecimalField(max_digits=5, decimal_places=2,null=True) publish = models.ForeignKey(to='Publish',null=True,on_delete=models.CASCADE) def test(self): # python是强类型语言,不支持字符串和数字直接相加 return self.title+str(self.price) class Publish(models.Model): name=models.CharField(max_length=32) addr=models.CharField(max_length=32) def __str__(self): return self.name子应用-views.pyfrom django.shortcuts import render # Create your views here. from rest_framework.views import APIView from rest_framework.response import Response from myModelSerializer import models # from app01.serializer import BookModelSerializer as BookSerializer # 一种替换序列化器的技巧 from myModelSerializer.serializer import BookModelSerializer from myModelSerializer.serializer import BookSerializer '''5个接口 1 查询所有 Book--》get 2 查询单个 BookDetali--》get 3 新增一个 Book--》post 4 删除一个 BookDetali--》delete 5 修改一个 BookDetali--》put 向地址books/1发送put请求 ''' class Book(APIView): def get(self, request, *args, **kwargs): res = models.Book.objects.all() # 借助序列化器 # 如果是多条,就是many=True # 如果是单个对象,就不写 # ser = BookSerializer(instance=res, many=True) ser = BookModelSerializer(instance=res, many=True) print(type(ser)) # rest_framework.serializers.ListSerializer # 通过序列化器得到的字典 # ser.data print(ser.data) return Response(ser.data) def post(self, request): # post提交的数据都在request.data 是个字典 print(request.data) # ser = BookSerializer(data=request.data) ser = BookModelSerializer(data=request.data) if ser.is_valid(): # 校验数据是否合法 ser.save() # 保存到数据库中 return Response(ser.data) else: # 没有校验通过的错误信息 return Response(ser.errors) # ============================================== class BookDetail(APIView): def get(self, request, id): res = models.Book.objects.all().filter(id=id).first() # 单个,去掉many=True # 加many=True和不加,ser不是同一个对象 ser = BookSerializer(instance=res) print(type(ser)) # app01.serializer.BookSerializer return Response(ser.data) def put(self, request, id): # 通过id取到对象 res = {'code': 100, 'msg': ''} try: book = models.Book.objects.get(id=id) ser = BookSerializer(instance=book, data=request.data) ser.is_valid(raise_exception=True) ser.save() res['msg'] = '修改成功' res['result'] = ser.data except Exception as e: res['code'] = 101 res['msg'] = str(e) return Response(res) def delete(self,request,id): response = {'code': 100, 'msg': '删除成功'} models.Book.objects.filter(id=id).delete() return Response(response)子应用-serializers.py# 序列化器类(序列化Book表) from rest_framework import serializers from rest_framework.exceptions import ValidationError from myModelSerializer import models # 一、Serializer序列化器 # BookSerializer class BookSerializer(serializers.Serializer): id=serializers.IntegerField(required=False) name=serializers.CharField(max_length=32,min_length=2,source='title') price=serializers.DecimalField(max_digits=5,decimal_places=2) publish=serializers.CharField(max_length=32,source='publish.name') publish_addr=serializers.CharField(source='publish.addr') # xxx=serializers.CharField(source='test') def create(self, validated_data): res=models.Book.objects.create(**validated_data) return res def update(self, instance, validated_data): instance.title=validated_data.get('title') instance.price=validated_data.get('price') instance.publish=validated_data.get('publish') instance.save() return instance # 二、ModelSerializer 表模型序列化器 # ModelSerializer 表模型序列化器,与模型表一一对应的序列化器;即专用于指定的表模型;一个表,可以有多个序列化器 # 表模型序列化器,继承serializers.Serializer;新增/修改 不需要手动重写 反序列化的 create /update 方法,因为序列化器源码已经重写了 反序列化的 create /update 方法 # 1 BookModelSerializer # version 1——序列化部分字段 class BookModelSerializer(serializers.ModelSerializer): # 3-1 重写'publish' publish=serializers.CharField(max_length=32,source='publish.name') # 3-2 新增'addr'字段 addr=serializers.CharField(source='publish.addr') #1、内部类 Meta class Meta: # 2、与哪张表建立关系,注意1 :变量model不加s,否则就与内置的models冲突了 model=models.Book # 3、需要序列化哪些字段 # fields=['id','title','price'] # 模型表中已有的字段 # 注意2:fields 为复数 fields=['id','title','price','publish','addr'] # 重写 'publish',让其在前端显示具体出版社信息;新增 'addr' 字段,需要在模型序列化器里定义该字段 # version 2——序列化/反序列化 所有字段,支持改写已有字段,但是不支持增加新的序列化字段 class BookModelSerializer(serializers.ModelSerializer): publish = serializers.CharField(max_length=32, source='publish.name') class Meta: model=models.Book fields='__all__' # version 3 —— 排除部分字段,不进行序列化 class BookModelSerializer(serializers.ModelSerializer): class Meta: model=models.Book # fields='__all__' exclude=['publish','price'] # 排除列表指定的字段,注意 不能和 fields 同时使用 # version 4 —— read_only_fields/write_only_fields/extra_kwargs class BookModelSerializer(serializers.ModelSerializer): class Meta: model=models.Book fields='__all__' read_only_fields=['price','publish'] # 只用于序列化输出 write_only_fields=['title',] # 只用于反序列化输入 / 可能有的drf版本不能使用(实际使用再看啦) # extra_kwargs:给字段额外添加参数——在序列化器里重写字段属性比较麻烦,因此可以使用extra_kwargs # extra_kwargs={'title':{'max_length':64},'price':{'validators':[check,]}} extra_kwargs={'title':{'max_length':64,'write_only':True},} # 也可以使用这种方式来指定write_only/write_only # 局部钩子/全局钩子 跟 Serializers序列化器的一样写法。注意:不要错写在内置类Meta类中!! # 了解 # version 5 —— depth 连表查询返回结果 class BookModelSerializer(serializers.ModelSerializer): # publish = serializers.CharField(max_length=32, source='publish.name') class Meta: model = models.Book fields = '__all__' depth=1 # 连表查询,将关联的publish表的数据一次全部获取,而不会只返回出版社的id;如果book关联两张表,则depth可以设为2; # 个人建议depth最多不要超过3,尽量不要用 # 2 ModelSerializer中 serializers.SerializerMethodField 的用法 class BookModelSerializer(serializers.ModelSerializer): class Meta: model = models.Book fields = '__all__' publish = serializers.SerializerMethodField() def get_publish(self,obj): dic={'name':obj.publish.name,'addr':obj.publish.addr} return dic # 3 模型序列化器类的嵌套 class PublishModelSerializer(serializers.ModelSerializer): class Meta: model=models.Publish # fields='__all__' fields=['name','addr'] class BookModelSerializer(serializers.ModelSerializer): class Meta: model = models.Book fields = '__all__' # publish = serializers.SerializerMethodField() # def get_publish(self,obj): # dic={'name':obj.publish.name,'addr':obj.publish.addr} # return dic # 序列化类的嵌套——会按照嵌套的序列化类的规则,序列化目标字段 publish = PublishModelSerializer()
参考:python/Django-rest-framework框架/2-drf-序列化组件
关于定义序列化器时,read_only和write_only有什么作用