一.
# 一些只参与反序列化的字段,但是不与数据库关联 # 在序列化类中规定,并在校验字段时从校验的参数字典中剔除 (比如re_password) class PublishModelSerializer(serializers.ModelSerializer): # 自定义不入库的 反序列化 字段 re_name = serializers.CharField(write_only=True) class Meta: model = models.Publish fields = ('name', 're_name', 'address') def validate(self, attrs): name = attrs.get('name') re_name = attrs.pop('re_name') # 剔除 if name != re_name: raise serializers.ValidationError({'re_name': '确认名字有误'}) return attrs
# model类中自定义插拔的外键序列化字段,可以采用外键关联表的序列化类来完成深度查询 class Book(BaseModel): # ... @property def publish_detail(self): from . import serializers data = serializers.PublishModelSerializer(self.publish).data return data
三.
from django.conf.urls import url from . import views urlpatterns = [ # ... url(r'^v3/books/$', views.BookV3APIView.as_view()), url(r'^v3/books/(?P<pk>.*)/$', views.BookV3APIView.as_view()), ]
b. 模型层 models.py
# 修改部分(群改):取消book类 name 与 publish 联合唯一 from django.db import models from utils.model import BaseModel class Book(BaseModel): name = models.CharField(max_length=64) price = models.DecimalField(max_digits=5, decimal_places=2) img = models.ImageField(upload_to='img', default='img/default.png') publish = models.ForeignKey(to='Publish', null=True, related_name='books', db_constraint=False, on_delete=models.DO_NOTHING, ) authors = models.ManyToManyField(to='Author', null=True, related_name='books', db_constraint=False, ) @property def publish_name(self): return self.publish.name @property def authors_info(self): author_list = [] for author in self.authors.all(): author_list.append({ 'name': author.name, 'age': author.age, 'mobile': author.detail.mobile }) return author_list @property def publish_bac(self): from . import serializers data = serializers.PublishModelSerializer(self.publish).data return data class Meta: db_table = 'old_boy_book' verbose_name = '书籍' verbose_name_plural = verbose_name # 联合唯一 # unique_together = ('name', 'publish') def __str__(self): return self.name class Publish(BaseModel): name = models.CharField(max_length=64) address = models.CharField(max_length=64) class Meta: db_table = 'old_boy_publish' verbose_name = '出版社' verbose_name_plural = verbose_name def __str__(self): return self.name class Author(BaseModel): name = models.CharField(max_length=64) age = models.IntegerField() @property def mobile(self): return self.detail.mobile class Meta: db_table = 'old_boy_author' verbose_name = '作者' verbose_name_plural = verbose_name def __str__(self): return self.name class AuthorDetail(BaseModel): mobile = models.CharField(max_length=11) author = models.OneToOneField(to='Author', null=True, related_name='detail', db_constraint=False, on_delete=models.CASCADE ) class Meta: db_table = 'old_boy_author_detail' verbose_name = '作者详情' verbose_name_plural = verbose_name def __str__(self): return '%s的详情' % self.author.name
c.
群增与群改反序列化实现
1)ModelSerializer默认不通过群改功能,需要在Meta中设置 list_serializer_class
2)自定义ListSerializer子类,重写update方法,将子类绑定给 list_serializer_class
3)重写update方法中通过 代表要更新的对象们instance 及 提供的更新数据们validated_data
得到 更新后的对象们instance 返回
class BookV3ListSerializer(serializers.ListSerializer): def update(self, instance, validated_data): ''' :param instance: [book_obj1, ..., book_obj2] :param validated_data: [{更新数据的字段}, ..., {更新数据的字段}] :return: [book_new_obj1, ..., book_new_obj2] ''' for index, obj in enumerate(instance): # type: int, models.Book # 单个对象数据更新 - 一个个属性更新值 for attr, value in validated_data[index].items(): # 对象有更新数据字典的key对应的属性,才完成该属性的值更新 if hasattr(obj, attr): setattr(obj, attr, value) # 信息同步到数据库 obj.save() return instance class BookV3ModelSerializer(serializers.ModelSerializer): class Meta: model = models.Book fields = ('name', 'price', 'publish', 'authors', 'img', 'publish_name', 'authors_info') list_serializer_class = BookV3ListSerializer extra_kwargs = { 'publish': { 'required': True, 'write_only': True }, 'authors': { 'required': True, 'write_only': True }, 'img': { 'read_only': True } } def validate_name(self, value): if 'sb' in value: raise serializers.ValidationError('书名有敏感词汇') return value def validate(self, attrs): name = attrs.get('name') publish = attrs.get('publish') if models.Book.objects.filter(name=name, publish=publish): raise serializers.ValidationError({'book': '书籍以存在'}) return attrs
补充:受群增启发自定义群改
补充自定义update方法
源码修改:
d.
(1)单增群增: 把单增群增数据都统一转化成[{},...]的形式,序列化时都要加上 many = True;
(2)单改群改:首先预处理需要更新的data及其pks,将其统一改成列表的形式,然后需要从数据库中取出与之pk对应的未删除的book_obj,剔除
data及book_obj中不存在或者已删除的数据,append到列表中,完成反序列化修改数据,many=True,单改时patial=True,群改时model模型
中联合唯一会导致出错,需要取消
(3)单删群删:将pk统一改为列表的形式(群删传输pks就以列表的形式),视图函数中取到queryset对象,通过update方法将is__delete=False
改为True
class BookV3APIView(APIView): # 单查群查 def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: book_obj = models.Book.objects.filter(is_delete=False, pk=pk).first() if not book_obj: return APIResponse(1, 'pk error') book_ser = serializers.BookV3ModelSerializer(book_obj) return APIResponse(0, 'ok', results=book_ser.data) book_query = models.Book.objects.filter(is_delete=False).all().order_by('-id') book_ser = serializers.BookV3ModelSerializer(book_query, many=True) return APIResponse(0, 'ok', results=book_ser.data) # 单增群增: 把单增群增数据都统一转化成[{},...]的形式,序列化时都要加上 many = True def post(self, request, *args, **kwargs): # 单增 /books/1/ data {} # 群增 /books/ data [{}, ..., {}] request_data = request.data if isinstance(request_data, dict): data = [request_data, ] elif isinstance(request_data, list): data = request_data else: return APIResponse(1, '数据格式有误') book_ser = serializers.BookV3ModelSerializer(data=data, many=True) if book_ser.is_valid(): book_obj_list = book_ser.save() return APIResponse(0, 'ok', results=serializers.BookV3ModelSerializer(book_obj_list, many=True).data ) else: return APIResponse(1, '添加失败', results=book_ser.errors) """ 1)先确定要更新的对象 主键们 与 数据们 2)通过校验数据库剔除 已删除的对象 与 不存在的对象 主键们 => 剔除过程 => 可以修改的对象们 数据们 => 剔除过程 => 可以修改的对象们对应的数据们 3)反序列化及校验过程 通过 => save() 完成更新 失败 => ser_obj.errors 返回 """ # 单改群改 def patch(self, request, *args, **kwargs): # 单改 /books/(pk)/ data {"name": "new_name", ...} # 群改 /books/ data [{"pk": 1, "name": "new_name", ...}, ...,{"pk": n, "name": "new_name", ...}] # 结果: # pks = [1, ..., n] => book_query => instance # data = [{"name": "new_name", ...}, ..., {"name": "new_name", ...}] => data # 数据预处理 pk = kwargs.get('pk') request_data = request.data if pk: if not isinstance(request_data, dict): return APIResponse(1, '单增数据有误') pks = [pk, ] data = [request_data, ] elif isinstance(request_data, list): try: pks = [] for dic in request_data: # pks.append(dic.pop('pk')) pks.append(dic.get('pk')) data = request_data except: return APIResponse(1, '群增数据有误') else: return APIResponse(1, '数据格式有误') # 将 已删除的书籍 与 不存在的书籍 剔除 (提供修改的数据相对应也剔除) book_query = [] filter_data = [] for index, pk in enumerate(pks): book_obj = models.Book.objects.filter(is_delete=False, pk=pk).first() if book_obj: book_query.append(book_obj) filter_data.append(data[index]) # 反序列化完成数据的修改 book_ser = serializers.BookV3ModelSerializer(instance=book_query, data=filter_data, many=True, partial=True) if book_ser.is_valid(): book_obj_list = book_ser.save() return APIResponse(1, 'ok', results=serializers.BookV3ModelSerializer(book_obj_list, many=True).data ) else: return APIResponse(1, '更新失败', results=book_ser.errors) # 单删群删 def delete(self, request, *args, **kwargs): # 单删 /books/(pk)/ # 群删 /books/ 数据包携带 pks => request.data pk = kwargs.get('pk') if pk: pks = [pk] else: pks = request.data.get('pks') if not pks: return APIResponse(1, '删除失败') book_query = models.Book.objects.filter(is_delete=False, pk__in=pks) if not book_query.update(is_delete=True): # 操作的记录大于0就记录删除成功 return APIResponse(1, '删除失败') return APIResponse(0, '删除成功')
views:基本视图
APIView
generics:工具视图
GenericAPIView 该家族的基类
将 queryset 和 serializer_class 封装成类属性,提供了三个方法
class BookGenericAPIView(generics.GenericAPIView): queryset = models.Book.objects.filter(is_delete=False).order_by('-id') serializer_class = serializers.BookModelSerializer
get_queryset() | get_object() | get_serializer(*args, **kwargs)
GenericAPIView众多子类
继承 (mixins中若干功能类, GenericAPIView基类)
提供 get | post | put | patch | delete 五大接口
eg:RetrieveUpdateAPIView就可以完成 单取(get),单局部(patch)及整体(put)修改
mixins:视图工具集
五大工具类:
RetrieveModelMixin:retrieve 单取
ListModelMixin:list 群取
CreateModelMixin:create 单增
UpdateModelMixin:update 单整体改 partial_update 单局部改
DestroyModelMixin:destroy 单删
viewsets:视图集
ViewSetMixin:视图集工具 - 重写as_view - 将 请求方式 映射到视图类中的 指定方法
.as_view({'get': 'retrieve', 'delete': 'remove_obj'})
GenericViewSet:
与模型类有关的接口视图集 - 可以从mixins那继承功能,也可以自定义功能
ViewSet:
与模型类无关或不是标准模型类接口 - 一般都是自定义功能
开发中常用类:
views:APIView
generics:ListAPIView, ...
viewsets:GenericViewSet
a.路由
from django.conf.urls import url from . import views urlpatterns = [ # views url(r'^books/$', views.BookAPIView.as_view()), url(r'^books/(?P<pk>.*)/$', views.BookAPIView.as_view()), # generics url(r'^v1/books/$', views.BookGenericAPIView.as_view()), url(r'^v1/books/(?P<pk>.*)/$', views.BookGenericAPIView.as_view()), # mixins + generics url(r'^v2/books/$', views.BookMixinsGenericAPIView.as_view()), url(r'^v2/books/(?P<pk>.*)/$', views.BookMixinsGenericAPIView.as_view()), # 系统整合mixins、generics url(r'^v3/books/$', views.BookRetrieveUpdateAPIView.as_view()), url(r'^v3/books/(?P<pk>.*)/$', views.BookRetrieveUpdateAPIView.as_view()), # viewsets url(r'^v4/books/$', views.BookGenericViewSet.as_view({ 'get': 'list', 'post': 'create' })), url(r'^v4/books/(?P<pk>.*)/$', views.BookGenericViewSet.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'remove_book' })), ]
from rest_framework import serializers from . import models class BookModelSerializer(serializers.ModelSerializer): class Meta: model = models.Book fields = ('name', 'price', 'publish', 'authors') extra_kwargs = { 'publish': { # 数据库有默认值,必须反序列化的字段,要设置required=True 'required': True, 'write_only': True }, 'authors': { 'required': True, 'write_only': True } }
from rest_framework import views, generics, mixins, viewsets from . import models, serializers from utils.response import APIResponse # 基础 class BookAPIView(views.APIView): def get(self, request, *args, **kwargs): book_query = models.Book.objects.filter(is_delete=False).order_by('-id').all() book_ser = serializers.BookModelSerializer(book_query, many=True) return APIResponse(0, 'ok', results=book_ser.data) def post(self, request, *args, **kwargs): book_ser = serializers.BookModelSerializer(data=request.data) book_ser.is_valid(raise_exception=True) book_obj = book_ser.save() return APIResponse(0, 'ok', results=serializers.BookModelSerializer(book_obj).data) # v1 generics - 视图基类 class BookGenericAPIView(generics.GenericAPIView): queryset = models.Book.objects.filter(is_delete=False).order_by('-id') serializer_class = serializers.BookModelSerializer def get(self, request, *args, **kwargs): if 'pk' in kwargs: book_obj = self.get_object() book_ser = self.get_serializer(book_obj) return APIResponse(0, 'ok', results=book_ser.data) book_query = self.get_queryset() book_ser = self.get_serializer(book_query, many=True) return APIResponse(0, 'ok', results=book_ser.data) def post(self, request, *args, **kwargs): book_ser = self.get_serializer(data=request.data) book_ser.is_valid(raise_exception=True) book_obj = book_ser.save() return APIResponse(0, 'ok', results=self.get_serializer(book_obj).data) # v2 mixins - 工具集 class BookMixinsGenericAPIView(mixins.RetrieveModelMixin, mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): queryset = models.Book.objects.filter(is_delete=False).order_by('-id') serializer_class = serializers.BookModelSerializer def get(self, request, *args, **kwargs): if 'pk' in kwargs: #注:主键pk在Mixin类中可以直接获取 response = self.retrieve(request, *args, **kwargs) else: response = self.list(request, *args, **kwargs) return APIResponse(0, 'ok', results=response.data) def post(self, request, *args, **kwargs): response = self.create(request, *args, **kwargs) return APIResponse(0, 'ok', results=response.data) # v3 视图基类子类 - 工具视图类 class BookRetrieveUpdateAPIView(generics.RetrieveUpdateAPIView): queryset = models.Book.objects.filter(is_delete=False).order_by('-id') serializer_class = serializers.BookModelSerializer # v4 视图集 - 快速实现模型类五大接口 - 自定义删除方法 class BookGenericViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet): queryset = models.Book.objects.filter(is_delete=False).order_by('-id') serializer_class = serializers.BookModelSerializer def remove_book(self, request, *args, **kwargs): pk = kwargs.get('pk') try: book_obj = models.Book.objects.get(is_delete=False, pk=pk) book_obj.is_delete = True book_obj.save() return APIResponse(0, '删除成功') except: return APIResponse(1, '删除失败')
深层authdetail数据未知显示bug补充: