本节大纲

  1、Generic Views

  2、ViewSets 

 

 

1、Generic Views

CBV的主要的一个优点就是极大的允许了对于代码的从用。自然,rest framework取其优势,提供了很多可以重构的视图。rest framework 提供的 Generic Views可以让你很快速的构建跟数据库模型映射紧密的API视图。

如果 generic view不满足你的API需求,很简单,你可以放弃它去使用正常的APIView类,或者将generic view内部包含的mixins和基础类重构成自己需要的,完成对于generic view的重用

样例

class StudentListView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentListSerializer

###########################################serilizers,单独建立一个serializers文件
from rest_framework.serializers import ModelSerializer
from app01.models import Student

class StudentListSerializer(ModelSerializer):

    class Meta:
        model = Student
        fields = ('name', 'age', 'gender', 'uuid')


###########################################url

    path('student/', StudentListView.as_view(), name='list-student'),

对于更复杂的需求,你可能需要重写各种各样的方法在你的视图类上,例如

class StudentListView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentListSerializer

    def list(self, request, *args, **kwargs):
        queryset = self.get_queryset()
        serializer = StudentListSerializer(queryset, many=True)
        return Response(serializer.data)

如上、我们重写了list方法,这是查看一下源码会发现这是ListAPIView的get方法调用的方法。

对于一些简单的例子,你可以通过as_view()方法传入各种属性,例如

path('student/', StudentListView.as_view(queryset = Student.objects.all(), serializer_class = StudentListSerializer), name='list-student'),

 

API Reference

GenericAPIView继承并扩展了APIView类,添加了很多对于标准的列表跟详细视图的常用的行为,其中每一个具体的generic views都是由GenericAPIView和其他一个或者多个mixin类组合而成。

 

Attributes

Basic settings

下面的属性控制这基础视图的行为。

1、queryset

用来返回一个视图的对象。你必须设置这个属性,或者是重写get_queryset()方法。如果你在重写一个视图方法,调用get_queryset()比直接用这个属性更好,因为queryset会一次获取整体的范围,这些数据将被缓存给各个随后的请求

2、serializer_class

序列化的类,用来验证,反序列化输入和序列化输出。同样的可以设置这个属性,或者是重写get_serializer_class()方法

3、lookup_field

这个模型字段应该被用来给对象查找单独的模型实例,默认是pk,如果url上的匹配也是pk,视图会自动筛选出主键值等于pk值的单独模型实例。

4、lookup_url_kwarg

URL关键字参数,可以被用来查找对象,url设置里面用该包含一个关键字对应的值,如果没有设置,默认使用lookup_field.

 

Pagination

接下来的方法主要是和list view相关的分页。

1、pagination_class

用来对list结果进行分页。可以在setting里面配置用'rest_framework.pagination.PageNumberPagination'来配置一个'DEFAULT_PAGINATION_CLASS'参数

 

Filtering

1、filter_backends

有很多可用的filter backend类可以用来对queryset进行筛选。默认可以再settings文件中设置DEFAULT_FILTER_BACKENDS

 

Methods

Base methods

1、get_query(self)

返回一个可以被list视图使用的queryset,并且作为后面查询具体视图的基础,默认返回queryset由queryset属性控制

def get_queryset(self):
    user = self.request.user
    return user.accounts.all()

2、get_object(self)

返回一个给具体视图(detail view)使用的对象实例。默认用lookup_field参数来筛选基础的queryset,可以被重写来提供更复杂的行为。

def get_object(self):
    queryset = self.get_queryset()
    filter = {}
    for field in self.multiple_lookup_fields:
        filter[field] = self.kwargs[field]

    obj = get_object_or_404(queryset, **filter)
    self.check_object_permissions(self.request, obj)
    return obj

如果你的api没有权限认证,那可以直接丢弃掉最后一句self.check_object_permissions(self.request, obj)

3、filter_queryset

传入一个queryset,通过在使用的filter backends来筛选它,返回一个新的queryset

def filter_queryset(self, queryset):
    filter_backends = (CategoryFilter,)

    if 'geo_route' in self.request.query_params:
        filter_backends = (GeoRouteFilter, CategoryFilter)
    elif 'geo_point' in self.request.query_params:
        filter_backends = (GeoPointFilter, CategoryFilter)

    for backend in list(filter_backends):
        queryset = backend().filter_queryset(self.request, queryset, view=self)

    return queryset

4、get_serializer_class(self)

返回一个序列化的类,默认返回serializer_class属性也可以重写成动态行为

def get_serializer_class(self):
    if self.request.user.is_staff:
        return FullAccountSerializer
    return BasicAccountSerializer

 

Save and deletion hooks(保存和删除的钩子)

下面这些方法是mixin类提供的,提供了对对象的保存和删除行为简易重写

.perform_create(self, serializer)       ==> 当保存一个新的实例对象时由CreateModelMixin发起

.perform_update(self, serializer)   ==> 当更新一个已有的实例对象时由UpdateModelmixin发起

.perform_destory(self, instance)   ==> 当删除一个实例对象时由DestoryModelMixin发起

当你需要的数据在request里面,但是不是request.data的一部分的时候,这些钩子就会变得特别有用,

比如你想记录request.user到实例里面

def perform_create(self, serializer):
    serializer.save(user=self.request.user)

比如你想在保存操作的同时发送一封邮件出去

def perform_update(self, serializer):
    instance = serializer.save()
    send_email_confirmation(user=self.request.user, modified=instance)

比如利用这些钩子提供额外的验证在数据库存储数据之前,并可以引发ValidationError()

def perform_create(self, serializer):
    queryset = SignupRequest.objects.filter(user=self.request.user)
    if queryset.exists():
        raise ValidationError('You have already signed up')
    serializer.save(user=self.request.user)

注意,这些方法代替了2.X版本里面的pre_save, post_save, pre_delete post_delete方法,它们不再可用

在GenericAPIView里面,顺便跟下面的方法有个眼缘。。

get_serializer_context(self)  #返回一个字典包含任何额外的数据,需要添加到serializer里面的

get_serializer(self, instance=None, data=None, many=False, partial=False)  # 返回一个serializer实例

get_paginated_response(self, data)  # 返回一个分页格式的Response对象

paginate_queryset(self, queryset)  # 对有需求的queryset进行分页,返回一个page对象或者空None,如果没有配置

 

Mixins

mixin类提供了一些是用来提供基础视图行为的措施。注意是mixin类提供的行为方法,而不是自定义处理方法,比如.get()或者.post(). 它允许更加灵活的组合行为

导入

rest_framework.mixins

ListModelMixin

提供.list(request, *args, **kwargs)方法,正常会返回200 ok的返回和序列化的queryset作为body,返回的数据可以被分页。

CreateModelMixin

提供了.create(request, *args, **kwargs)方法,正常返回201 created返回和序列化的对象作为body. 如果返回的数据不合法,返回400和错误的详细作为body

RetrieveModelMixin

提供.Retrieve(request, *args, **kwargs)方法,正常返回200 ok和创建好的序列化实例. 否则返回404 Not Found.

UpdateModelMixin

提供.update(request, *args, **kwargs)方法,更新现有的实例。也提供了.partial_update(request, *args, **kwargs)方法,跟update很像,除了所有的更新字段都是可选的。允许支持patch请求。正常返回200 ok返回,序列化的对象。否则400 bad request.

DestoryModelMixin

.destory(request, *args, **kwargs)方法,正常204 No Content返回,否则404 Not Found.

 

Concrete View Classes

CreateAPIView

只创建;支持post方法处理;扩展:GenericAPIView, CreateModelMixin

ListAPIView

只读,展示一组模型实例;支持get方法处理;扩展:GenericAPIView, ListModelMixin

RetrieveAPIView

只读,展示一个单独的模型实例,支持get方法处理;扩展:GenericAPIView, RetrieveModelMixin

DestoryAPIView

只删除,针对单独的模型实例. 支持put和patch方法处理;扩展:GenericAPIView, UpdateModelMixin

UpdateAPIView

只更新,针对单独模型实例. 支持put和patch方法处理;扩展:GenericAPIView, UpdateModelMixin

ListCreateAPIView

读写,展示一组模型实例. get和post方法处理;扩展:GenericAPIView,ListModelMIxin,CreateModelMixin

RetrieveUpdateAPIView

读或者更新,展示一个独立的模型实例. get, put和patch方法处理;扩展:GenericAPIView, RetrieveModelMixin,UpdateModelMixin

RetrieveDestoryAPIView

读或删除,展示一个独立的模型实例. get和delete方法处理;扩展:GenericAPIView,RetrieveModelMixin, DestoryModelMIxin

RetrieveUpdateDestoryAPIView

读或者删除,展示一个独立的模型实现. get和delete方法处理;扩展:GenericAPIView,RetrieveModelMixin,DestoryModelMixin

RetrieveUpdateDestoryAPIView

读写删除,展示一个独立的模型实例. get, put, patch和delete方法处理;扩展:GenericAPIView,RetrieveMinxin,UpdateModelMixin,DestoryModelMixin

 

Customizing the generic views

Creating custom mixins

比如,需要查找对象基于多个url字段,你可以创建一个mixin类如下:

class MultipleFieldLookupMixin(object):
    """
    Apply this mixin to any view or viewset to get multiple field filtering
    based on a `lookup_fields` attribute, instead of the default single field filtering.
    """
    def get_object(self):
        queryset = self.get_queryset()             # Get the base queryset
        queryset = self.filter_queryset(queryset)  # Apply any filter backends
        filter = {}
        for field in self.lookup_fields:
            if self.kwargs[field]: # Ignore empty fields.
                filter[field] = self.kwargs[field]
        obj = get_object_or_404(queryset, **filter)  # Lookup the object
        self.check_object_permissions(self.request, obj)
        return obj

你可以简单得将这个mixin应用在view或者viewset,当你想需要应用这个客制化行为的任何时候

class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_fields = ('account', 'username')

Creating Custom Base Classes

如果你使用mixin链接多个视图,可以用下面的这种更进一步创建你自己的基础视图,贯穿你的项目

class BaseRetrieveView(MultipleFieldLookupMixin,
                       generics.RetrieveAPIView):
    pass

class BaseRetrieveUpdateDestroyView(MultipleFieldLookupMixin,
                                    generics.RetrieveUpdateDestroyAPIView):
    pass

 

Put As Create

3.0版本的rest framework mixins 对待put即是更新也是创建,主要依赖于你的项目是不是已经存在

 

2、ViewSets

首先需要记住的一点是viewset是一种简单的CBV,不提供任何处理方法比如.get()或者.post(),而是用.list().create()来替代,这种东西翻一下源码就知道了,当然熟悉了也就不需要特别记忆了。

还有一点值得注意的是相比于直接注册带有视图集在url设置里,使用路由类注册视图集将会是更好的选择,他将自动的为你决定url的设置,例如

from rest_framework.generics import get_object_or_404
from app01.serializers import PersonModelSerializer
from rest_framework.viewsets import ModelViewSet, ViewSet
from rest_framework.response import Response
from app01.models import PersonResource


class StudentViewSet(ViewSet):
    def list(self, request):
        queryset = PersonResource.objects.filter(job=1)
        serializer = PersonModelSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        queryset = PersonResource.objects.filter(job=1)
        student = get_object_or_404(queryset, pk=pk)
        serializer = PersonModelSerializer(student)
        return Response(serializer.data)

如果需要的话,可以丙丁这个视图集到两个分开的视图,如下

student_list = StudentViewSet.as_view({'get': 'list'})
student_detail = StudentViewSet.as_view({'get': 'retrieve'})

但实际上不会这么做,而是注册一个路由绑定视图集来替代,这允许了url自动生成

from rest_framework.routers import DefaultRouter
from app01.customviewset import StudentViewSet


router = DefaultRouter()
router.register(r'student', StudentViewSet, base_name='student-viewset')


urlpatterns = [
    ......
]

urlpatterns += router.urls

此时就已经借助ViewSet完成了2类API

http://127.0.0.1:8001/api/student/
http://127.0.0.1:8001/api/student/1/

 下面截图可以看到具体提供的URL格式,带format的就不说了,看了应该懂,只看对目前有用的就行

Django REST Framework API Guide 02

Django REST Framework API Guide 02

Django REST Framework API Guide 02

在使用viewset来完成此API工作的时候,可以发现还是有很多重复的变量申明的,对于追求极致的API来说显然是不允许的,所以接下来修改成ModelViewSet

class StudentViewSet(ModelViewSet):
    queryset = PersonResource.objects.filter(job=1)
    serializer_class = PersonModelSerializer

其实这个定义跟上面讲的封装的高级视图很相似。

 

ViewSet Actions

class UserViewSet(viewsets.ViewSet):
    """
    Example empty viewset demonstrating the standard
    actions that will be handled by a router class.

    If you're using format suffixes, make sure to also include
    the `format=None` keyword argument for each action.
    """

    def list(self, request):
        pass

    def create(self, request):
        pass

    def retrieve(self, request, pk=None):
        pass

    def update(self, request, pk=None):
        pass

    def partial_update(self, request, pk=None):
        pass

    def destroy(self, request, pk=None):
        pass

Introspecting ViewSet Actions

在dispatch的时候,下面的属性在视图集内是可用的

    basename - the base to use for the URL names that are created.
    action - the name of the current action (e.g., list, create).
    detail - boolean indicating if the current action is configured for a list or detail view.
    suffix - the display suffix for the viewset type - mirrors the detail attribute.

你可以注入下面的属性来调整当前操作的行为,比如限制权限

def get_permissions(self):
    """
    Instantiates and returns the list of permissions that this view requires.
    """
    if self.action == 'list':
        permission_classes = [IsAuthenticated]
    else:
        permission_classes = [IsAdmin]
    return [permission() for permission in permission_classes]

 

Marking extra actions format routinng

如果你有特殊的方法应该是可路由的,你可以用@action装饰器来标记它们。与常规操作一样,额外的操作可以用于对象列表或单个实例。通过detail参数的True、False标示这个。通过DefaultRouter来配置action,包含pk在URL里面。

class StudentViewSet(ModelViewSet):
    queryset = PersonResource.objects.filter(job=1)
    serializer_class = PersonModelSerializer

    @action(methods=['post'], detail=True)
    def change_age(self, request, pk=None):
        student = self.get_object()
        student.age += 1
        student.save()
        return Response({'status': '%s age changed.' % student.name})

    @action(detail=False)
    def ordering(self, request):
        student = PersonResource.objects.filter(job=1).order_by('modify_time')

        page = self.paginate_queryset(student)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(student, many=True)
        return Response(serializer.data)

注意,我们一直使用的是DefaultRouter,而不是url里面自己定义。。当然上面的这种写法有点烦。。因为Detail=True的是只允许了post方法,可以改成get方法

可以随便打一个错误的url来看一下链接

Django REST Framework API Guide 02

id=1指向的是dandy,此时

Django REST Framework API Guide 02

Django REST Framework API Guide 02

也可以在装饰器里面添加额外的参数,这边就直接copy官方文档的了,随便看看

  @action(methods=['post'], detail=True, permission_classes=[IsAdminOrIsSelf])
    def set_password(self, request, pk=None):
       ...

装饰器默认是get请求们也可以自己添加

    @action(methods=['post', 'delete'], detail=True)
    def unset_password(self, request, pk=None):
       ...

Reversing action URLS

本身正常情况下,在django里面,我们通过url里面的name进行url反转,再特殊一点,如果遇到两个一样的name,会根据命名空间appname来进行区别。而如果使用了viewset视图集,可以抛弃.reverse()方法,使用再次封装的.reverse_action()进行url反转。

    @action(methods=['get', 'put'], detail=True)
    def change_age(self, request, pk=None):
        student = self.get_object()
        student.age += 1
        student.save()
        return Response({
            'status': '%s age changed.' % student.name,
            'url': self.reverse_action('change-age', args=[pk])
        })

注意 反转的basename其实就是action装饰器的函数名,尤其是当函数名跟上面的实例一样,有下划线_的时候,需要转换成-,别掉进坑。。

或者有一种更简单的方法

    @action(methods=['get', 'put'], detail=True)
    def change_age(self, request, pk=None):
        student = self.get_object()
        student.age += 1
        student.save()
        return Response({
            'status': '%s age changed.' % student.name,
            'url': self.reverse_action('change-age', args=[pk]),
            'url1': self.reverse_action(self.change_age.url_name, args=[pk])
        })

Django REST Framework API Guide 02

API Reference

ViewSet

ViewSet继承了APIView,你可以使用APIView的提供的任何标准的属性,比如permission_classesauthentication_classes...

视图集类不能提供任何执行操作,为了使用视图集类,你可以直白地重写类或者定义执行操作。

GenericViewSet

GenericViewSet继承自GenericAPIView,包含get_object(), get_queryset()方法和其他generic view基础行为,默认不包含操作。同样地,为了使用GenericViewSet类,你需要直白的重写类无论是mixin还是自定义执行操作。

ModelViewSet

继承自GenericAPIView,包含各种执行操作,源自各种各样的mixin类。

ModelViewSet提供:.list(), .retrieve(), .create(), .update(), .partial_update()和.destory()

因为是扩展了GenericAPIView,所以有很多属性可以使用,下面的几个示例都不是很重要,直接copy的官方文档

class AccountViewSet(viewsets.ModelViewSet):
    """
    A simple ViewSet for viewing and editing accounts.
    """
    queryset = Account.objects.all()
    serializer_class = AccountSerializer
    permission_classes = [IsAccountAdminOrReadOnly]

或者同样的跟GenericAPIView的其他扩展类一样。

class AccountViewSet(viewsets.ModelViewSet):
    """
    A simple ViewSet for viewing and editing the accounts
    associated with the user.
    """
    serializer_class = AccountSerializer
    permission_classes = [IsAccountAdminOrReadOnly]

    def get_queryset(self):
        return self.request.user.accounts.all()

 

ReadOnlyModelViewSet

继承自GenericAPIView,但是从名字就可以猜到read-only,所以支持.list(), .retrieve()方法

class AccountViewSet(viewsets.ReadOnlyModelViewSet):
    """
    A simple ViewSet for viewing accounts.
    """
    queryset = Account.objects.all()
    serializer_class = AccountSerializer

 

Custome ViewSet base classes

有时你需要提供客制化的视图集类,而ModelViewSet并没有满足,就需要自定制。

from rest_framework import mixins

class CreateListRetrieveViewSet(mixins.CreateModelMixin,
                                mixins.ListModelMixin,
                                mixins.RetrieveModelMixin,
                                viewsets.GenericViewSet):
    """
    A viewset that provides `retrieve`, `create`, and `list` actions.

    To use it, override the class and set the `.queryset` and
    `.serializer_class` attributes.
    """
    pass

 

这里,我们讲GenericView跟ViewSet放在了一起,因为这其中有很多很多相似的地方,如果每一个都看示例,翻源码就很好理解了,如果只是一笔看过,可能会混淆掉其中的内容,当然,如果真正做到看rest framework这一块,相信在这个领域已经不算是小白了,源码随便看看应该是可以的。