DRF初识与序列化

一、Django的序列化方法

1、为什么要用序列化组件

做前后端分离的项目,我们前后端数据交互一般都选择JSON,JSON是一个轻量级的数据交互格式。
那么我们给前端数据的时候都要转成json格式,那就需要对我们从数据库拿到的数据进行序列化。

 

2、表的构建

复制代码
CHOICES = ((1, "python"), (2, "linux"), (3, "go"))

# 书籍表
class Book(models.Model):
    title = models.CharField(max_length=64)
    category = models.IntegerField(choices=CHOICES)  # 书籍分类
    pub_time = models.DateField()
    publisher = models.ForeignKey(to="Publisher")
    authors = models.ManyToManyField(to="Author")

    class Meta:
        verbose_name = '书籍'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.title


# 出版社表
class Publisher(models.Model):
    title = models.CharField(max_length=64)

    class Meta:
        verbose_name = '出版社'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.title


# 作者表
class Author(models.Model):
    name = models.CharField(max_length=32)
    class Meta:
        verbose_name = '作者'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name
View Code
复制代码

 

3、希望构建的字典格式

复制代码
book_list = [
    {
        "id": 1,
        "title": "",
        "publisher": {  # 外键
            "id": 1
            "title": ""
        },
        "authors": [{}, {}]  # 多对多
    },
    {
        "id": 2,
        "title": "",
        "publisher": {  # 外键
            "id": 1
            "title": ""
        },
        "authors": [{}, {}]  # 多对多
    },

]
复制代码

 

4、方法一

复制代码
在Django中使用Json模块序列化
class BooksView(views.View):
    def get(self, request):
        book_queryset = Book.objects.values("id", "title", "pub_time", "publisher")
        book_list = list(book_queryset)  # 把queryset类型转换成列表
        # 如果我们需要取外键关联的字段信息 需要循环获取外键 再去数据库查然后拼接成我们想要的
        ret = []
        for book in book_list:
            publisher_obj = Publisher.objects.filter(id=book["publisher"]).first()
            # 修改原字典中的publisher对应的值
            book["publisher"] = {
                "id": publisher_obj.id,
                "title": publisher_obj.title
            }
            # 把新的字典追加到一个空列表中
            ret.append(book)
        ret = json.dumps(ret, ensure_ascii=False, cls=MyJson)
        return HttpResponse(ret)


# json不能序列化时间字段,重写JSONEncoder里面的default方法解决
class MyJson(json.JSONEncoder):
    def default(self, field):
        if isinstance(field, datetime.datetime):
            return field.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(field, datetime.date):
            return field.strftime('%Y-%m-%d')
        else:
            return json.JSONEncoder.default(self, field)
View Code
复制代码

 

5、方法二

复制代码
使用JsonResponse,自动帮我们重写了JSONEncoder里面的default方法,解决时间字段的问题
class BooksView(views.View):
    def get(self, request):
        book_queryset = Book.objects.values("id", "title", "pub_time", "publisher")
        book_list = list(book_queryset)  # 把queryset类型转换成列表
        # 如果我们需要取外键关联的字段信息 需要循环获取外键 再去数据库查然后拼接成我们想要的
        ret = []
        for book in book_list:
            publisher_obj = Publisher.objects.filter(id=book["publisher"]).first()
            # 修改原字典中的publisher对应的值
            book["publisher"] = {
                "id": publisher_obj.id,
                "title": publisher_obj.title
            }
            # 把新的字典追加到一个空列表中
            ret.append(book)
        return JsonResponse(ret, safe=False, json_dumps_params={'ensure_ascii': False})
View Code
复制代码

 

6、方法三

复制代码
使用Django自带的序列化模块
from django.core import serializers


# 能够得到我们要的效果 结构有点复杂
class BooksView(views.View):
    def get(self, request):
        book_queryset = Book.objects.all()
        ret = serializers.serialize("json", book_queryset, ensure_ascii=False)
        return HttpResponse(ret)
View Code
复制代码

 

二、DRF序列化的介绍

1、介绍

复制代码
下载DRF模块:pip install djangorestframework
导入:
    from rest_framework.views import APIView
    from rest_framework.response import Response

使用DRF默认的页面,需要在settings的APP注册rest_framework
复制代码
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
]
View Code
复制代码
首先,我们要用DRF的序列化,就要遵循DRF框架的一些标准,
  -- Django我们CBV继承类是View,现在DRF我们要用APIView
  -- Django中返回的时候我们用HTTPResponse,JsonResponse,render ,DRF我们用Response
复制代码

 

2、APIView跟View区别

复制代码
-- APIView继承了View
-- APIView的as_view方法实现了csrf中间件的豁免(用DRF不需要再把settings的csrf中间件注释掉了)
-- 重新封装了request
    request._request可以拿到旧的request
    request.query_params  旧的request.GET即_request.GET
    request.data    除了GET请求外的所有的数据,_request.POST、_request.FILES等
-- 序列化器对象.data
  存放序列化好的数据
  校验通过的数据存放到validated_data里,最后也会序列化封装到序列化对象.data里面返回给前端
  校验不通过,错误信息存到序列化对象.errors里面
-- DRF有自己的序列化模块
  from rest_framework import serializers
-- Response   继承了HttpResponse   携带HTTP标准状态码   做模板的渲染
复制代码

 

3、使用方法

复制代码
-- 序列化(传数据到前端)
    -- 声明一个序列化器
        class BookSerializer(serializers.Serializer):
            id = serializers.IntegerField(required=False)
            title = serializers.CharField(max_length=32)
            pub_time = serializers.DateField()
    -- 视图里序列化我们的queryset或者某个对象
        # queryset需要声明many=True
        ser_obj = BookSerializer(queryset, many=True)
        # 具体的某个的对象则不需要声明
        ser_obj = BookSerializer(book_obj)
        return Response(ser_obj.data)
    -- 实现流程
        -- 如果指定了many=True
        -- 把queryset当成可迭代对象去循环 得到每个模型对象
        -- 把每个模型对象放入序列号器进行序列化
        -- 进行字段匹配 匹配上的字段进行序列化 匹配不上丢弃
        -- 必须满足序列化的所有字段要求
        
-- 反序列化(从前端获取数据)
    -- 获取前端传过来的数据
    -- 用序列化器进行校验
        # 新增一条数据
        ser_obj = BookSerializer(data=request.data)
        # 编辑某条数据
        # instance编辑哪个对象,data前端传过来要跟新的某些字段数据,partial表示允许部分跟新
        ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True)
        if ser_obj.is_valid():
            ser_obj.save()# 调用create/update方法
            return Response(ser_obj.data)
        else:
            return Response(ser_obj.errors)
    -- 写create方法
        在create方法里用ORM操作创建新对象
    -- 写update方法
        在update方法里用ORM操作创编辑对象
复制代码

 

4、注意事项

复制代码
1. 外键和多对多字段的序列化需要额外再设置序列化器
2. 序列化
    序列化器的参数是queryset和many
3. 反序列化
    序列化器的参数是data=提交上来的数据
4. 序列化器字段类型不统一的情况
    反序列化要用的一些字段通过一些参数跟序列化区分开
    -- required=False  # 可以不传的字段
    -- read_only=True  # 只用于序列化的字段
    -- write_only=True  # 只用于反序列化的字段

5. 反序列化的验证
    is_valid()       --> 校验数据
    post请求中的save()  --> 调用序列化器的create方法
  put请求中的save() --> 调用序列化器的update方法
复制代码

 

三、DRF序列化示例

1、声明序列化器

复制代码
# serializers.py文件

from rest_framework import serializers
from libsys.models import Book


CHOICES = ((1, "python"), (2, "linux"), (3, "go"))


# 继承serializers.Serializer
class PublisherSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField(max_length=64)


class AuthorSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=32)


# 字段的声明和models相类似
class BookSerializer(serializers.Serializer):
    # POST校验的时候required=False声明不需要校验这个字段
    id = serializers.IntegerField(required=False)

    title = serializers.CharField(max_length=64)
    pub_time = serializers.DateField()

    # 选择字段,显示的是数字
    # category = serializers.ChoiceField(choices=CHOICES)
    # 把选择字段显示成字符类型,source参数后面跟的是ORM操作
    # read_only=True表示这个字段只在渲染前端的时候使用
    category = serializers.CharField(source='get_category_display', read_only=True)
    # write_only=True表示这个字段只在POST提交数据,做校验的时候使用
    post_category = serializers.IntegerField(write_only=True)

    # 外键需要设置额外的序列化器对它进行序列化
    publisher = PublisherSerializer(read_only=True)
    publisher_id = serializers.IntegerField(write_only=True)

    # 多对多字段需要设置额外的序列化器对它进行序列化,且声明many=True
    authors = AuthorSerializer(many=True, read_only=True)
    author_list = serializers.ListField(write_only=True)

    def create(self, validated_data):  # validated_data是通过校验的数据,最后也会封装到data里面
        #通过ORM操作给Book表增加数据
        book_obj = Book.objects.create(title=validated_data["title"], pub_time=validated_data["pub_time"],
                            category=validated_data["post_category"], publisher_id=validated_data["publisher_id"])
        book_obj.authors.add(*validated_data["author_list"])
        return book_obj

    def update(self, instance, validated_data):
        # 通过ORM操作给Book表编辑数据
        # instance就是book_obj
        instance.title = validated_data.get("title", instance.title)
        instance.pub_time = validated_data.get("pub_time", instance.pub_time)
        instance.category = validated_data.get("post_category", instance.category)
        instance.publisher_id = validated_data.get("publisher_id", instance.publisher_id)
        if validated_data.get("author_list", False):
            instance.authors.set(validated_data["author_list"])
        instance.save()
        return instance
复制代码

 

2、在视图函数中调用

复制代码
from libsys.models import Book, Publisher, Author
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import BookSerializer

# Create your views here.


# 书籍列表
class BookView(APIView):
    def get(self, request):
        """前端获取到的数据格式如下
        [
            {
                "id": 4,
                "title": "go入门",
                "pub_time": "2017-07-18",
                "category": "go",
                "publisher": {
                    "id": 1,
                    "title": "南山出版社"
                },
                "authors": [
                    {
                        "id": 1,
                        "name": "小明"
                    },
                    {
                        "id": 2,
                        "name": "小红"
                    }
                ]
            },
             {
                  ...
             },
             {
                  ...
             }
        ]
        """
        book_queryset = Book.objects.all()
        # 声明一个序列化器
        # 用序列化器去序列化queryset(queryset有多个对象的时候,需要声明many=True)
        # 把数据提交到序列化器,跟序列化器的字段进行匹配,匹配成功就进行序列化
        ser_obj = BookSerializer(book_queryset, many=True)
        return Response(ser_obj.data)

    # 新增书籍
    def post(self, request):
        """前端传过来的数据应该这样的
        {
            "title": "python工程师",
            "pub_time": "2015-08-09",
            "post_category": 1,
            "publisher_id": 2,
            "author_list": [1, 2]
        }
        """
        
        # 获取前端传过来的数据
        book_obj = request.data
        # 用序列化器做校验
        ser_obj = BookSerializer(data=book_obj)
        if ser_obj.is_valid():
            # 校验通过,新增书籍
            ser_obj.save()  # 这里的save方法会去调用序列化器的create方法
            print(ser_obj.validated_data)  # validated_data是通过校验的数据,也会封装到data里面
            return Response(ser_obj.data)
        # 校验不通过返回错误信息
        return Response(ser_obj.errors)

# 编辑书籍
class BookEditView(APIView):
    def get(self, request, id):
        book_obj = Book.objects.filter(pk=id).first()
        ser_obj = BookSerializer(book_obj)
        return Response(ser_obj.data)

    def put(self, request, id):
        book_obj = Book.objects.filter(id=id).first()
        ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True)
        if ser_obj.is_valid():
            ser_obj.save()  # 这里的save方法会去调用序列化器的update方法
            return Response(ser_obj.data)
        return Response(ser_obj.errors)
复制代码

 

3、反向查询的序列化

直接看例子

1.urls

from django.conf.urls import url
from my_app import views

urlpatterns = [
    url(r'^books_reverse/(\d)', views.RevBookView.as_view()),
]

 

2.models

复制代码
from django.db import models

CHOICES = ((1, "python"), (2, "linux"), (3, "go"))


class Publisher(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=20)

    def __str__(self):
        return self.name


class Book(models.Model):
    title = models.CharField(max_length=64)
    publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE,
                                  null=True)
    category = models.IntegerField(choices=CHOICES, default=1)

    def __str__(self):
        return self.title
复制代码

 

3.my_serializer

复制代码
from rest_framework import serializers


################################ 外键正向查询 ##################################
class PublisherSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=20)


class BookSerializer(serializers.Serializer):
    """外键正向查询"""
    id = serializers.IntegerField(required=False)
    title = serializers.CharField(max_length=64)

    category = serializers.CharField(source='get_category_display',
                                     read_only=True)
    # 正向外键查询:从Book查询Publisher
    publisher = PublisherSerializer(read_only=True)


################################ 外键反向查询 ##################################
class ReverseBookSerializer(serializers.Serializer):
    id = serializers.IntegerField(required=False)
    title = serializers.CharField(max_length=64)

    category = serializers.CharField(source='get_category_display',
                                     read_only=True)


class ReversePublisherSerializer(serializers.Serializer):
    """外键反向查询"""
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=20)
    # 反向外键查询:从Publisher查询Book,且关联多个数据的时候,要设置many=True
    # 跟ORM的反向查询一致,小写的表名_set,或者使用related_name
    book_set = ReverseBookSerializer(read_only=True,many=True)
复制代码

 

4.views

复制代码
from rest_framework.views import APIView
from rest_framework.response import Response
from my_app.models import *
from my_app.my_serializer import *


class RevBookView(APIView):
    def get(self, request, type):
        if int(type) == 1:
            # 正向外键查询:从Book查询Publisher
            book_queryset = Book.objects.all()
            ser_obj = BookSerializer(book_queryset, many=True)
        else:
            # 反向外键查询:从Publisher查询Book
            publish_queryset = Publisher.objects.all()
            ser_obj = ReversePublisherSerializer(publish_queryset, many=True)
        return Response(ser_obj.data)
复制代码

 

5.结果

正向查询

 

反向查询

 

四、验证

1、 单个字段的验证(局部钩子)

复制代码
class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(required=False)
    title = serializers.CharField(max_length=64)
    其他字段...

    # 局部钩子方法:validate_字段名,value 是提交过来的这个字段的数据
    def validate_title(self, value):
        # 定义校验规则:标题必须含有python、linux、go
        course_list = ['python', 'linux', 'go']
        for course in course_list:
            if course in value.lower():
                return value
        else:
            raise serializers.ValidationError('输入的书籍名不合法')
复制代码

 

2、 多个字段的验证(全局钩子)

复制代码
class BookSerializer(serializers.Serializer):
    post_category = serializers.IntegerField(write_only=True)
    publisher_id = serializers.IntegerField(write_only=True)
    其他字段...

    # 全局钩子方法:validate,attrs 是前端传过来的所有的数据组成的字典
    def validate(self, attrs):
        # 定义校验规则:书籍分类和作者id不能超过3
        if attrs['post_category'] > 3 or attrs['publisher_id'] > 3:
            raise serializers.ValidationError('输入的图书分类或作者不存在')
        return attrs
复制代码

 

3、 自定义校验规则

复制代码
def my_validate(value):
    if '周星星' in value:
        raise serializers.ValidationError('输入的书籍太帅,不合法')
    return value


class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(required=False)
    # validators声明校验的规则
    title = serializers.CharField(max_length=64, validators=[my_validate,])
复制代码

 

4、校验的权重

自定义校验 > 局部钩子 > 全局钩子

 

五、ModelSerializer

1、介绍

跟Django的Form组件类似,我们使用DRF进行前后端数据交互,有很多需要序列化和反序列化的字段都跟models模型相关,
那么,DRF也给我们提供了跟模型紧密相关的序列化器:ModelSerializer
-- 继承serializers.ModelSerializer
-- 它和Form有点类似
-- 它会根据模型自动生成一组字段
-- 它默认实现了.update()以及.create()方法

 

2、定义ModelSerializer序列化器

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = "__all__"
        # fields = ["id", "title", "pub_time"]
        # exclude = ["user"]
        # 分别是所有字段 包含某些字段 排除某些字段

 

3、外键关系的字段

复制代码
当序列化类META中定义了depth时,这个序列化类中引用字段(外键)则自动变为只读

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = "__all__"
        # fields = ["id", "title", "pub_time"]
        # exclude = ["user"]
        # 分别是所有字段 包含某些字段 排除某些字段

        # depth 代表找嵌套关系的第几层
        depth = 1
复制代码

 

4、META中的其他参数

复制代码
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = "__all__"
        # fields = ["id", "title", "pub_time"]
        # exclude = ["user"]
        # 分别是所有字段 包含某些字段 排除某些字段

        # depth 代表找嵌套关系的第几层
        depth = 1

        # 只读字段
         read_only_fields = ["id"]

         # 给某些字段设置额外参数
        extra_kwargs = {"title": {"validators": [my_validate,]}}
复制代码

 

5、SerializerMethodField方法字段

复制代码
外键关联的对象有很多字段我们是用不到的,如果都传给前端会有数据冗余,
就需要我们自己去定制序列化外键对象的哪些字段。

使用了方法字段,它会自动去找对应的钩子函数(get_字段名),这个方法字段展示的值就是钩子函数的返回值
钩子函数的参数obj:就是序列化的每个模型对象 book_obj

class BookSerializer(serializers.ModelSerializer):
    # 方法字段
    # SerializerMethodField 会去找钩子方法 钩子方法的返回值给这个字段
    # 钩子函数:get_字段名称
    category_text = serializers.SerializerMethodField(read_only=True)
    publisher_info = serializers.SerializerMethodField(read_only=True)
    author_info = serializers.SerializerMethodField(read_only=True)

    def get_category_text(self, obj):
        # obj就是序列化的每个模型对象 book_obj
        return obj.get_category_display()

    def get_publisher_info(self, obj):
        return {"id": obj.publisher_id, "title": obj.publisher.title}

    def get_author_info(self, obj):
        return [{"id": author.id, "name": author.name} for author in obj.authors.all()]

    class Meta:
        model = Book
        fields = "__all__"
复制代码

 

6、完整的ModelSerializer思路

复制代码
因为depth会让我们外键变成只读,所以一般来说我们不用它,
因为如果是前端发数据过来(post,put等),我们对外键、多对多等字段进行验证是验证其在数据库的中真实值,
因此,把ModelSerializer序列化器fields代表的字段应该用于反序列化,而对序列化到前端的外键、多对多等字段的数据,
我们可以使用SerializerMethodField对其进行处理后展示到前端。


class BookSerializer(serializers.ModelSerializer):
    # 方法字段
    category_text = serializers.SerializerMethodField(read_only=True)
    publisher_info = serializers.SerializerMethodField(read_only=True)
    author_info = serializers.SerializerMethodField(read_only=True)
    # 方法字段的值,取决于它对应的方法字段的钩子函数的返回值
    def get_category_text(self, obj):
        # obj就是序列化的每个模型对象 book_obj
        return obj.get_category_display()

    def get_publisher_info(self, obj):
        return {"id": obj.publisher_id, "title": obj.publisher.title}

    def get_author_info(self, obj):
        return [{"id": author.id, "name": author.name} for author in obj.authors.all()]

    class Meta:
        model = Book
        fields = "__all__"
        # fields = ["id", "title", "pub_time"]
        # exclude = ["user"]
        # 分别是所有字段 包含某些字段 排除某些字段

        # depth 代表找嵌套关系的第几层
        # depth = 1

        extra_kwargs = {
            "category": {'write_only': True},
            "publisher": {'write_only': True},
            "authors": {'write_only': True},
        }
复制代码

 

7、ModelSerializer的数据格式

复制代码
class BookView(APIView):
    def get(self, request):
        """序列化的数据如下
        [
            {
                "id": 2,
                "category_text": "python",
                "publisher_info": {
                    "id": 1,
                    "title": "南山出版社"
                },
                "author_info": [
                    {
                        "id": 1,
                        "name": "小明"
                    }
                ],
                "title": "python开发",
                "pub_time": "2019-06-03"
            },
            {...},
            {...},
        ]
        """
        book_queryset = Book.objects.all()
        # 声明一个序列化器
        # 用序列化器去序列化queryset(queryset有多个对象的时候,需要声明many=True)
        # 把数据提交到序列化器,跟序列化器的字段进行匹配,匹配成功就进行序列化
        ser_obj = BookSerializer(book_queryset, many=True)
        return Response(ser_obj.data)

    # 新增书籍
    def post(self, request):
        """前端传过来的数据应该这样的
        {
            "title": "python大师",
            "pub_time": "2013-03-09",
            "category": 1,
            "publisher": 2,
            "authors": [1]
        }
        """

        # 获取前端传过来的数据
        book_obj = request.data
        # 用序列化器做校验
        ser_obj = BookSerializer(data=book_obj)
        if ser_obj.is_valid():
            # 校验通过,新增书籍
            ser_obj.save()  # 这里的save方法会去调用序列化器的create方法
            print(ser_obj.validated_data)  # validated_data是通过校验的数据,也会封装到data里面
            return Response(ser_obj.data)
        # 校验不通过返回错误信息
        return Response(ser_obj.errors)
复制代码

 

posted @   我用python写Bug  阅读(454)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示