本节大纲
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这一块,相信在这个领域已经不算是小白了,源码随便看看应该是可以的。