APIView :
在django中写CBV的时候是继承View, rest_framework继承的是APIView, 这两种具体有什么不同呢?
urlpatterns = [ url(r'^book$', BookView.as_view()), url(r'^book/(?P<id>d+)$', BookEditView.as_view()), ]
无论是View还是APIView最开始调用的都是as_view()方法, 看源码:
可以看到, APIView继承了View, 并且执行了View中的as_view()方法, 最后把view返回, 用csrf_exempt()方法包裹后去掉了csrf的认证.
而在View中的as_view()方法如下:
在View中的as_view方法返回了view函数, 而view函数执行了self.dispatch()方法, 但是这里的dispatch方法应该是APIView中的.
再去initialize_request中看下把什么赋值给了request, 并且赋值给了self.request, 也就是在视图中用的request.xxx到底是什么?
可以看到, 这个方法返回的是Request这个类的实例对象, 而这个Request类中的第一个参数request, 使我们在django中使用的request.
可以看到, 这个Request类把原来的request赋值给了self._request, 也就是说_request就是我们原先的request, 新的request使我们这个Request类.
那继承APIView之后请求来的数据都在哪呢?
当我们使用了rest_framework框架之后, 我们的request是重新封装的Request类.
request.query_params 存放的是我们get请求的参数.
request.data 存放的是我们所有的数据, 包括post请求的以及put, patch请求.
相比原来的django的request, 我们现在的request更加精简, 清晰.
封装代码 :
原代码(封装之前) :
from django.conf.urls import url from SerDemo import views urlpatterns = [ # 第一二版本 # url(r'^book/$', views.BookView.as_view()), # url(r'^book/(?P<edit_id>d+)', views.BookEditView.as_view()), ]
from rest_framework import serializers from app01 import models class PublisherSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) class AuthorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) # 一个判断某一个字段是否含有敏感信息的函数, 直接调用即可 def my_validate(value): if "敏感信息" in value.lower(): raise serializers.ValidationError("存在敏感词汇!!!") class BookSerializer(serializers.Serializer): id = serializers.IntegerField(required=False) # required 为False时, 反序列化不做校验 title = serializers.CharField(max_length=32, validators=[my_validate]) pub_time = serializers.DateField() category = serializers.CharField(source="get_category_display", read_only=True) # 自定义一个字段只用来反序列化接收使用 post_category = serializers.IntegerField(write_only=True) publisher = PublisherSerializer(read_only=True) publisher_id = serializers.IntegerField(write_only=True) # 多对多有many参数 author = AuthorSerializer(many=True, read_only=True) author_list = serializers.ListField(write_only=True) # 新增数据要重写的create方法 def create(self, validated_data): # validated_data是验证通过的数据 # 通过ORM操作给Book表增加数据 # 添加除多对多字段的所有字段 book_obj = models.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.author.add(*validated_data["author_list"]) return book_obj # 更新数据要重写update方法 def update(self, instance, validated_data): # instance 是要更新的对象 # 对除多对多字段以外的字段进行更新, 并设置当前已存在的数据为默认值 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) # 判断前端传过来的数据是否含有author_list字段, 如果有则更新, 没有就不变动 if validated_data.get("author_list"): instance.author.set(validated_data["author_list"]) instance.save() return instance # 对前端传过来的数据进行条件控制 def validate(self, attrs): # 相当于钩子函数 # attrs是一个字典, 含有传过来的所有字段 if "python" in attrs["title"].lower() and attrs["post_category"] == 1: return attrs else: raise serializers.ValidationError("分类或标题不匹配")
from rest_framework.views import APIView from app01 import models from .serializers import BookSerializer from rest_framework.response import Response from rest_framework.viewsets import ViewSetMixin # Create your views here. # 版本一 class BookView(APIView): def get(self, request): book_queryset = models.Book.objects.all() # 用序列化器进行序列化 ser_obj = BookSerializer(book_queryset, many=True) return Response(ser_obj.data) def post(self, request): # 接收前端传过来的数据 book_obj = request.data # 对前端传过来的数据使用自定义序列化方法进行校验(是否合法等) ser_obj = BookSerializer(data=book_obj) # 如果校验通过做些什么 if ser_obj.is_valid(): ser_obj.save() # validated_data是校验通过之后的数据 return Response(ser_obj.validated_data) # 验证不通过返回错误信息 return Response(ser_obj.errors) class BookEditView(APIView): def get(self, request, edit_id): book_obj = models.Book.objects.filter(id=edit_id).first() ser_obj = BookSerializer(book_obj) return Response(ser_obj.data) def put(self, request, edit_id): book_obj = models.Book.objects.filter(id=edit_id).first() ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True) if ser_obj.is_valid(): ser_obj.save() return Response(ser_obj.validated_data) return Response(ser_obj.errors) def delete(self, request, edit_id): book_obj = models.Book.objects.filter(id=edit_id).first() if not book_obj: return Response("删除对象不存在!") book_obj.delete() return Response("删除成功了呢!")
第一次封装 :
from django.conf.urls import url from SerDemo import views urlpatterns = [ # 第一二版本 # url(r'^book/$', views.BookView.as_view()), # url(r'^book/(?P<edit_id>d+)', views.BookEditView.as_view()), ]
class GenericAPIView(APIView): """ 定义一个公共的类, 用来获取需要的资源 """ # 设置默认操作的数据为空 queryset = None # 设置需要使用的序列化器为空 serializer_class = None def get_queryset(self): """ 定义一个获取需要操作的数据的函数 子类函数直接调用即可 子类中调用此方法, 返回值中的self指的是调用此方法的子类的实力化对象 queryset属性在本类中是None, 每一个继承此类的子类中都会重写queryset和serializer_class方法 然后子类中执行此类中的方法是去执行子类对应的属性. :return: """ return self.queryset.all() def get_serializer_class(self, *args, **kwargs): return self.serializer_class(*args, **kwargs) class ListModelMixin: """ 定义一个展示类,将展示方法统一写成一个方法 """ def list(self, request): queryset = self.get_queryset() ser_obj = self.get_serializer_class(queryset, many=True) return Response(ser_obj.data) class CreateModeMixin: """ 新增的视图类 """ def create(self, request): ser_obj = self.get_serializer_class(data=request.data) if ser_obj.is_valid(): ser_obj.save() return Response(ser_obj.validated_data) return Response(ser_obj.errors) class EditModeMixin: """ 编辑的视图类 """ def retrieve(self, request, id): obj = self.get_queryset().filter(id=id).first() ser_obj = self.get_serializer_class(obj) return Response(ser_obj.data) class UpdateModeMixin: """ 更新的视图类 """ def update(self, request, id): obj = self.get_queryset().filter(id=id).first() ser_obj = self.get_serializer_class(instance=obj, data=request.data, partial=True) if ser_obj.is_valid(): ser_obj.save() return Response(ser_obj.validated_data) return Response(ser_obj.errors) class DeleteModeMixin: """ 删除的视图类 """ def destroy(self, request, id): obj = self.get_queryset().filter(id=id).first() if not obj: return Response("删除的对象不存在!") obj.delete() return Response("删除了呢") class ListCreateAPIview(GenericAPIView, ListModelMixin, CreateModeMixin): pass class OperationAPIview(GenericAPIView, EditModeMixin, UpdateModeMixin, DeleteModeMixin): pass class BookView(ListCreateAPIview): # 定义queryset属性时, DRF方法内部和关键字重名, 内部会识别并将此属性做缓存 # 换个名字DRF不识别属性, 不做缓存, 也就不需要.all() queryset = models.Book.objects.all() serializer_class = BookSerializer def get(self, request): return self.list(request) def post(self, request): return self.create(request) class BookEditView(OperationAPIview): queryset = models.Book.objects.all() serializer_class = BookSerializer def get(self, request, edit_id): return self.retrieve(request, edit_id) def put(self, request, edit_id): return self.update(request, edit_id) def delete(self, request, edit_id): return self.destroy(request, edit_id)
我们封装的GenericAPIView, , 包括封装的每个方法的类, 其实框架都帮我们封装好了,
我们可以继承这个二类, 来实现上面的视图.
其中框架还给我们提供了一个路由传参的方法:
actioon这个默认参数其实就是接收路由参数的参数.
再次封装 :
from SerDemo import views urlpatterns = [ # 第三版 # url(r'^book/$', views.BookModelView.as_view({"get": "list", "post": "create"})), # url(r'^book/(?P<pk>d+)', views.BookModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})), ]
from rest_framework.viewsets import ViewSetMixin # 重写了源码中的as_view()方法, 是as_view方法可以传参数 # 在执行dispatch()方法之前 class ModelViewSet(ViewSetMixin, ListCreateAPIview, OperationAPIview): pass class BookModelView(viewsets.ModelViewSet): queryset = models.Book.objects.all() serializer_class = BookSerializer
这样我们的视图只要写两行就可以了
其实我们所写的所有视图, 框架都帮我们封装好了.
注意 :
应框架封装的视图, url上的那个关键字参数要用pk, 系统默认的
继承顺序图 :
DRF路由 :
# 最终版 # 帮助我们生成带参数的路由 from rest_framework.routers import DefaultRouter # 实例化DefaultRouter对象 router = DefaultRouter() # 注册我们的路由以及视图 router.register(r"book", views.BookModelView) urlpatterns = [ ] urlpatterns += router.urls
可以看到, 通过框架可以把路由视图都变得非常简单, 但是需要自定制的时候还是需要自己用APIView写, 当不需要那么多路由时候, 也不需药使用这种路由注册.