DRF -- Django REST framework

版权声明:转载请标明出处 https://blog.csdn.net/gymaisyl/article/details/84036713

模型类序列化器相关用法可以参考: 模型类序列化器讲解

在web开发过程中,有前后端分离和前后端不分离两种情况;

  • 前后端不分离:后端通过模板渲染/返回Json数据/重定向等方法,将后端的数据返回给前端;
  • 前后端分离:后端仅需要返回前端所需要的数据,至于前端界面的数据如何获取,或者html数据如何展示,后端是不需要再过问的;

前后端分离的应用模式的特点:

  • 前后端分离情况下,前端和后端的耦合程度降低
  • 在这种情况下,我们后端写的视图被称为接口,也叫API,前端会根据访问不同的接口对数据库进行增删改查的动作。

RESTful

所以在有些情况下,使用前后端分离的应用模式就显得比较友好了;但是就像一千个读者就有一千个哈姆雷特一样。如果没有一个相对的约束的话,那么一千个程序员就会有一千零一个设计API的风格方式了,然后RESTful这样一个设计理念就出现了。

Roy Thomas Fielding(提出REST的大牛)将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。维基百科称其为“具象状态传输”,国内大部分人理解为“表现层状态转化”。

RESTful是一种开发理念。维基百科说:REST是设计风格而不是标准。 REST描述的是在网络中client和server的一种交互形式;REST本身不实用,实用的是如何设计 RESTful API(REST风格的网络接口),一种万维网软件架构风格。

做个对比大家可能更能感受到它的魅力所在:

我们先来具体看下RESTful风格的url,比如我要查询商品信息,那么
非REST的url: http://.../queryGoods?id=1001&type=t01
REST的url: http://.../t01/goods/1001

相对而言,是不是间接很多很多。
具体关于RESTful的信息,有兴趣的可以看一下
RESTful详细介绍


DRF:

那么说到这,Django中,我们想要实现REST的话,那么Django REST framework就应运而生了。

官方文档

Django REST framework 框架是一个用于构建Web API 的强大而又灵活的工具。
通常简称为DRF框架 或 REST framework。

特点:

  • 提供了定义序列化器Serializer的方法,可以快速根据 Django ORM 或者其它库自动序列化/反序列化;
  • 提供了丰富的类视图、Mixin扩展类,简化视图的编写;
  • 丰富的定制层级:函数视图、类视图、视图集合到自动生成 API,满足各种需要;
  • 多种身份认证和权限认证方式的支持;
  • 内置了限流系统;
  • 直观的 API web 界面;
  • 可扩展性,插件丰富

安装配置
安装和配置,在官方文档中都有详细介绍,这里只做简单的记录一下:

  1. 安装: pip install djangorestframework
  2. 配置: 在INSTALL_APPS中增加 ‘rest_framework’

然后,现在开始实现一下它的强大之处。

Serializer 序列化

本文代码实现前提:我已经在子应用的models.py文件中定义了几个模型类

# 定义图书模型类BookInfo
class BookInfo(models.Model):
    btitle = models.CharField(max_length=20, verbose_name='名称')
    bpub_date = models.DateField(verbose_name='发布日期')
    bread = models.IntegerField(default=0, verbose_name='阅读量')
    bcomment = models.IntegerField(default=0, verbose_name='评论量')
    is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')
    image = models.ImageField(upload_to='booktest', verbose_name='图片', null=True)
    
    def __str__(self):
        """定义每个数据对象的显示信息"""
        return self.btitle
#定义英雄模型类HeroInfo
class HeroInfo(models.Model):
    GENDER_CHOICES = (
        (0, 'male'),
        (1, 'female')
    )
    hname = models.CharField(max_length=20, verbose_name='名称')
    hgender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')
    hcomment = models.CharField(max_length=200, null=True, verbose_name='描述信息')
    hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书')  # 外键
    is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')

    def __str__(self):
        return self.hname
  1. Serializer定义
    ①针对一个模型类创建一个对应的序列化器,我们的Serializer的定义是在子应用下新创建的一个serializer.py文件中;
    ②序列化类是继承于rest_framework.serializers.Serializer;
    ③serializer不是只能为数据库模型类定义,也可以为非数据库模型类的数据定义。serializer是独立于数据库之外的存在。
# 为模型提供一个序列化器-- 书模型类提供序列化器
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)

同时,为了后面代码的实现,我们也给HeroInfo类提供一个序列化器:

# 为模型提供一个序列化器-- 英雄模型类提供序列化器
class HeroInfoSerializer(serializers.Serializer):
    GENDER_CHOICES = (
        (0, 'male'),
        (1, 'female')
    )
    id = serializers.IntegerField(label='ID', read_only=True)
    hname = serializers.CharField(label='名字', max_length=20)
    hgender = serializers.ChoiceField(choices=GENDER_CHOICES, label='性别', required=False)
    hcomment = serializers.CharField(label='描述信息', max_length=200, required=False, allow_null=True)
  1. 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属性获取

  1. 序列化的基本使用
    1)先获得一个书籍对象
In [9]: from testfile.models import BookInfo, HeroInfo                                                                              

In [10]: book = BookInfo.objects.get(id=1)                                                                                          

In [11]: book                                                                                                                       
Out[11]: <BookInfo: 西游记>

2)创建序列化对象

from testfile.serializers import BookInfoSerializer, HeroInfoSerializer 
n [12]: s = BookInfoSerializer(book)                                                                                               


3)获取序列化数据

通过data属性可以获取序列化后的数据

In [13]: s.data                                                                                                                     
Out[13]: {'id': 1, 'bcomment': 10, 'bread': 10, 'image': None, 'heroinfo_set': [1, 2], 'btitle': '西游记', 'bpub_date': '1988-01-01'}

4)如果要被序列化的是包含多条数据的查询集QuerySet,可以通过添加many=True参数补充说明

In [20]: books = BookInfo.objects.all()                                                                                             

In [21]: books                                                                                                                      
Out[21]: <QuerySet [<BookInfo: 西游记>, <BookInfo: 水浒传>, <BookInfo: 繁星春水>, <BookInfo: 三国演义>, <BookInfo: 红楼梦>, <BookInfo: 骆驼祥子>]>

In [22]: s = BookInfoSerializer(books, many=True)                                                                                   

In [23]: s.data                                                                                                                     
Out[23]: [OrderedDict([('id', 1), ('btitle', '西游记'), ('bpub_date', '1988-01-01'), ('bread', 10), ('bcomment', 10), ('image', None('heroinfo_set', [1, 2])]), OrderedDict([('id', 2), ('btitle', '水浒传'), ('bpub_date', '1988-09-09'), ('bread', 0), ('bcomment', 0)'image', None), ('heroinfo_set', [3, 4])]), OrderedDict([('id', 3), ('btitle', '繁星春水'), ('bpub_date', '1995-01-24'), ('bread', 3('bcomment', 5), ('image', None), ('heroinfo_set', [])]), OrderedDict([('id', 4), ('btitle', '三国演义'), ('bpub_date', '1978-05-07''bread', 6), ('bcomment', 33), ('image', None), ('heroinfo_set', [5])]), OrderedDict([('id', 5), ('btitle', '红楼梦'), ('bpub_date',017-09-02'), ('bread', 9), ('bcomment', 14), ('image', 'booktest/cat.jpg'), ('heroinfo_set', [6, 7])]), OrderedDict([('id', 6), ('btitle', '骆驼祥子'), ('bpub_date', '1987-11-13'), ('bread', 9), ('bcomment', 4), ('image', None), ('heroinfo_set', [])])]

  1. 序列化的使用–关联对象嵌套

如果需要序列化的数据中包含其他关联对象,则对关联对象的序列化需要说明。

1) PrimaryKeyRelatedField – 该字段将被序列化为关联对象的主键
在HeroInfoSerializer中添加:

hbook = serializers.PrimaryKeyRelatedField(label=‘图书’, read_only=True)

In [4]: hero = HeroInfo.objects.get(id=1)                                                                                           

In [5]: hero                                                                                                                        
Out[5]: <HeroInfo: 猪悟能>

In [6]: s = HeroInfoSerializer(hero)                                                                                                

In [7]: s.data                                                                                                                      
Out[7]: {'id': 1, 'hname': '猪悟能', 'hgender': 0, 'hbook': 1, 'hcomment': None}

这里的外键hbook显示的就是关联对象的主键 1

2) StringRelatedField – 该字段将被序列化为关联对象的字符串表示方式(即__str__方法的返回值)
将1)中添加的数据删除,在HeroInfoSerializer中添加:

hbook = serializers.StringRelatedField(label=‘图书’) # 关联对象的__str__显示的数据信息

In [4]: hero = HeroInfo.objects.get(id=2)                                                                                           

In [5]: hero                                                                                                                        
Out[5]: <HeroInfo: 孙悟空>

In [6]: s = HeroInfoSerializer(hero)                                                                                                

In [7]: s.data                                                                                                                      
Out[7]: {'hname': '孙悟空', 'hgender': 0, 'id': 2, 'hcomment': None, 'hbook': '西游记'}

这里显示的就是关联对象中定义__str__希望显示的字符串内容了,这里显示的是书籍对应的名称。

3)使用关联对象的序列化器 – 顾名思义,就是将外键作为关联对象的序列化器类的对象
将 2)中添加的数据删除,在HeroInfoSerializer中添加:

hbook = BookInfoSerializer()

In [4]: hero = HeroInfo.objects.get(id=3)                                                                                           

In [5]: hero                                                                                                                        
Out[5]: <HeroInfo: 武松>

In [6]: s = HeroInfoSerializer(hero)                                                                                                

In [7]: s.data                                                                                                                      
Out[7]: {'hgender': 0, 'hname': '武松', 'hbook': OrderedDict([('id', 2), ('btitle', '水浒传'), ('bpub_date', '1988-09-09'), ('bread' ('bcomment', 0), ('image', None), ('heroinfo_set', [3, 4])]), 'hcomment': '醉拳', 'id': 3}

这里数据的显示就是外键对应的序列化后的数据了。

4)还有其他几个方法,这里列出来,但是不做相关演示了。
①HyperlinkedRelatedField
此字段将被序列化为获取关联对象数据的接口链接
②SlugRelatedField
此字段将被序列化为关联对象的指定字段数据
③ 重写to_representation方法
序列化器的每个字段实际都是由该字段类型的to_representation方法决定格式的,可以通过重写该方法来决定格式。
注意,to_representations方法不仅局限在控制关联对象格式上,适用于各个序列化器字段类型。

  1. many参数

如果关联的对象数据不是只有一个,而是包含多个数据,如想序列化图书BookInfo数据,每个BookInfo对象关联的英雄HeroInfo对象可能有多个,此时关联字段类型的指明仍可使用上述几种方式,只是在声明关联字段时,多补充一个many=True参数即可。

在BookInfoSerializer中添加:

heroinfo_set = serializers.PrimaryKeyRelatedField(label=‘英雄’, read_only=True, many=True)

In [4]: book = BookInfo.objects.get(id=2)                                                                                           

In [5]: book                                                                                                                        
Out[5]: <BookInfo: 水浒传>

In [6]: s = BookInfoSerializer(book)                                                                                                

In [7]: s.data                                                                                                                      
Out[7]: {'bcomment': 0, 'bpub_date': '1988-09-09', 'id': 2, 'btitle': '水浒传', 'image': None, 'heroinfo_set': [3, 4], 'bread': 0}

字段与类型

在这里插入图片描述

在这里插入图片描述

反序列化

反序列化的使用,包括两部分:
一:数据校验
二:反序列化

  1. 数据校验
    使用序列化器对数据进行反序列化之前,必须要对数据进行校验,数据的校验又可以分为:
    1)序列化器自带的检验方法
    2)自定义校验方法

①自定义的校验方法:is_valid()
在获得反序列数据之前,需要通过is_valid()进行对数据的校验,验证成功返回True,验证不成功返回False。

例1:False案例

In [4]: s = BookInfoSerializer(data=data)                                                                                           

In [8]: data = {}                                                                                                                   

In [9]: s = BookInfoSerializer(data=data)                                                                                           

In [10]: s.is_valid()                                                                                                               
Out[10]: False

当然,关于错误信息,我们是可以通过 errors 获取属性的错误信息,信息内容包括字段和字段的错误;
非字段的错误,可以通过修改REST framework配置中的NON_FIELD_ERRORS_KEY来控制错误字典中的键名。

In [11]: s.errors                                                                                                                   
Out[11]: {'btitle': [ErrorDetail(string='This field is required.', code='required')]}

例2:True案例
对于验证成功的数据,我们可以通过 validated_data 获取到,这个和之前的序列化不一样,序列化是通过data获得序列化后的数据的。

In [13]: data = {'btitle': 'python'}                                                                                                

In [14]: s = BookInfoSerializer(data=data)                                                                                          

In [15]: s.is_valid()                                                                                                               
Out[15]: True

In [16]: s.errors                                                                                                                   
Out[16]: {}

In [17]: s.validated_data                                                                                                           
Out[17]: OrderedDict([('btitle', 'python')])

例3:错误抛出案例

is_valid()方法还可以在验证失败时抛出异常serializers.ValidationError,可以通过传递 raise_exception=True 参数开启,REST framework接收到此异常,会向前端返回HTTP 400 Bad Request响应;

In [19]: data = {}                                                                                                                  

In [20]: s = BookInfoSerializer(data=data)                                                                                          

In [21]: s.is_valid(raise_exception=True) 

ValidationError: {'btitle': [ErrorDetail(string='This field is required.', code='required')]}

自定义验证

1) validate_<filed_name> : 针对单一字段增加验证要求
在对应的序列化器中添加自定义验证

例1:在BookInfoSerializer序列化器中添加下面的代码

def validate_btitle(self, value):
        if 'python' not in value.lower():
            raise serializers.ValidationError('参数不是我想要的')
        return value

ipython中执行的结果

In [4]: data = {'btitle': 'pythons'}                                                                                                

In [5]: s = BookInfoSerializer(data=data)                                                                                           

In [6]: s.is_valid()                                                                                                                
Out[6]: True

In [7]: s.validated_data                                                                                                            
Out[7]: OrderedDict([('btitle', 'pythons')])

In [8]: data = {'btitle': 'django'}                                                                                                 

In [9]: s = BookInfoSerializer(data=data)                                                                                           

In [10]: s.is_valid()                                                                                                               
Out[10]: False

In [11]: s.errors                                                                                                                   
Out[11]: {'btitle': [ErrorDetail(string='参数不是我想要的', code='invalid')]}

2)valildate :多个字段之间的比较验证
在BookInfoSerializer序列化器中添加下面的代码

  def validate(self, attrs):
        bread = attrs['bread']
        bcomment = attrs['bcomment']
        if bread < bcomment:
            raise serializers.ValidationError('我需要阅读量比评论量大的数据,你不符合')

执行过程和结果如下:

In [6]: data = {'btitle': 'python02', 'bread': 30, 'bcomment':50}                                                                   

In [7]: s = BookInfoSerializer(data=data)                                                                                           

In [8]: s.is_valid()                                                                                                                
Out[8]: False

In [9]: s.errors                                                                                                                    
Out[9]: {'non_field_errors': [ErrorDetail(string='我需要阅读量比评论量大的数据,你不符合', code='invalid')]}

这里抛出的错误内容不是字段的错误信息,也就是非字段的错误信息,所以我们的errors中获取到的是 ‘non_field_errors’,如果希望显示结果不是这个,是可以在test_frame的配饰中进行修改的。

3)validators : 在字段中添加validators选项参数,具体的验证要求,可以在序列化器外面进行函数定义。

在BookInfoSerializer序列化器外面定义一个validators函数,并把
序列化器中的validate_btitle删除;

def about_python(value):
    if 'python' not in value.lower():
        raise serializers.ValidationError('参数不是我想要的')

序列化器中的btitle字段添加一个参数:

    btitle = serializers.CharField(label='名称', max_length=20, validators=[about_python])

执行过程和结果如下:

In [4]: data = {'btitle': 'django', 'bread': 30, 'bcomment':50}                                                                     

In [5]: s = BookInfoSerializer(data=data)                                                                                           

In [6]: s.is_valid()                                                                                                                
Out[6]: False

In [7]: s.errors                                                                                                                    
Out[7]: {'btitle': [ErrorDetail(string='参数不是我想要的', code='invalid')]}

validators 的总结:
(1):在序列化器外面进行定义,然后再字段里面添加这个验证的参数;
(2):validators的作用和 validate_<filed_name> 差不多,只不过一个是再序列化器内部进行定义,只是针对这一个序列化器使用;而validators定义是在序列化器外面的,所以的其他的序列化器也可以使用。
(3)validators这个函数和 validate_<filed_name> 不同的是,validators如果验证成功的话,是不需要return的,只是在验证失败的时候,会抛出异常。

当然,validators在DRF中是有提供的,上面的是我们自定义的:

(1)UniqueValidator
单字段唯一,如:

from rest_framework.validators import UniqueValidator
slug = SlugField(
max_length=100,
validators=[UniqueValidator(queryset=BlogPost.objects.all())]
)

(2)UniqueTogetherValidation
联合唯一,如:

from rest_framework.validators import UniqueTogetherValidator
class ExampleSerializer(serializers.Serializer):
# …
class Meta:
validators = [
UniqueTogetherValidator(
queryset=ToDoItem.objects.all(),
fields=(‘list’, ‘position’)
)
]

  1. 反序列化–保存

1)在进行序列化验证之后,我们就可以基于validated_data完成数据对象的创建,可以通过实现create()和update()两个方法来实现。

我们在序列化器中添加两个函数 create()和 update()

    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

我们需要进行数据对象的创建,我们可以使用s.save() ,但是又如如何判断什么时候调用create,什么时候调用update呢,那就得看我们在创建序列化对象的时候,传递的参数了。

简单来说:
如果 instance 有值,那么就是存在模型对象,那么我们再传递data参数的时候,我们就会调用update,来进行数据的更新。

反之,如果没有 instance , 或者没有给它传值,那么我们就会调用 create() , 进行数据的创建。

2)当然,我们上面只是进行模型对象的创建,我们还需要将数据保存到数据库中,我们可以直接在函数中直接将数据添加到数据库就可以了。

    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

说明
1)save()调用的时候,也可以进行参数传递,传递的参数,可以在create() / update() 中获取到。

2)我们在进行数据的更新的时候,只会把需要更新的数据部分传递进来,但是,如果在没有传递的字段中,是required=True,必须传递,那么应该如何解决呢?
很简单,我们可以在创建序列化对象的时候,将partial=True写在后面,这样就允许部分数据的更新了。

s = BookInfoSerializer(book, data={'btitle: 'python' , partial=True)

猜你喜欢

转载自blog.csdn.net/gymaisyl/article/details/84036713