Day 24 DRF视图集
一、DRF-ModelSerializer多对多数据创建
models.py
class Book(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
author = models.ManyToManyField(to='Author', null=True) # 多对多关系
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE) # 外键关系
class Publish(models.Model):
name = models.CharField(max_length=32)
address = models.CharField(max_length=64)
class Author(models.Model):
name = models.CharField(max_length=32)
salary = models.IntegerField()
phone = models.CharField(max_length=11)
serializer.py
class Author(serializers.ModelSerializer):
class Meta:
# 关联
model = models.Author
# 选取需要显示的字段
fields = '__all__'
class Publish(serializers.ModelSerializer):
class Meta:
model = models.Publish
fields = ['name', 'address']
class BookModel(serializers.ModelSerializer):
author1 = Author(read_only=True)
publish = Publish() # 外键这样能够读出来但是不能存
# 存
# publish1 = Publish(read_only=True)
class Meta:
model = models.Book # 表模型 与之关联
fields = '__all__'
但是换一种是不行的
没有read_only = True
class BookModel(serializers.ModelSerializer):
author = Author()
publish = Publish()
这样是不行的,因为两个字段对应的是表对象,
get的时候,由于原本 author 对应的是关系,现在变成了表对象(覆盖了) publish 也是一样所以也是查不到数据的
post的时候默认都是要存入完整的对象的 也就是一个字典
出现这个问题的原因是由于多对多的关系很特殊,他是有一张 中间表 的 把原来对应的 表关系 换成 表对象 就找不到对应字段的数据,所以就报错,这里我们就要保留 原来的 表关系(author字段)
如果我们要跨表查询 就需要重新 定义一个字段名(author1)
,并将其设置为 read_only
属性 让drf通过 ModelSerializer
寻找对应的字段内容,如果不设置 就会与自己的(author)
发生冲突,也就是 读写 分离, 自己的负责 write_only
,这样就能正常读取和写入了!
二、视图基类(2个)
Django REST framwork 提供视图的主要作用
- 控制序列化器的执行(检验,保存,转换数据)
- 控制数据库的查询
这两个最大的区别是 GenericAPIView继承APIView类的,在拥有APIView所有特性外,还提供了链接Model类与Serializer类的接口
1. APIView
是REST framework提供的所有视图的基类,继承了原生Django的View
从之前的流程中我们知道,他是继承父类的as_view方法,执行自己的dispath,然后返回返回REST framework的Response
对象,当然这其中还发生了很多事情!
在APIView仍然以常规的类视图定义方法来实现get(),post()或着其他请求方法。
2.GenericAPIView
继承自
APIVIew
,主要增加了操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类。
2.1支持定义的属性
- 列表视图与详情视图通用
- queryset 列表视图的查询集
- serializer_class 视图使用的序列化器
- 列表视图使用
- pagination_class 分页控制类
- filter_bankends 过滤控制后端
- 详情页视图使用
- lookup_field 查询单一数据库对象时使用的条件字段,默认为“pk”
- lookup_url_kwarg 查询单一数据时URL中的参数关键字名字,默认与look_field相同
2.2 提供的方法
- 列表视图与详情视图
get_queryset()
返回视图使用的查询集,是列表视图与详情视图获取数据的基础,默认返回queryset属性,可以重写get_serializer_class()
返回序列化器对象,被其他视图或扩展类使用,如果我们在视图中想要获取序列化器对象,可以直接调用此方法get_serializer()
返回序列化器对象,被其他视图或扩展类使用,如果我们在视图中想要获取序列化器对象,可以直接调用此方法。get_object()
返回详情视图需要的一个模型对象,默认使用lookup_field参数来过滤queryset
我们来试一下GenericAPIView
serializer.py同上
views.py
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
class Book(GenericAPIView):
# 因为是queryset对象 可以自调用 加不加all()不重要,源码中默认加了 all()
queryset = models.Book.objects
serializer_class = serializer.BookModel
def get(self, request, *args, **kwargs):
books = self.get_queryset()
ser = self.get_serializer(books, many=True)
return Response(ser.data)
def post(self, request, *args, **kwargs):
ser = self.get_serializer(data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)
else:
return Response(ser.errors)
我们来发送下get请求,得到了我们需要的数据
我们来研究下源码
单个记录的 查改删的操作
urls.py
url(r'^bookinfo/(?P<pk>\d+)', views.BookInfo.as_view()),
views.py
整体逻辑和我们之前APIView差不多,方法上有略微改变
class BookInfo(GenericAPIView):
queryset = models.Book.objects
serializer_class = serializer.BookModel
def get(self,request,*args, **kwargs):
book = self.get_object()
ser = self.get_serializer(book)
return Response(ser.data)
def patch(self,request,*args, **kwargs):
book = self.get_object()
ser = self.get_serializer(book, data = request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)
else:
return Response('保存失败!')
def delete(self,request,*args, **kwargs):
book = self.get_object().delete()
return Response('删除成功!')
这次只测试下 修改方法
源码分析
三、视图扩展类(5个)
提供了几种后端视图(对数据资源进行曾删改查)处理流程的实现,如果需要编写的视图属于这五种,则视图可以通过继承相应的扩展类来复用代码,减少自己编写的代码量。
这五个扩展类需要搭配GenericAPIView父类,因为五个扩展类的实现需要调用GenericAPIView提供的序列化器与数据库查询的方法。
1. ListModelMixin
列表视图扩展类,提供list(request, *args, **kwargs)
方法快速实现列表视图,返回200状态码。该Mixin的list方法会对数据进行过滤和分页
源代码:
Copyclass ListModelMixin(object):
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
# 过滤
queryset = self.filter_queryset(self.get_queryset())
# 分页
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
# 序列化 自己覆盖GenericAPIView的queryset和serializer
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
举个栗子
class Book(ListModelMixin, CreateModelMixin, GenericAPIView):
queryset = models.Book.objects
serializer_class = serializer.BookModel
def get(self, request, *args, **kwargs):
# 调用继承的ListModelMixin的list方法并返回
return self.list(request, *args, **kwargs)
2.CreateModelMixin
创建视图扩展类,提供create(request, *args, **kwargs)
方法快速实现创建资源的视图,成功返回201状态码。
如果序列化器对前端发送的数据验证失败,返回400错误。
源代码:
Copyclass CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
# 获取序列化器
serializer = self.get_serializer(data=request.data)
# 验证
serializer.is_valid(raise_exception=True)
# 保存
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {
'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {
}
3. RetrieveModelMixin
详情视图扩展类,提供retrieve(request, *args, **kwargs)
方法,可以快速实现返回一个存在的数据对象。
如果存在,返回200, 否则返回404。
源代码:
Copyclass RetrieveModelMixin(object):
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
# 获取对象,会检查对象的权限
instance = self.get_object()
# 序列化
serializer = self.get_serializer(instance)
return Response(serializer.data)
举例:
Copyclass BookDetailView(RetrieveModelMixin, GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request, pk):
return self.retrieve(request)
4.UpdateModelMixin
更新视图扩展类,提供update(request, *args, **kwargs)
方法,可以快速实现更新一个存在的数据对象。
同时也提供partial_update(request, *args, **kwargs)
方法,可以实现局部更新。
成功返回200,序列化器校验数据失败时,返回400错误。
源代码:
Copyclass UpdateModelMixin(object):
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {
}
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
5.DestroyModelMixin
删除视图扩展类,提供destroy(request, *args, **kwargs)
方法,可以快速实现删除一个存在的数据对象。
成功返回204,不存在返回404。
源代码:
Copyclass DestroyModelMixin(object):
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
但是这样还是有局限性,我们还是要写两个视图类,配置两个路由
class Book(ListModelMixin, CreateModelMixin, GenericAPIView):
queryset = models.Book.objects
serializer_class = serializer.BookModel
def get(self, request, *args, **kwargs):
# 调用继承的ListModelMixin的list方法并返回
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class BookInfo(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericAPIView):
queryset = models.Book.objects
serializer_class = serializer.BookModel
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
有没有更好地方法呢?
有的!
四、子类视图(9个)
1.CreateAPIView
提供 post 方法
继承自: GenericAPIView、CreateModelMixin
2.ListAPIView
提供 get 方法
继承自:GenericAPIView、ListModelMixin
3.RetrieveAPIView
提供 get 方法
继承自: GenericAPIView、RetrieveModelMixin
4.DestoryAPIView
提供 delete 方法
继承自:GenericAPIView、DestoryModelMixin
5.UpdateAPIView
提供 put 和 patch 方法
继承自:GenericAPIView、UpdateModelMixin
6.RetrieveUpdateAPIView
提供 get、put、patch方法
继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin
7.RetrieveUpdateDestoryAPIView
提供 get、put、patch、delete方法
继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin
8.ListCreateAPIView:继承
有get获取所有,post方法新增
继承自:ListModelMixin,CreateModelMixin,GenericAPIVie
9.RetrieveDestroyAPIView
有get方法获取一条,delete方法删除
继承自:RetrieveModelMixin,DestroyModelMixin,GenericAPIView
使用方法
class Book(ListCreateAPIView, RetrieveDestroyAPIView, ):
# 根据多继承python3的广度优先,他会从左到右依次匹配相关的方法,进而执行
queryset = models.Book.objects
serializer_class = serializer.BookModel
有一点需要注意的是,单记录查询和多记录查询是不冲突的,因为他们用的接口是不一样的!
那么有没有连借口都不用写的方法呢?