正在进入ing...

Django rest_framework 源码学习笔记(一)之序列化器

发布时间:2020-12-14 浏览量: 1506 文章分类: python

Django rest_framework 也简称叫做DRF,虽然有使用,但是用的并不多,顺带这次有时间,就抽空也看了一下源码。果然自己的认知加深了很多,而且之前确实我自己用的很潜,所以希望可以将自己学习的收获记录一下。

前言

整体来说DRF是一个不错的框架,也遵循了restful风格,作者也贴心的帮助实现了很多的功能,让快速出活变的更简单。所以还是值得深入学习一下,同时理解一下作者的思路。

因为这个框架涉及的内容还是很庞大,所以可能会拆分开 一点一点来进行总结。

根据官网的学习方式,我们也从序列化器开始从代码看进去。serializers

能看到这篇文章的同学,我假设你已经会安装使用django,同时也对pythondjango有一定的了解 不会DRF也没有问题的,认真看,保证你能看得懂

序列化器

导入

`from rest_framework import serializers

官方一共提供了以下6个方法,让我们使用。

BaseSerializer(基类)
SerializerMetaclass(不常用)
Serializer(常用)
ListSerializer(不常用)
ModelSerializer(常用)
HyperlinkedModelSerializer (不常用)

对了,这里先同步一下 数据的格式和样式,为了方便阅读起见,我尽量都用最简模式创建。

from django.db import models

class ArticleType(models.Model):
    type_name = models.CharField(max_length=32)

class Author(models.Model):
    name = models.CharField(max_length=64)

class ArticleInfo(models.Model):
    title = models.CharField(max_length=100, verbose_name='标题')
    content = models.TextField(verbose_name='正文')
    article_class = models.ForeignKey('ArticleType',on_delete= models.DO_NOTHING, verbose_name='文章分类')
    auth = models.ForeignKey('Author',on_delete=models.CASCADE,verbose_name='作者')
    views = models.IntegerField(default= 0, verbose_name='浏览量')
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='发布时间')
    is_true = models.BooleanField(default=True, verbose_name='是否显示')

接下来就正常的同步数据,创建表,然后就开始我们这次的正题了。

基于Serializer类的实现

这是一个基础类,可以快速理解序列化器,我们先来一个最简单的,通过url实现获取文章的分类。(别忘记先去增加一下假数据)不用多,我的数据样式如下 Django rest_framework  源码学习笔记 接下来我们先按照传统的模式实现一下。

from rest_framework import serializers # 导入序列化器
from django.views import View # 导入django视图
from web_api.models import artile_models # 引入自己实现的model类
from django.http import JsonResponse # 引入django的JsonResponse

class ArticleTypeView(APIView):
    def get(self,request,*args,**kwargs):
        # 定义存放返回的json数据列表
        ret = {'article_type':[]}
        # 查询数据
        article_type_obj = xuexi01_models.ArticleType.objects.all()
        # 遍历装载
        for item in article_type_obj:
            ret['article_type'].append(
                {
                    'id':item.id,
                    'title':item.type_name
                }
            )
        return JsonResponse(ret)

接下来,在用DRFSerializer类改写一下

from django.views import View
from web_api.models import artile_models
from django.http import JsonResponse
from rest_framework import serializers


class ArticleTypeSerializers(serializers.Serializer):
    # 继承 rest_framework 的serializers中的Serializer类
    id = serializers.IntegerField()
    type_name = serializers.CharField()

class ArticleTypeView(View):
    def get(self,request,*args,**kwargs):
        # 定义存放返回的json数据列表
        ret = {'article_type':[]}
        # 查询数据
        article_type_obj = xuexi01_models.ArticleType.objects.all()
        # 序列化
        ser = ArticleTypeSerializers(instance=article_type_obj,many=True)
        ret['article_type'] = ser.data
        return JsonResponse(ret)

如果仔细对比,会发现ArticleTypeView里面只是将以前的循环填充数组,变成了引用一个外部类,然后传给他参数,在将结果赋值就完成了。

一步一步来,先继续研究ArticleTypeSerializers的实现,id = serializers.IntegerField()、type_name = serializers.CharField()有没有发现他很像我们在models定义的数据格式,这里其实就是你需要什么数据,就可以列在这里,序列化器就会根据你列的数据进行序列化。 不用担心格式不知道的问题,在serializers.py的实现中,作者导入了他支持的模式,所以我们能看到支持的字段格式如下,基本和Django的字段一致。只是将models.CharField()变成了serializers.CharField()而已。

# 附所有支持的字段格式
from rest_framework.fields import (  # NOQA # isort:skip
    BooleanField, CharField, ChoiceField, DateField, DateTimeField, DecimalField,
    DictField, DurationField, EmailField, Field, FileField, FilePathField, FloatField,
    HiddenField, HStoreField, IPAddressField, ImageField, IntegerField, JSONField,
    ListField, ModelField, MultipleChoiceField, NullBooleanField, ReadOnlyField,
    RegexField, SerializerMethodField, SlugField, TimeField, URLField, UUIDField,
)

在接下来继续看我们在实例化了类以后,传递的instancemany两个参数是干嘛的。 由于Serializer继承了BaseSerializer.所以需要的话,可以直接从源码点进去具体看作者的实现。 暂时只要先知道instance可以将数据库查询后的QuerySet结果传给他、many主要是告诉他 结果是多条还是单条,如果是多条就是True,单条就是False或者不传。

那么 现在又出现了一个新的问题,因为返回的字段都是我自己在数据库定义好的,假设,我想将这里的id在返回给前端的时候变成aid,那么,我们还可以在序列化器的时候增加source字段。比如

    aid = serializers.IntegerField(source='id')
    name = serializers.CharField(source='type_name')

也就是在source里面填写原始的字段名,然后变量写我们想返回前端的字段 那么返回的结果也会从之前的

{"article_type":[{"id":1,"type_name":"前端开发"},{"id":2,"type_name":"后端开发"},{"id":3,"type_name":"运维心得"},{"id":4,"type_name":"心情杂谈"}]}

变成

{"article_type":[{"aid":1,"name":"前端开发"},{"aid":2,"name":"后端开发"},{"aid":3,"name":"运维心得"},{"aid":4,"name":"心情杂谈"}]}

这样也就实现了我们自定义的变量名转换。

基于ModelSerializer 的实现

还是同样的数据,我们在切换一下序列化器的代码。

class ArticleTypeSerializers(serializers.ModelSerializer):
    # 继承 rest_framework 的serializers中的Serializer类
    class Meta:
        model = artile_models.ArticleType
        fields = '__all__'

其余代码不变,主要我们主要是将ArticleTypeSerializers类的继承从Serializer变成了ModelSerializer。根据官方的介绍,在常规的基础上,增加了.create().update()方法的实现,我们只要将数据库进行绑定,则自动填充一组数据。 也可以通过fields来指定我们需要的字段. 如果我们将fields = '__all__'变成fields = ['type_name']那么返回的内容就没有id这个字段了。 同时值得注意的是,因为ModelSerializer类继承的是Serializer类。所以他们其实也是可以混合使用的。 接下来,还是老问题,我想将id变成sid,则可以这么写。所以可以看到,直观的结论就是ModelSerializer可以节省我们更多的代码

class ArticleTypeSerializers(serializers.ModelSerializer):
    sid = serializers.CharField(source='id')
    class Meta:
        model = artile_models.ArticleType
        fields = ['type_name','sid']

至于官方说的 .create().update()方法,我后面在统一总结。这里先跳过。(已经更新,点击这里查阅)

至于那些不常用的,就暂时先不放这里说了。以后用到在慢慢补充。

补充方法说明

刚才我们一直都是在拿单表的数据,也就是没有涉及到ForeignKeyManyToManyField的情况。 现在我们在随便给ArticleInfoAuthor表都增加一点数据。 然后在使用同样的方式完成

class ArticleInfoSerializers(serializers.ModelSerializer):
    class Meta:
        model = artile_models.ArticleInfo
        fields = '__all__'

class ArticleInfoView(View):
    def get(self,request,*args,**kwargs):
        ret = {'article_list': []}
        article_info_obj = artile_models.ArticleInfo.objects.all()
        ser = ArticleInfoSerializers(instance=article_info_obj,many=True)
        ret['article_list'] = ser.data
        return JsonResponse(ret)

页面返回结果就出现了,我们创建的2篇文章数据

{"article_list":[{"id":1,"title":"第一篇文章","content":"这是第一篇文章的正文","views":20,"create_time":null,"is_true":true,"article_class":1,"auth":1},{"id":2,"title":"第二篇文章","content":"这是第二篇文章的正文","views":35,"create_time":null,"is_true":true,"article_class":1,"auth":1}]}

首先说说 这里存在的问题article_classauth返回的都是关联表的数字id1,而不是对应的中文。 要解决这个问题,第一种方式是使用.解决,也就是下面这样

class ArticleInfoSerializers(serializers.ModelSerializer):
    auth = serializers.CharField(source="auth.name")
    class Meta:
        model = xuexi01_models.ArticleInfo
        fields = '__all__'

因为我们在数据库取回来的auth也是一个对象,所以可以通过.的方法继续往下取值。 所以也就是上面那样。这样可以解决。

{"article_list":[{"id":1,"auth":"endpein","title":"第一篇文章","content":"这是第一篇文章的正文","views":20,"create_time":null,"is_true":true,"article_class":1},{"id":2,"auth":"endpein","title":"第二篇文章","content":"这是第二篇文章的正文","views":35,"create_time":null,"is_true":true,"article_class":1},{"id":3,"auth":"endpein","title":"这是第三篇文章","content":"这是第三篇文章的正文","views":15,"create_time":"2020-12-14T20:40:07.253158","is_true":true,"article_class":2}]}

可以看到,作者auth已经变成了endpein

第二种方法就是使用DRF里面自带的depth深度参数

class ArticleInfoSerializers(serializers.ModelSerializer):
    class Meta:
        model = artile_models.ArticleInfo
        fields = '__all__'
        depth = 1

而这种返回的格式在和上面的有一些区别,类似下面这样

{"article_list":[{"id":1,"title":"第一篇文章","content":"这是第一篇文章的正文","views":20,"create_time":null,"is_true":true,"article_class":{"id":1,"type_name":"前端开发"},"auth":{"id":1,"name":"endpein"}},{"id":2,"title":"第二篇文章","content":"这是第二篇文章的正文","views":35,"create_time":null,"is_true":true,"article_class":{"id":1,"type_name":"前端开发"},"auth":{"id":1,"name":"endpein"}},{"id":3,"title":"这是第三篇文章","content":"这是第三篇文章的正文","views":15,"create_time":"2020-12-14T20:40:07.253158","is_true":true,"article_class":{"id":2,"type_name":"后端开发"},"auth":{"id":1,"name":"endpein"}}]}

可以看到auth变成了一个字典,返回包含了keyvalue。 所以其实depth参数 适用于ForeignKeyManyToManyField两种方法。 具体如何使用就需要看个人的选择来决定。

假如我们使用了depth参数,但是对返回的结果又不是很满意,希望可以有更细粒度的控制。那么我们也可以使用SerializerMethodField自定义序列化器 例如:

class ArticleInfoSerializers(serializers.ModelSerializer):
    article_class = serializers.SerializerMethodField()
    class Meta:
        model = artile_models.ArticleInfo
        fields = '__all__'
        depth = 1
    def get_article_class(self,row):
        return row.article_class.type_name

这样第一种方式和第二种方式则返回的样式就一致了。(row就是每行返回的对象,我们可以用.的方法继续往下找) SerializerMethodField 自定义序列化器,在使用的同时,需要在下面增加对应的get_xxx 函数来完成具体的数据实现

补充说明

日常的数据库中,除了常用字段ForeignKeyManyToManyField外,有时候还会使用choices来指定选项。 这种的只要在source='get_字段名_display'即可

extra_kwargs参数

可以给Meta增加或修改字段选项参数 还是前面的例子,假设我现在对于直接生成的字段属性不是很满意,需要对生成的模型类字段进行一些增加属性,我们可以直接在类中直接显示指明字段的方法来覆盖article_class = serializers.SerializerMethodField(read_only=True)当然也可以用下面这样的方式操作。

class ArticleInfoSerializers(serializers.ModelSerializer):
    class Meta:
        model = artile_models.ArticleInfo
        fields = '__all__'
        # 添加修改字段选项参数
        extra_kwargs = {
            # 如果有多个字段,就用,间隔
            "article_class":{
                "read_only":True
            }
        }

当然也可以使用read_only_fields = ('title',)的方式来给字段设置让字段只参与序列化过程,不过这个方式个人感觉不如上面的extra_kwargs直观和方便