Title

DRF框架基础五之序列化类外键字段的覆盖,十大接口和视图家族

一.序列化类外键字段的覆盖

1.先补充普通的字段覆盖

# 比如在models.py中定义了一个类名和字段名
class Book(BaseModel):
   name = models.CharField(max_length=64)
   
# 那么在serializers.py文件中序列化的时候可以对该字段进行覆盖,比如
from rest_framwork import serializers

class BookModelSerializer(serializers.ModelSerializer):
   # 在这个位置可以覆盖原字段规则,比如设置成只读
   name = serializers.CharField(read_only = True)
   name =
   class Meta:
       model = models.Book
       fields = ('name')
       extra_kwargs = {}

2.那怎么对外键字段进行覆盖呢

from rest_framework import serializers
from . import models

# 主序列化类
class BookModelSerializer(serializers.ModelSerializer):
    # 外键字段覆盖为只读
   publish = serializers.PrimaryKeyRelatedField(read_only=True)
   # queryset里面存的是该外键字段关联的表的所有数据,外键字段覆盖为只写
   publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects, write_only=True)
   # 外键字段覆盖为可读可写
   publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all())
   # 比如这个书可能对应多个作者,就要设置many = True
   authors = serializers.PrimaryKeyRelatedField(queryset=models.Author.objects.all(), many=True)
   
   class Meta:
       model = models.Book
       fields = ('name', 'price', 'image', 'publish', 'authors',) # 返回给前台展示的字段
       extra_kwargs = {
           'image': {
               'read_only': True,
          },
           'publish': {  
           'write_only': True, # 如果设置write_only,则定义为只写,即反序列话,即使返回给前台字段也不显示
          },
           'authors': {
               'write_only': True,
          },
      }

总结:

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

2)外键覆盖字段用PrimaryKeyRelatedField来实现,可以做到只读、只写、可读可写三种形式
只读:read_only=True
只写:queryset=关联表的queryset, write_only=True
可读可写:queryset=关联表的queryset

3)当外界关联的数据是多个时,需标识many=True条件
"""

 

十大接口序列化总结

"""
def __init__(self, instance=None, data=empty, **kwargs):
  pass

instance:是要被赋值对象的 - 对象类型数据赋值给instance
data:是要被赋值数据的 - 请求来的数据赋值给data
kwargs:内部有三个属性:many、partial、context
  many:操作的对象或数据,是单个的还是多个的
  partial:在修改需求时使用,可以将所有校验字段required校验规则设置为False
  context:用于视图类和序列化类直接传参使用
"""

# 常见使用
# 单查接口
UserModelSerializer(instance=user_obj)

# 群查接口
UserModelSerializer(instance=user_query, many=True)

# 单增接口,request.data是字典
UserModelSerializer(data=request.data)

# 群增接口,request.data是列表
UserModelSerializer(data=request.data, many=True)

# 单整体改接口,request.data是字典
UserModelSerializer(instance=user_obj, data=request.data)

# 群整体改接口,request.data是列表,且可以分离出pks,转换成user_queryset
UserModelSerializer(instance=user_queryset, data=request.data, many=True)

# 单局部改接口,request.data是字典
UserModelSerializer(instance=user_obj, data=request.data, partial=True)

# 群局部改接口,request.data是列表,且可以分离出pks,转换成user_queryset
UserModelSerializer(instance=user_queryset, data=request.data, partial=True, many=True)

# 删接口,用不到序列化类

 

十大接口核心知识小结

"""
1)初始化序列化类,设置partial=True可以将所有反序列化字段的 required 设置为 False(提供就校验,不提供不校验),可以运用在局部修改接口

2)初始化序列化类,设置context={...},在序列化类操作self.context,实现视图类和序列化类数据互通

3)只有要完成资源的群改这种特殊需求时,才需要自定义ListSerializer绑定给自定义的ModelSerializer,重写update方法,来完成需求
"""

 

案例

models.py
# 基类:是抽象的(不会完成数据库迁移),目的是提供共有字段的
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)
   author = models.OneToOneField(to=Author, related_name='detail', db_constraint=False, on_delete=models.CASCADE)
urls.py
url(r'^books/$', views.BookAPIView.as_view()),
url(r'^books/(?P<pk>\d+)/$', views.BookAPIView.as_view()),
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
views.py
from rest_framework.views import APIView
from . import models, serializers
from .response import APIResponse

# 六个必备接口:单查、群查、单增、单删、单整体改(了解)、单局部改
# 四个额外接口:群增、群删、群整体改、群局部改
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)

   # 单增群增
   """
  单增:接口:/books/   数据:dict
  群增:接口:/books/   数据:list
  """
   def post(self, request, *args, **kwargs):
       # 如何区别单增群增:request.data是{}还是[]
       if not isinstance(request.data, list):
           # 单增
           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)

   # 单删群删
   """
  单删:接口:/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表示受影响的行数
           rows = models.Book.objects.filter(is_delete=False, pk__in=pks).update(is_delete=True) # pk__in = pks表示会在pks中一个一个拿数据(那是双
       except:
           return APIResponse(1, '数据有误')

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

       return APIResponse(2, '删除失败')

   # 单整体改群整体改
   """
  单整体改:接口:/books/(pk)/ 数据:dict
  群整体改:接口:/books/   数据:[{pk1, ...}, ..., {pkn, ...}] | {pks: [pk1, ..., pkn], data: [{}, ..., {}]}
  """
   def put(self, request, *args, **kwargs):
       pk = kwargs.get('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数据 [{'pk':1, 'name': '', 'publish': 1, 'authors': [1, 2]}, ...]
          1)从 request.data 中分离出 pks 列表
          2)pks中存放的pk在数据库中没有对应数据,或者对应的数据已经被删除了,这些不合理的pk要被剔除
          3)pks最终转换得到的 objs 列表长度与 request.data 列表长度不一致,就是数据有误
          """
           pks = []
           try:  # 只要不是要求的标准数据,一定会在下方四行代码某一行抛出异常
               for dic in request.data:
                   pks.append(dic.pop('pk'))
               objs = models.Book.objects.filter(is_delete=False, pk__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)


   # 单局部改群局部改
   """
  单局部改:接口:/books/(pk)/ 数据:dict
  群局部改:接口:/books/   数据:[{pk1, ...}, ..., {pkn, ...}] | {pks: [pk1, ..., pkn], data: [{}, ..., {}]}
  """
   def patch(self, request, *args, **kwargs):
       pk = kwargs.get('pk')
       if pk:  # 单
           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'))
               objs = models.Book.objects.filter(is_delete=False, pk__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)

 

 

 补充:为什么直接群改会报错?

群增群改都会调用serializer.save()方法,但是直接群改会报错save里面的部分源码如下,即增会走creat方法,改会走update方法,

 

 

 这个表示为什么配置类一定要写Meta

但是listserializer类里面只提供了create方法,没有提供update方法

 

 

 

视图家族

"""
视图基类: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方法(整体改)
partial_updata方法(局部改)

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

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

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

注:
i)在这些模式下,不能实现单查群查共存(可以加逻辑区分,也可以用视图集知识)
原因是RetrieveAPIView和listAPIView都是get方法,不管带不带pk的get请求,只能映射给一个get方法
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

 

 

 

posted @ 2020-05-13 16:26  Mr江  阅读(239)  评论(0编辑  收藏  举报