本节大纲
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的就不说了,看了应该懂,只看对目前有用的就行
在使用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来看一下链接
id=1指向的是dandy,此时
也可以在装饰器里面添加额外的参数,这边就直接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]) })
API Reference
ViewSet
ViewSet继承了APIView,你可以使用APIView的提供的任何标准的属性,比如permission_classes
,authentication_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这一块,相信在这个领域已经不算是小白了,源码随便看看应该是可以的。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Django REST Framework API Guide 02 - Python技术站