• 4: drf视图组件


    一. 前言

    Django REST framwork 提供的视图的主要作用

    1. 控制序列化器的执行(检验、保存、转换数据)
    2. 控制数据库查询的执行
    

    二. 两个视图基类

    两个视图基类: APIView, GenericAPIView

    1. APIView

    1) models.py

    class Book(models.Model):
        name = models.CharField(max_length=32, null=True)
        price = models.DecimalField(max_digits=8, decimal_places=2, null=True)
        author = models.CharField(max_length=32, null=True)
        publish = models.CharField(max_length=32, null=True)
    

    2) serializer.py 自定义序列化.py文件

    from rest_framework import serializers
    
    from .models import Book
    
    def check_name(data):
        return data
    
    
    class BookSerializer(serializers.Serializer):
        nid = serializers.CharField(read_only=True, source='pk')
        name = serializers.CharField(required=True, error_messages={
            'required': '必须输入内容'
        }, validators=[check_name])
        price = serializers.DecimalField(max_digits=8, decimal_places=2, max_value=1000000, min_value=1)
        author = serializers.CharField(max_length=32, min_length=0, trim_whitespace=True)
        publish = serializers.CharField(allow_blank=True)
    
        def update(self, instance, validate_data):
            for key, value in validate_data.items():
                if hasattr(instance, key):
                    setattr(instance, key, value)
            instance.save()
            return instance
    
        def create(self, validate_data):
            return Book.objects.create(**validate_data)
    

    3) views.py

    # 自定义封装的SelfResponse类
    '''
    from rest_framework.response import Response
    
    
    class SelfResponse(Response):
        def __init__(self, status=1000, messages='成功', results=None, error=None, *args, **kwargs):
            self.data = {
                'status': status,
                'messages': messages,
            }
            if results:
                self.data['results'] = results
            elif results:
                self.data['error'] = error
            super().__init__(data=self.data, *args, **kwargs)
    '''
    from rest_framework.views import APIView
    from rest_framework.renderers import JSONRenderer
    
    from .models import Book
    from .serializer import BookSerializer
    from drf_views.utils import SelfResponse
    
    
    # Create your views here.
    class BookListCreateView(APIView):
        # 控制响应格式. 默认有2种: 一种响应浏览器, 一种响应json格式数据
        renderer_classes = [JSONRenderer]
    
        def get(self, request):
            book_queryset = Book.objects.filter()
            serializer = BookSerializer(instance=book_queryset, many=True)
            return SelfResponse(results=serializer.data)
    
        def post(self, request):
            serializer = BookSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save()
                obj = SelfResponse(results=serializer.data)
            else:
                obj = SelfResponse(2000, '失败', error=serializer.errors)
            return obj
    
    
    class BookDestroyRetrieveUpdate(APIView):
        def get(self, request, pk):
            book_obj = Book.objects.filter(pk=pk).first()
            serializer = BookSerializer(instance=book_obj)
            return SelfResponse(results=serializer.data)
    
        def put(self, request, pk):
            book_obj = Book.objects.filter(pk=pk).first()
            serializer = BookSerializer(instance=book_obj, data=request.data)
            if serializer.is_valid():
                serializer.save()
                obj = SelfResponse(results=serializer.data)
            else:
                obj = SelfResponse(2000, '失败', error=serializer.errors)
            return obj
    
        def delete(self, request, pk):
            Book.objects.filter(pk=pk).delete()
            return SelfResponse()
    

    4) urls.py

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

    5) 总结

    1. 继承关系: APIView继承View
    2. APIView基于View的拓展:
        APIView重写了View的dispatch方法, 在该方法中实现了实现了一下功能:
        1) 对来的原生请求对象request进行了封装.
        2) 提供了对包装过后的请求对象的三段认证: 认证, 权限控制, 频率控制
        3) 重写了View中通过本次请求的方式动态的反射到自定义继承APIView类实例化的对象中定义的请求方法
        4) 使用异常处理处理2,3步骤中的异常
        5) 处理完毕异常以后使用drf的response对象对请求响应
    3. 针对路由配置
        路由中的有名分组必须指定pk, 视图中使用必须使用相同的关键字参数接受
    

    2. GenericAPIView和5个视图扩展类写的接口#

    1) models.py

    from django.db import models
    
    # Create your models here.
    class Book(models.Model):
        name = models.CharField(max_length=32, null=True)
        price = models.DecimalField(max_digits=8, decimal_places=2, null=True)
        author = models.CharField(max_length=32, null=True)
        publish = models.CharField(max_length=32, null=True)
        publish_time = models.DateTimeField(auto_now_add=True)
    

    2) serializer.py 自定义序列化.py文件#

    from rest_framework import serializers
    from .models import Book
    
    
    class BookModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = Book
            fields = '__all__'
            # exclude = ('id', )
            # exclude = ['id']
            extra_kwargs = {
                # 'price': {'write_only': True}
            }
            read_only_fields = ['id', ]
            # 该方式在drf3.2版本以后就被弃用了, 使用ModelSerializer现在使用的的extra_kwargs
            # write_only_fields = []
    

    3) views.py

    # 自定义封装的SelfResponse类
    '''
    from rest_framework.response import Response
    
    
    class SelfResponse(Response):
        def __init__(self, status=1000, messages='成功', results=None, error=None, *args, **kwargs):
            self.data = {
                'status': status,
                'messages': messages,
            }
            if results:
                self.data['results'] = results
            elif results:
                self.data['error'] = error
            super().__init__(data=self.data, *args, **kwargs)
    '''
    from rest_framework.generics import GenericAPIView
    
    from drf_views.utils import SelfResponse
    from .models import Book
    from .serializer import BookModelSerializer
    
    
    # Create your views here.
    
    class BookListCrateView(GenericAPIView):
        # queryset = Book.objects.all()
        queryset = Book.objects
        serializer_class = BookModelSerializer
    
        def get(self, request):
            book_obj = self.get_queryset()
            serializer = self.get_serializer(instance=book_obj, many=True)
            return SelfResponse(results=serializer.data)
    
        def post(self, request):
            serializer = self.get_serializer(data=request.data)
            if serializer.is_valid():
                serializer.save()
                obj = SelfResponse(results=serializer.data)
            else:
                obj = SelfResponse(2000, '失败', error=serializer.errors)
            return obj
    
    
    class BookUpdateDestroyRetrieve(GenericAPIView):
        queryset = Book.objects
        serializer_class = BookModelSerializer
        lookup_url_kwarg = 'www'
    
        def get(self, request, www):
            book_obj = self.get_object()
            serializer = self.get_serializer(instance=book_obj)
            return SelfResponse(results=serializer.data)
    
        def put(self, request, pk):
            book_obj = self.get_object()
            serializer = self.get_serializer(instance=book_obj, data=request.data)
            if serializer.is_valid():
                serializer.save()
                obj = SelfResponse(results=serializer.data)
            else:
                obj = SelfResponse(2000, '失败', error=serializer.errors)
            return obj
    
        def delete(self, request, pk):
            self.get_object().delete()
            return SelfResponse()
    

    4) GenericAPIView提供的三种方法的源码分析

    # 1. self.get_queryset
        def get_queryset(self):
            # 1) 断言继承GenericAPIView的视图类实例化的对象时候有queryset对象
            '''
            如果没有: 那么断言成功抛出异常
            如果有:   那么将会继续往下执行
            因此为什么视图类中要为类新增一个queryset属性的原因就明白了
            '''
            assert self.queryset is not None, (
                "'%s' should either include a `queryset` attribute, "
                "or override the `get_queryset()` method."
                % self.__class__.__name__
            )
            # 2) 由自定义视图类实例化出来的对象获取类中定义的queryset对象. 进行判断
            ''''
            如果我们自定义视图类中书写的属性是queryset对象, 那么就会帮我们自动.all(). 因此我们可以不用点all了.
            如果不是, 那么就直接返回数据对象
            '''
            queryset = self.queryset
            if isinstance(queryset, QuerySet):
                # Ensure queryset is re-evaluated on each request.
                queryset = queryset.all()
            return queryset
    
    # 2. self.get_serializer
        def get_serializer(self, *args, **kwargs):
            # 1) 获取继承GenericAPIView视图类实例化对象中找get_serializer_class
            '''
            def get_serializer_class(self):
                # ① 断言继承GenericAPIVIew的自定义视图类实例化出来的对象中serializer_class时候为None
                '''
                为None断言成功, 抛出指定异常
                不为None断言失败, 继续往下执行
                '''
                assert self.serializer_class is not None, (
                    "'%s' should either include a `serializer_class` attribute, "
                    "or override the `get_serializer_class()` method."
                    % self.__class__.__name__
                )
                # ② 发现本质就是获取视图类中定义的serializer_class序列化器类, 因此从这里我们就明白为什么视图类中要写序列化器类了.
                return self.serializer_class
            '''
            # 2) get_serializer_class方法就是获取到了我们自定义视图类中定义的serializer_classes属性
            serializer_class = self.get_serializer_class()
            # 3) 内部就是return, 获取我们自定义视图类中的上下文一些信息
            '''
            def get_serializer_context(self):
                return {
                    'request': self.request,
                    'format': self.format_kwarg,
                    'view': self
                }
            '''
            kwargs['context'] = self.get_serializer_context()
            # 4) 这里的serializer_class就是自定义视图类中的类属性, 通过类属性调用传值
            return serializer_class(*args, **kwargs)
    
    # 3. self.get_object
        def get_object(self):
            # 1) 执行过滤操作, 默认没有配置过滤类. 因此queryset还是queryset
            queryset = self.filter_queryset(self.get_queryset())
    
            # 2) 运用短路运算, lookup_url_kwarg默认就是None,  lookup_field默认就是pk, 因此返回值就是pk
            '''
            提示: 继承GenericAPIView的自定义视图类中没有定义以下参数. 默认使用的是GenericAPIView类中配置的
            lookup_field = 'pk'
            lookup_url_kwarg = None
            '''
            lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
    
            # 3) 断言刚刚的pk时候在self.kwargs中
            '''
            查找顺序: 视图对象 -> 视图类 -> GenericAPIView -> APIView -> dispatch.
            kwargs就是路由匹配来了, 走到APIVIew中的dispatch方法中将传入的关键字参数形式的key:value对到kwargs字典中,
             再存到的对象当中, 直至此刻拿出来进行判断.
             本质就是必须安装关键字pk=xxx的形式传参, 路由中必须指定又名分组pk, 视图中必须指定接受的关键字参数是pk
            '''
            assert lookup_url_kwarg in self.kwargs, (
                'Expected view %s to be called with a URL keyword argument '
                'named "%s". Fix your URL conf, or set the `.lookup_field` '
                'attribute on the view correctly.' %
                (self.__class__.__name__, lookup_url_kwarg)
            )
            # 4) 这里就是获取默认定义lookup_field充当字典的key, 又从self.kwargs这个dispatch方法就赋值的字典中, 将通过key取值, lookup_url_kwarg就是第2步分析出来的值
            filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
            # 5) get_object_or_404的功能就是获取对象 或者 抛出抛出404异常
            '''
            **filter_kwargs就是步骤4的字典. 字典的结构{'pk': value},
            **就将字典拆散, 以关键字的形式传参, 传给了get_object_or_404方法
    
            def get_object_or_404(queryset, *filter_args, **filter_kwargs):
                try:
                    # _get_object_or_404内部就是从闯传进来的queryset对象中使用get以关键字的形式查询,但是get方法会在2种情况下, 会抛出异常, 因此内部也做了异常处理
                    return _get_object_or_404(queryset, *filter_args, **filter_kwargs)
                except (TypeError, ValueError, ValidationError):
                    # 这里就获取到了_get_object_or_404中抛出异常, 由Http404实例化类实例化出来的对象返回到上一层
                    raise Http404
    
            拓展: 修改必须使用pk作为有名分组!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            本质就是通过get将传过来的字段拆散, 以关键字的形式获取对应的数据对象
            因此在自定义的视图类中重写 lookup_url_kwarg 就可以实现, 修改路由中, 对应的又名分组必须取名是pk了.
            例如: 有名分组是(?P<num>), 关键字必须num接受
                那么self.kwargs在给视图对象中方法传参的时候就是{'num': param}而,
                self.kwargs[lookup_url_kwarg]这一步操作直接获取到的就是param了.
            '''
            obj = get_object_or_404(queryset, **filter_kwargs)
    
            # 5) 这一步是APIView中dispatch方法中定义的三段认证中的权限认证. 在drf的认证中会讲到
            self.check_object_permissions(self.request, obj)
    
            return obj
    

    5) urls.py

    from django.conf.urls import url
    from .views import BookListCrateView
    from .views import BookUpdateDestroyRetrieve
    
    urlpatterns = [
        url(r'^books/$', BookListCrateView.as_view()),
        url(r'^books/(?P<www>d+)', BookUpdateDestroyRetrieve.as_view()),
    ]
    

    6) 总结

    1. 继承GenericAPIView的视图类, 当需要修改接口指定操作的模型类, 以及序列化的模型类直接在视图类中修改即可了.其他都不用动就可以实现偷换模型类以及序列化类
    2. GenericAPIView提供了3个主要的方法
        self.get_object()     获取单条数据
        self.get_queryset()   获取多条数据
        self.serializer_classes(参数同原来即可)  执行自定义视图类中定义的序列化类进行序列化, 将ORM对象的数据转换成python的对象
    3. GenericAPIView提供了可修改路由又名分组的指定不再是默认的pk
        自定义的视图类
    

    三. 五个视图扩展类

    1) models.py

    from django.db import models
    
    
    # Create your models here.
    class Book(models.Model):
        name = models.CharField(max_length=32)
        price = models.DecimalField(max_digits=8, decimal_places=2)
        author = models.CharField(max_length=32)
    
        # 这里的定义可以让序列化类中当作序列化字段来处理, return的结果是什么就是什么.
        @property
        def current_time(self):
            import time
            return time.strftime("%Y-%m-%d %X")
    
        publish = models.ForeignKey(to='Publish')
    
    
    class Publish(models.Model):
        name = models.CharField(max_length=32)
        email = models.EmailField()
    

    2) serializer.py 自定义序列化.py文件

    from rest_framework import serializers
    
    from .models import Book
    from .models import Publish
    
    
    class PublishModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = Publish
            fields = '__all__'
    
    
    class BookModelSerializer(serializers.ModelSerializer):
        publish = PublishModelSerializer()
        '''
        publish = serializers.SerializerMethodField(read_only=True, source='publish')
        # publish = serializers.CharField()
        def get_publish(self, instance):
            print('instance:', instance)
            fields_list = ['name', 'email']
            fields_dict = {}
            for field in fields_list:
                if hasattr(instance.publish, field):
                    fields_dict[field] = getattr(instance.publish, field)
            return fields_dict
        '''
        # 这里不做任何格外的校验处理, 只是看看校验成功以后的结果
        def validated_price(self, data):
            # print('data:', data)
            return data
    
        # 这里也是
        def validate(self, validate_data):
            # print('validate validate_data:', validate_data)
            return validate_data
    
        class Meta:
            model = Book
            # fields = ['id', 'name', ...]
            # exclude = ['id']
            fields = '__all__'
            extra_kwargs = {
                'price': {'max_value': 10000, 'min_value': 0}
            }
    
        def create(self, validated_data):
            publish_dict = validated_data.pop('publish')
            publish_obj = Publish.objects.create(**dict(publish_dict))
    
            Book.objects.create(**validated_data, publish=publish_obj)
            validated_data.update({'publish': publish_dict})
    
            return validated_data
    
        def update(self, instance, validated_data):
            print('validated_data:', validated_data)
            publish_dict = validated_data.pop('publish')
            Publish.objects.filter(book__pk=instance.pk).update(**publish_dict)
    
            Book.objects.filter(pk=instance.pk).update(**validated_data)
            validated_data.update({'publish': publish_dict})
            return validated_data
    

    3) views.py

    from rest_framework.generics import GenericAPIView
    from rest_framework.mixins import ListModelMixin, DestroyModelMixin, UpdateModelMixin, CreateModelMixin, 
        RetrieveModelMixin
    
    from .models import Book
    from .serializer import BookModelSerializer
    
    
    class BookCreateListView(CreateModelMixin, ListModelMixin, GenericAPIView):
        queryset = Book.objects
        serializer_class = BookModelSerializer
    
        def get(self, request):
            """
            list 的实现是 ListModelMixin 类中定义的 list 方法,
            并在 GenericAPIView 类中定义的的方法基础之上实现查询数据的功能
            """
            return self.list(request)
    
        def post(self, request):
            """
            create 的实现是 CreateModelMixin 类中定义的 create 方法,
            并在 GenericAPIView 类中定义的的方法基础之上实现新增数据的功能
            """
            print('request.data:', request.data)
            return self.create(request)
    
    
    class BookUpdateDestroyRetrieveView(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericAPIView):
        queryset = Book.objects
        serializer_class = BookModelSerializer
    
        def get(self, request, pk):
            """
            retrieve 的实现是 RetrieveModelMixin 类中定义的 retrieve 方法,
            并在 GenericAPIView 类中定义的的方法基础之上实现查询一条数据的功能
            """
            return self.retrieve(request, pk)
    
        def put(self, request, pk):
            """
            update 的实现是 UpdateModelMixin 类中定义的 update 方法,
            并在 GenericAPIView 类中定义的的方法基础之上实现更新一条数据的功能
            """
            return self.update(request, pk)
    
        def delete(self, request, pk):
            """
            destroy 的实现是 DestroyModelMixin 类中定义的 destroy 方法,
            并在 GenericAPIView 类中定义的的方法基础之上实现删除一条数据的功能
            """
            return self.destroy(request, pk)
    

    4) urls.py

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

    5) 总结

    提示: 以下的5种视图扩展类必须和GenericAPIView连用
    ListModelMixin     内部封装了list方法, 实现了查询所有数据
    CreateModelMixin   内部封装了create方法, 实现了新增数据
    RetrieveModelMixin 内部封装了retrieve方法, 实现了查询一条数据
    UpdateModelMixin   内部封装了update方法, 实现了更新一条数据
    DestroyModelMixin  内部封装
    

    四. GenericAPIView的9个视图子类

    1) 9个GenericAPIView的视图子类快速介绍

    提示: 以下都是基与对应的5种不同的视图扩展类ModelMixin和GenericAPiView. 在原来的基础之上剔除了对应的上一个节需要定义的重复的方法
    CreateAPIView
    DestroyAPIView
    UpdateAPIView
    ListAPIView
    RetrieveAPIView
    ListCreateAPIView
    RetrieveDestroyAPIView
    RetrieveUpdateDestroyAPIView
    RetrieveUpdateAPIView
    

    2) views.py

    from rest_framework.generics import GenericAPIView, 
        CreateAPIView, DestroyAPIView, UpdateAPIView, ListAPIView, RetrieveAPIView, 
        ListCreateAPIView, RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView, RetrieveUpdateAPIView
    
    class BookListCreateAPIView(ListCreateAPIView):
        queryset = Book
        serializer_class = BookModelSerializer
    
    
    class BookRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
        queryset = Book
        serializer_class = BookModelSerializer
    

    3) urls.py

    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
        url(r'^books/$', views.BookModelViewSet.as_view(actions={'get': 'list',})),
        url(r'^books/(?P<pk>d+)',
            views.BookModelViewSet.as_view(actions={'get': 'retrieve', 'put': 'update',})),
    ]
    

    五. ViewSetMixin

    1) views.py

    from rest_framework.viewsets import ViewSetMixin
    class Book6View(ViewSetMixin,APIView): #一定要放在APIVIew前
        def get_all_book(self,request):
            print("xxxx")
            book_list = Book.objects.all()
            book_ser = BookSerializer(book_list, many=True)
            return Response(book_ser.data)
    

    2) urls.py

    # 继承ViewSetMixin的视图类,路由可以改写成这样
    path('books6/', views.Book6View.as_view(actions={'get': 'get_all_book'})),
    

    3) 总结

    只要是基础 ViewSetMixin 的类都可以修改视图类中方法的调用名
    

    六. ModelViewSet

    1) views.py

    Copyfrom rest_framework.viewsets import ModelViewSet, ViewSetMixin, GenericViewSet, ViewSet, ReadOnlyModelViewSet
    
    class BookModelViewSet(ModelViewSet):
        queryset = Book.objects
        serializer_class = BookModelSerializer
    
        # ModelViewSet -> GenericViewSet -> ViewSetMixin 因此继承了ModelViewSet的视图类, 也可以在视图类中自定义方法, 只是路由中指定的要一致
        def get_all(self, request):
            return self.list(request)
    

    2) urls.py

    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
        url(r'^books/$', views.BookModelViewSet.as_view(actions={'get': 'get_all', 'post': 'create'})),
        url(r'^books/(?P<pk>d+)',
            views.BookModelViewSet.as_view(actions={'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
    ]
    

    3) 其他viewsets中的类

    1. GenericViewSet(
        ViewSetMixin, generics.GenericAPIView
    )
    2. ViewSet(
        ViewSetMixin, views.APIView
    )
    3. ReadOnlyModelViewSet(
        mixins.RetrieveModelMixin,
        mixins.ListModelMixin,
        GenericViewSet
    )
    

    4) 总结

    介绍: ModelViewSet继承关系非常多, 通过一层层的继承, 只需要很少的代码就能实现API的5中视图接口
        它, 还可以和路由组件使用, 这样就直接使用路由直接生成对应功能的路由接口, 以及在不指定路由的基础之上默认帮我们书写了路由中在as_view()类方法中添加的actions参数了
    
    庞大的继承可实现的功能:
        1. APIView提供的
            request封装
            三段认证
            全局异常处理
            response响应格式
        2. GenericAPIView提供的
            视图类中声明queryset 和 serializer_classes 类属性
            self.get_object	()
            self.get_queryset()
            self.get_serializer()
        3. 五个基本视图扩展类ModelMixin系列提供的
            self.list()
            self.create()
            self.update()
            self.retrieve()
            self.destroy()
        4. ViewSetMixin提供的
            修改路由中ac_view()类方法的actions参数
            如:
                路由配置: url(r'index/', BookView.as_view(actions={'get': 'get_list'}))
                视图使用:
                    class BookView(ViewSetMixin, APIView):
                        def get_list(self, request):
                            ...
    

    七. 继承关系流程图图片-20200707211311713

    image-20201108214709445

  • 相关阅读:
    010-spring事务管理
    009-事务管理
    008-ThreadLocal
    Bmob用户管理操作
    Textview下划线注册用户跳转实现
    Android中多个调用Activity的问题
    解决android:theme="@android:style/Theme.NoDisplay" 加入这句话后程序不能运行
    友盟自动更新
    友盟消息推送和更新XML配置
    Android 云服务器的搭建和友盟APP自动更新功能的实现
  • 原文地址:https://www.cnblogs.com/wait59/p/13976444.html
Copyright © 2020-2023  润新知