序列化组件三,视图函数家族

鲁氏汤包王 2020-02-21

一、多表查询序列化类外键字段的覆盖

"""
1)在序列化类中自定义字段,名字与model类中属性名一致,就称之为覆盖操作
    (覆盖的是属性的所有规则:extra_kwargs中指定的简易规则、model字段提供的默认规则、数据库唯一约束等哪些规则)

2)外键覆盖字段用PrimaryKeyRelatedField来实现,可以做到只读、只写、可读可写三种形式
    只读:read_only=True
    只写:queryset=关联表的queryset, write_only=True
    可读可写:queryset=关联表的queryset
    
3)当外界关联的数据是多个时,需标识many=True条件
"""
from rest_framework import serializers
from rest_framework import exceptions
from . import models

class BookModelSerializer(serializers.ModelSerializer):

    # 如何覆盖外键字段
    # publish = serializers.PrimaryKeyRelatedField(read_only=True)  # 只读覆盖
    # publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all())  # 课读可写 queryset后面跟的就是绑定的类
    # publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all(), write_only=True)  # 只写
    
    publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all())
    authors = serializers.PrimaryKeyRelatedField(queryset=models.Author.objects.all(), many=True)

    class Meta:
        model = models.Book
        # fields = [‘name‘, ‘price‘, ‘image‘, ‘publish‘, ‘authors‘, ‘publish_name‘, ‘author_list‘]
        fields = [‘name‘, ‘price‘, ‘image‘, ‘publish‘, ‘authors‘]
        extra_kwargs = {
            ‘image‘: {
                ‘read_only‘: True,
            },
            ‘publish‘: {  # 系统原有的外键字段要留给反序列化过程使用,序列化外键内容用@property自定义
                ‘write_only‘: True,
            },
            ‘authors‘: {
                ‘write_only‘: True,
            },
        }

二、序列化类十大接口

1、单查群查

models.py

from django.db import models

# Create your models here.
class BaseModel(models.Model):
    is_delete = models.BooleanField(default=False)
    updated_time = models.DateTimeField(auto_now_add=True)

    class Meta:
        abstract = True  # 注意:必须完成该配置才可以让别的类去继承基类


class Book(BaseModel):
    name = models.CharField(max_length=64)
    price = models.DecimalField(max_digits=5, decimal_places=2, null=True)
    image = models.ImageField(upload_to=‘img‘, default=‘img/default.png‘)

    publish = models.ForeignKey(to=‘Publish‘, related_name=‘books‘, db_constraint=False, on_delete=models.DO_NOTHING)
    authors = models.ManyToManyField(to=‘Author‘, related_name=‘books‘, db_constraint=False)

    @property  # @property字段默认就是read_only,且不允许修改
    def publish_name(self):
        return self.publish.name

    @property  # 自己手动格式化
    def author_list(self):
        temp_author_list = []
        for author in self.authors.all():
            author_dic = {
                ‘name‘: author.name
            }
            try:
                author_dic[‘phone‘] = author.detail.phone
            except:
                author_dic[‘phone‘] = ‘‘
            temp_author_list.append(author_dic)

        return temp_author_list


class Publish(BaseModel):
    name = models.CharField(max_length=64)


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


class AuthorDetail(BaseModel):
    phone = models.CharField(max_length=11)
    # related_name就是可以方向访问,比如主键在作者详情,作者访问作者详情就可以直接.detail就相当于来到了详情表
    # db_constraint是控制两张表之间是否有关联,默认是True,改为False就是断关联
    author = models.OneToOneField(to=Author,
                                  related_name=‘detail‘,
                                  db_constraint=False,
                                  # on_delete=models.CASCADE
                                  # on_delete=models.DO_NOTHING
                                  # default=1
                                  on_delete=models.SET_NULL,
                                  null=True
                                  )

models.py

主路由urls.py

from django.conf.urls import url, include
from django.contrib import admin
from django.views.static import serve
from django.conf import settings


urlpatterns = [
    url(r‘^admin/‘, admin.site.urls),
    url(r‘^api/‘, include(‘api.urls‘)),

    url(r‘^media/(?P<path>.*)‘, serve,  {‘document_root‘: settings.MEDIA_ROOT})
]

子路由urls.py

from . import views
from django.conf.urls import url
urlpatterns = [
    url(r‘^books/$‘, views.BOOKAPIView.as_view()),
    url(r‘^books/(?P<pk>\d+)/$‘, views.BOOKAPIView.as_view()),
]

二次封装response.py

from rest_framework.response import Response

class APIResponse(Response):

    def __init__(self, status=0, msg=‘ok‘, http_status=None,  headers=None, exception=False, **kwargs):
        # 将外界传入的数据状态码,状态信息以及其他所有额外储存在kwargs中的信息,都格式化成data信息
        data = {
            ‘status‘: status,  # 数据状态码
            ‘msg‘: msg
        }
        if kwargs:
            data.update(kwargs)
        super().__init__(data=data, status=http_status, headers=headers, exception=exception)

serializers.py

from rest_framework import serializers
from rest_framework import exceptions
from . import models
# 主序列化类
class BookModelSerializer(serializers.ModelSerializer):

    # 如何覆盖外键字段
    # publish = serializers.PrimaryKeyRelatedField(read_only=True)  # 只读覆盖
    # publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all())  # 课读可写 queryset后面跟的就是绑定的类
    # publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all(), write_only=True)  # 只写

    publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all())
    authors = serializers.PrimaryKeyRelatedField(queryset=models.Author.objects.all(), many=True)

    class Meta:
        model = models.Book
        # fields = [‘name‘, ‘price‘, ‘image‘, ‘publish‘, ‘authors‘, ‘publish_name‘, ‘author_list‘]
        fields = [‘name‘, ‘price‘, ‘image‘, ‘publish‘, ‘authors‘]
        extra_kwargs = {
            ‘image‘: {
                ‘read_only‘: True,
            },
            ‘publish‘: {  # 系统原有的外键字段要留给反序列化过程使用,序列化外键内容用@property自定义
                ‘write_only‘: True,
            },
            ‘authors‘: {
                ‘write_only‘: True,
            },
        }

views.py

from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from . import models, serializers
from . response import APIResponse
# Create your views here.
# 六个必备接口:单查,群查,单增,单删,单整体改(了解),单局部改
# 四个额外接口:群增,群删,群整体改,群局部改
class BOOKAPIView(APIView):
    # 序列化
    # 单查群查
    """
    单查:接口:/books/(pk)/
    群查:接口:/books/
    """
    def get(self, request, *args, **kwargs):
        pk = kwargs.get(‘pk‘)
        if pk:
            obj = models.Book.objects.filter(is_delete=False, pk=pk).first()
            serializer = serializers.BookModelSerializer(instance=obj)
            return APIResponse(result=serializer.data)

        else:
            queryset = models.Book.objects.filter(is_delete=False).all()
            serializer = serializers.BookModelSerializer(instance=queryset, many=True)
            return APIResponse(results=serializer.data)

2、单增群增

views.py

from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from . import models, serializers
from . response import APIResponse
# Create your views here.
# 六个必备接口:单查,群查,单增,单删,单整体改(了解),单局部改
# 四个额外接口:群增,群删,群整体改,群局部改
class BOOKAPIView(APIView):

    """

    # 反序列化
    # 单增群增
    """
    单增:接口:/books/  数据:dict
    群增:接口:/books/  数据:list
    """
    def post(self, request, *args, **kwargs):
        # 如何区别单增群增:request.data是{}还是[]
        if not isinstance(request.data, list):  # isinstance()是用来判断一个对象是否是某个类型
            # 单增
            serializer = serializers.BookModelSerializer(data=request.data)
            serializer.is_valid(raise_exception=True)  # 如果校验失败会直接抛异常返回给前台
            obj = serializer.save()
            # 为什么要将新增的对象重新序列化给前台,因为序列化与反序列化数据不对等
            return APIResponse(result=serializers.BookModelSerializer(obj).data, http_status=201)
        else:
            # 群增
            serializer = serializers.BookModelSerializer(data=request.data, many=True)
            serializer.is_valid(raise_exception=True)  # 如果校验失败会直接抛异常返回给前台
            objs = serializer.save()
            # 为什么要将新增的对象重新序列化给前台,因为序列化与反序列化数据不对等
            return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data, http_status=201)

3、单删群删

views.py

from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from . import models, serializers
from . response import APIResponse
# Create your views here.
# 六个必备接口:单查,群查,单增,单删,单整体改(了解),单局部改
# 四个额外接口:群增,群删,群整体改,群局部改
class BOOKAPIView(APIView):

    # 单删群删
    """
    单删:接口:/books/(pk)/
    群删:接口:/books/  数据[pk1,...,pkn]
    """
    def delete(self, request, *args, **kwargs):
        pk = kwargs.get(‘pk‘)
        if pk:
            pks = [pk]  # 将单删伪装成群删一条
        else:
            pks = request.data  # 群删的数据就是群删的主键们
        try:  # request.data可能提交的数据是乱七八糟的,所以orm操作可能会异常
            rows = models.Book.objects.filter(is_delete=False, pk__in=pks).update(is_delete=True)
        except:
            return APIResponse(1, ‘数据有误‘)

        if rows:  # 只要有受影响的行,就代表删除成功
            return APIResponse(0, ‘删除成功‘)
        return APIResponse(2, ‘删除失败‘)

4、单整体改群整体改

views.py

from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from . import models, serializers
from . response import APIResponse
# Create your views here.
# 六个必备接口:单查,群查,单增,单删,单整体改(了解),单局部改
# 四个额外接口:群增,群删,群整体改,群局部改
class BOOKAPIView(APIView):

    # 单整体改
    # 群整体改
    """
    单整体改:接口:/books/(pk)/ 数据:dict
    群整体改:接口:/books/  数据[{pk1:xxx},...,{pkn:xxx}]
    """
    def put(self,  request, *args, **kwargs):
        pk = kwargs.pop(‘pk‘)
        if pk:  # 单
            try:
                instance = models.Book.objects.get(is_delete=False, pk=pk)
            except:
                return APIResponse(1, ‘pk error‘, http_status=400)
            # 序列化类同时赋值instance和data,代表用data重新更新instance => 修改
            serializer = serializers.BookModelSerializer(instance=instance, data=request.data)
            serializer.is_valid(raise_exception=True)
            obj = serializer.save()
            return APIResponse(result=serializers.BookModelSerializer(obj).data)
        else:  # 群
            """分析request.data数据
            1)从request.data中分离出pks列表
            2)pks中存放的pk在数据库中没有对应的数据,或者对应的数据已经被删除了,我们要把这些不合理的PK剔除
            3)pks最终转换得到的objs 列表长度 与request.data 列表长度不一致,就是数据有误
            
            """
            pks = []
            try:
                for dic in request.data:  # 不是列表套字典不可以
                    pks.append(dic.get(‘pk‘))  # 是字典没有pk也不可以
                objs = models.Book.objects.filter(is_delete=False, pk__in=pks)  # null in pks也会异常
                assert len(objs) == len(request.data)  # 两个列表长度必须一致
            except:
                return APIResponse(1, ‘数据有误‘, http_status=400)

            serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True)
            serializer.is_valid(raise_exception=True)
            objs = serializer.save()
            # 为什么要将新增的对象重新序列化给前台,因为序列化与反序列化数据不对等
            return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data)

serializers.py

from rest_framework import serializers
from . import models

# 群增群改辅助类(了解)
class BookListSerializer(serializers.ListSerializer):
    """
    1)create群增方法不需要重新
    2)update群改方法需要重写,且需要和views中处理request.data的逻辑配套使用
    3)self.child就代表该ListSerializer类绑定的ModelSerializer类
        BookListSerializer的self.child就是BookModelSerializer
    """
    # 重新update方法
    def update(self, queryset, validated_data_list):
        return [
            self.child.update(queryset[index], validated_data) for index, validated_data in enumerate(validated_data_list)
        ]

# 主序列化类
class BookModelSerializer(serializers.ModelSerializer):
    class Meta:
        # 配置自定义群增群改序列化类
        list_serializer_class = BookListSerializer

        model = models.Book
        fields = (‘name‘, ‘price‘, ‘image‘, ‘publish‘, ‘authors‘, ‘publish_name‘, ‘author_list‘)
        # fields = (‘name‘, ‘price‘, ‘image‘, ‘publish‘, ‘authors‘, ‘abc‘)
        extra_kwargs = {
            ‘image‘: {
                ‘read_only‘: True,
            },
            ‘publish‘: {  # 系统原有的外键字段,要留给反序列化过程使用,序列化外键内容,用@property自定义
                ‘write_only‘: True,
            },
            ‘authors‘: {
                ‘write_only‘: True,
            },
        }

    # 需求:内外传参
    # 1)在钩子函数中,获得请求请求对象request 视图类 => 序列化类
    # 2)序列化钩子校验过程中,也会产生一些数据,这些数据能不能传递给外界使用:序列化类 => 视图类
    # 序列化类的context属性,被视图类与序列化类共享
    def validate(self, attrs):
        print(self.context)  # 可以获得视图类在初始化序列化对象时传入的context
        # self.context.update({‘a‘: 10})  # 序列化类内部更新context,传递给视图类
        return attrs

5、单局部改群局部改

views.py

from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from . import models, serializers
from . response import APIResponse
# Create your views here.
# 六个必备接口:单查,群查,单增,单删,单整体改(了解),单局部改
# 四个额外接口:群增,群删,群整体改,群局部改
class BOOKAPIView(APIView):

    # 单局部改
    # 群局部改
    """
    单局部改:接口:/books/(pk)/ 数据:dict
    群局部改:接口:/books/  数据[{pk1:xxx},...,{pkn:xxx}]
    """
    def patch(self, request, *args, **kwargs):
        pk = kwargs.get(‘pk‘)
        if pk:  # 单局部改与单整体改就多了个partial=True
            try:
                instance = models.Book.objects.get(is_delete=False, pk=pk)
            except:
                return APIResponse(1, ‘pk error‘, http_status=400)
            #  partial=True的作用就是将所有的required改为false
            serializer = serializers.BookModelSerializer(instance=instance, data=request.data, partial=True)
            serializer.is_valid(raise_exception=True)
            obj = serializer.save()
            return APIResponse(result=serializers.BookModelSerializer(obj).data)
        else:  # 群
            pks = []
            try:
                for dic in request.data:  # 不是列表套字典不可以
                    pks.append(dic.get(‘pk‘))  # 是字典没有pk也不可以
                objs = models.Book.objects.filter(is_delete=False, pk__in=pks)  # null in pks也会异常
                assert len(objs) == len(request.data)  # 两个列表长度必须一致
            except:
                return APIResponse(1, ‘数据有误‘, http_status=400)

            serializer = serializers.BookModelSerializer(instance=objs,
                                                         data=request.data,
                                                         many=True, partial=True,
                                                         context={‘request‘: request}  # 初始化时,对Context赋值,将试图类中数据传递给序列化类
                                                         )
            serializer.is_valid(raise_exception=True)
            objs = serializer.save()
            # 为什么要将新增的对象重新序列化给前台,因为序列化与反序列化数据不对等
            print(serializer.context)  # 在完成序列化类校验后,可以重新拿到序列化类内部对context做的值更新
            return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data)

三、视图家族

"""
视图基类:APIView、GenericAPIView
视图工具类:mixins包下的五个类(六个方法)
工具视图类:generics包下的所有GenericAPIView的子类
视图集:viewsets包下的类
"""

""" GenericAPIView基类(基本不会单独使用,了解即可,但是是高级视图类的依赖基础)
1)GenericAPIView继承APIView,所有APIView子类写法在继承GenericAPIView时可以保持一致
2)GenericAPIView给我们提供了三个属性 queryset、serializer_class、lookup_field
3)GenericAPIView给我们提供了三个方法 get_queryset、get_serializer、get_obj
"""


""" mixins包存放了视图工具类(不能单独使用,必须配合GenericAPIView使用)
CreateModelMixin:单增工具类
    create方法
    
ListModelMixin:群查工具类
    list方法

RetrieveModelMixin:单查工具类
    retrieve方法

UpdateModelMixin:单整体局部改工具类
    update方法

DestroyModelMixin:单删工具类
    destory方法
"""

""" generics包下的所有GenericAPIView的子类(就是继承GenericAPIView和不同mixins下的工具类的组合)
1)定义的视图类,继承generics包下已有的特点的GenericAPIView子类,可以在只初始化queryset和serializer_class两个类属性后,就获得特定的功能

2)定义的视图类,自己手动继承GenericAPIView基类,再任意组合mixins包下的一个或多个工具类,可以实现自定义的工具视图类,获得特定的功能或功能们

注:
i)在这些模式下,不能实现单查群查共存(可以加逻辑区分,也可以用视图集知识)
ii)DestroyModelMixin工具类提供的destory方法默认是从数据库中删除数据,所以一般删除数据的需求需要自定义逻辑
"""

urls.py

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

urlpatterns = [
    # ...
    
    url(r‘^v1/books/$‘, views.BookV1APIView.as_view()),
    url(r‘^v1/books/(?P<pk>\d+)/$‘, views.BookV1APIView.as_view()),

    url(r‘^v2/books/$‘, views.BookV2APIView.as_view()),
    url(r‘^v2/books/(?P<pk>\d+)/$‘, views.BookV2APIView.as_view()),

    url(r‘^v3/books/$‘, views.BookV3APIView.as_view()),
    url(r‘^v3/books/(?P<pk>\d+)/$‘, views.BookV3APIView.as_view()),
]

views.py

# ----------------------------- 过渡写法:了解 -----------------------------

from rest_framework.generics import GenericAPIView
class BookV1APIView(GenericAPIView):
    # 将数据和序列化提示为类属性,所有的请求方法都可以复用
    queryset = models.Book.objects.filter(is_delete=False).all()
    serializer_class = serializers.BookModelSerializer
    lookup_field = ‘pk‘  # 可以省略,默认是pk,与url有名分组对应的

    # 群查
    def get(self, request, *args, **kwargs):
        # queryset = models.Book.objects.filter(is_delete=False).all()  # => 方法+属性两行代码
        queryset = self.get_queryset()
        # serializer = serializers.BookModelSerializer(instance=queryset, many=True)  # => 方法+属性两行代码
        serializer = self.get_serializer(instance=queryset, many=True)
        return APIResponse(results=serializer.data)

    # 单查
    # def get(self, request, *args, **kwargs):
    #     obj = self.get_object()
    #     serializer = self.get_serializer(obj)
    #     return APIResponse(results=serializer.data)

    # 单增
    def post(self, request, *args, **kwargs):
        # serializer = serializers.BookModelSerializer(data=request.data)
        serializer = self.get_serializer(data=request.data)  # 同样的步骤多了,好处就来了
        serializer.is_valid(raise_exception=True)
        obj = serializer.save()
        return APIResponse(result=self.get_serializer(obj).data, http_status=201)
    
# ----------------------------- 过渡写法:了解 -----------------------------

from rest_framework.generics import GenericAPIView
from rest_framework import mixins
class BookV2APIView(GenericAPIView, mixins.RetrieveModelMixin, mixins.CreateModelMixin):
    queryset = models.Book.objects.filter(is_delete=False).all()
    serializer_class = serializers.BookModelSerializer

    # 单查
    def get(self, request, *args, **kwargs):
        # obj = self.get_object()
        # serializer = self.get_serializer(obj)
        # return APIResponse(results=serializer.data)

        # return self.retrieve(request, *args, **kwargs)

        response = self.retrieve(request, *args, **kwargs)
        return APIResponse(result=response.data)

    # 单增
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)


# ----------------------------- 开发写法:常用 -----------------------------

from rest_framework.generics import RetrieveAPIView
class BookV3APIView(RetrieveAPIView):
    queryset = models.Book.objects.filter(is_delete=False).all()
    serializer_class = serializers.BookModelSerializer

    # 单查
    pass

序列化组件三,视图函数家族

相关推荐