记录 DRF 学习片段 刷官方 tutorial 的记录,官网地址 django-rest-framework
开始
代码不是全部都贴出,仅做记录(不难看懂)
参考 q1mi老师翻译的文档
示例 models.py
中的 Snippet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from django.db import modelsfrom pygments.lexers import get_all_lexersfrom pygments.styles import get_all_stylesLEXERS = [item for item in get_all_lexers() if item[1 ]] LANGUAGE_CHOICES = sorted ([(item[1 ][0 ], item[0 ]) for item in LEXERS]) STYLE_CHOICES = sorted ((item, item) for item in get_all_styles()) class Snippet (models.Model): created = models.DateTimeField(auto_now_add=True ) title = models.CharField(max_length=100 , blank=True , default='' ) code = models.TextField() linenos = models.BooleanField(default=False ) language = models.CharField(choices=LANGUAGE_CHOICES, default='python' , max_length=100 ) style = models.CharField(choices=STYLE_CHOICES, default='friendly' , max_length=100 ) class Meta : ordering = ('created' ,)
示例的序列化类 serializers.py
定义,写法的进化过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from rest_framework import serializersfrom snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICESclass SnippetSerializer (serializers.Serializer): id = serializers.IntegerField(read_only=True ) title = serializers.CharField(required=False , allow_blank=True , max_length=100 ) code = serializers.CharField(style={'base_template' : 'textarea.html' }) linenos = serializers.BooleanField(required=False ) language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python' ) style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly' ) def create (self, validated_data ): """ 根据提供的验证过的数据创建并返回一个新的`Snippet`实例。 """ return Snippet.objects.create(**validated_data) def update (self, instance, validated_data ): """ 根据提供的验证过的数据更新和返回一个已经存在的`Snippet`实例。 """ instance.title = validated_data.get('title' , instance.title) instance.code = validated_data.get('code' , instance.code) instance.linenos = validated_data.get('linenos' , instance.linenos) instance.language = validated_data.get('language' , instance.language) instance.style = validated_data.get('style' , instance.style) instance.save() return instance class SnippetSerializer (serializers.ModelSerializer): class Meta : model = Snippet fields = ('id' , 'title' , 'code' , 'linenos' , 'language' , 'style' )
序列化和反序列化 基础的序列化过程,Snippet
是个表 model,以及它的序列化类 SnippetSerializer
,先忽略他们定义的内容,看序列化做的事情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from snippets.models import Snippetfrom snippets.serializers import SnippetSerializerfrom rest_framework.renderers import JSONRendererfrom rest_framework.parsers import JSONParsersnippet = Snippet(code='foo = "bar"\n' ) snippet.save() snippet = Snippet(code='print "hello, world"\n' ) snippet.save() serializer = SnippetSerializer(snippet) print (serializer.data) content = JSONRenderer().render(serializer.data) print (content)
反序列化过程,类似 forms 验证,接上以上代码
1 2 3 4 5 6 7 8 import iostream = io.BytesIO(content) data = JSONParser().parse(stream) serializer = SnippetSerializer(data=data) serializer.is_valid() serializer.validated_data serializer.save()
序列化类 介绍 rest_framework.serializers
中创建序列化器的快捷方式
ModelSerializer 类似 django forms 中的 ModelForm
可简单定义需要序列化的字段
默认实现的 create
和 update
方法,用于增加和更新
1 2 3 4 class SnippetSerializer (serializers.ModelSerializer): class Meta : model = Snippet fields = ('id' , 'title' , 'code' , 'linenos' , 'language' , 'style' )
HyperlinkedModelSerializer
可以更好的处理对象实体之间的关系
这个序列化器在有关联字段的时候,展示的不是 ID,而是相关对象的 URL,与 ModelSerializer
有区别
默认不包括 ID 字段
数据含一个url
字段,使用HyperlinkedIdentityField
使用的关联使用 HyperlinkedRelatedField
而不是 PrimaryKeyRelatedField
(它会把关联的对象仅给出 ID 返回)
得到此对象对应的多个关联对象ID,如解析 User
实例的 Snippet
对象:
1 2 3 4 5 6 class UserSerializer (serializers.ModelSerializer): snippets = serializers.PrimaryKeyRelatedField(many=True , queryset=Snippet.objects.all ()) class Meta : model = User fields = ('id' , 'username' , 'snippets' )
得到此对象对应的多个关联对象实体 URL(需要传入 view_name
)
如下,解析 User
实例的 Snippet
对象,snippet-detail
对应 urls.py
中的 name
参数(参考后面关系和超链接 部分)
1 2 3 4 5 6 7 class UserSerializer (serializers.HyperlinkedModelSerializer): snippets = serializers.HyperlinkedRelatedField(many=True , view_name='snippet-detail' , read_only=True ) class Meta : model = User fields = ('id' , 'username' , 'snippets' , 'url' )
请求和响应 请求对象 1 2 request.POST request.data
响应对象 继承了 django 中的 SimpleTemplateResponse
,但未渲染模板,会使用内容协商来确定返回给客户端的正确内容类型
状态码 命名了 HTTP 状态码,便于使用
1 2 # from rest_framework import status return Response(data, status=status.HTTP_201_CREATED)
API 视图包装器 均可限制访问接口的方法,否则抛出 405 Method Not Allowed
错误
APIView 供类视图继承,与 django 的类视图相似,路由中传入 as_view
,并内置dispatch方法,若请求方法未实现将抛出 405
api_view 给函数视图使用的装饰器,默认仅允许 GET 请求,可传入 method 列表
action 给视图集增加动作,如一个 viewSet
默认可能有detail
、list
等方法,若它注册路由时设置 basename=snippets
将自动生成可解析的 snippets-detail
等动作。action可以增加自定义的动作,如下有增加 snippets-highlight
的示例,默认后缀为被装饰的方法名(viewSet 路由注册部分看 viewsets 视图集和 routers )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 from rest_framework.views import APIViewfrom rest_framework.decorators import api_view, actionclass Demo (APIView ): def get (self, request, format =None ): pass def post (self, request, format =None ): pass @api_view(['GET' , 'POST' ] ) def api_func_demo (request ) if request.method == "GET" : pass elif request.method == "POST" : pass class SnippetViewSet (viewsets.ModelViewSet): """ Snippet的常规增删查改、列表视图 还提供了一个额外的`highlight`操作 """ queryset = Snippet.objects.all () serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly) @action(renderer_classes=[renderers.StaticHTMLRenderer], detail=True , methods=['get' ] ) def highlight (self, request, *args, **kwargs ): snippet = self.get_object() return Response(snippet.highlighted) def perform_create (self, serializer ): serializer.save(owner=self.request.user)
类视图 mixins 混合类 在 rest_framework.mixins
中,定义了很多可服用的行为,对应我们接口处理 model 的查询、修改、序列化、反序列化、保存等操作,它们需要和 generics.GenericAPIView
一起继承,搭配使用
CreateModelMixin
用于创建对象,封装了 create
,调用序列化类校验数据、保存等操作
ListModelMixin
用于列出多个对象,封装了 list
方法,从 model 中查询数据,并序列化成数据、分页返回给请求
RetrieveModelMixin
用于查找单个对象示例,封装了 retrieve
方法,返回单个对象已序列化的数据
UpdateModelMixin
用于更新数据,封装了 update
方法,更新单个对象的数据
DestroyModelMixin
用于删除对象,封装了 destroy
方法,查找对象实例进行删除
继承了上面多个类将有多个类的功能,如下将 mixin 中的封装对应 HTTP 方法的处理函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from snippets.models import Snippetfrom snippets.serializers import SnippetSerializerfrom rest_framework import mixinsfrom rest_framework import genericsclass SnippetList (mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): queryset = Snippet.objects.all () serializer_class = SnippetSerializer def get (self, request, *args, **kwargs ): return self.list (request, *args, **kwargs) def post (self, request, *args, **kwargs ): return self.create(request, *args, **kwargs)
generics 通用类 在 rest_framework.generics
中有已经内置混合好的通用视图,用它可以将HTTP方法与 Mixin 封装对应上。
GenericAPIView
是通用视图中最基础的类,继承 views.APIView
,带有序列化、分页、对象过滤等封装
使用通用类视图写接口,代码量将变的很少,逻辑就像配置一样,设置 queryset
和序列化类 就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 from snippets.models import Snippetfrom snippets.serializers import SnippetSerializerfrom rest_framework import genericsclass SnippetList (generics.ListCreateAPIView): queryset = Snippet.objects.all () serializer_class = SnippetSerializer class SnippetDetail (generics.RetrieveUpdateDestroyAPIView): queryset = Snippet.objects.all () serializer_class = SnippetSerializer
基于上面两个视图,修改 urls.py
,增加 URL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from django.contrib import adminfrom django.urls import pathfrom django.conf.urls import includefrom snippets import viewsurlpatterns = [ path('admin/' , admin.site.urls), path('api-auth/' , include('rest_framework.urls' , namespace='rest_framework' )), path('snippets/' , views.SnippetList.as_view()), path('snippets/<int:pk>/' , views.SnippetDetail.as_view()), ]
运行测试(创建用户访问 http://localhost:8000/api-auth/login 再打开 /snippets/ 页面)
viewsets 视图集和 routers
应该是最常用的方式,包括这种方式的路由
ViewSet
和 View
很类似,但它提供的是读取、更新、创建、删除的操作,而不是类里面 get
和 post
这种请求处理的函数。ViewSet
类可以不自己写 URL,使用 rest_framework.routers
中的路由类,可以自动生成 URL
内置了几个常用类,继承自 mixin
和 generics
组成了几种常用的搭配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class ViewSetMixin class ViewSet (ViewSetMixin, views.APIView)class GenericViewSet (ViewSetMixin, generics.GenericAPIView)class ReadOnlyModelViewSet (mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet)class ModelViewSet (mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet)
使用示例,由上面的函数签名可以知道,ReadOnlyModelViewSet
只有读的方法
1 2 3 4 5 6 7 8 from rest_framework import viewsetsclass UserViewSet (viewsets.ReadOnlyModelViewSet): """ 此视图自动提供`list`和`detail`操作。 """ queryset = User.objects.all () serializer_class = UserSerializer
增加路由的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from django.contrib import adminfrom django.urls import pathfrom django.conf.urls import includefrom rest_framework.routers import DefaultRouterfrom snippets import viewsrouter = DefaultRouter() router.register('users' , views.UserViewSet, basename='snippet' ) router.register('snippets' , views.SnippetViewSet, basename='user' ) router2 = DefaultRouter() router.registry.extend(router.registry) urlpatterns = [ path('admin/' , admin.site.urls), path('api-auth/' , include('rest_framework.urls' , namespace='rest_framework' )), path('' , include(router.urls)), ]
权限 内置权限类 在 rest_framework.permissions
中有权限验证相关的内容
通常权限继承自 BasePermission
基类,主要定义了两个方法,均返回 Bool 值,用于区分是否有权限
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class BasePermission (metaclass=BasePermissionMetaclass): """ A base class from which all permission classes should inherit. """ def has_permission (self, request, view ): """ Return `True` if permission is granted, `False` otherwise. """ return True def has_object_permission (self, request, view, obj ): """ Return `True` if permission is granted, `False` otherwise. """ return True
自定义权限只需要继承 BasePermission
在两个方法中做判断,然后在视图中指定即可。
rest_framework.permissions
中内置了以下几种权限:
IsAuthenticated
IsAdminUser
IsAuthenticatedOrReadOnly
DjangoModelPermissions
DjangoModelPermissionsOrAnonReadOnly
DjangoObjectPermissions
自定义权限 例如在 snippets model 增加了 owner 字段关联用户,并且增加 permissions.py
,实现只有 owner 用户可以修改自己的 snippets,其他情况均只读的权限类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from rest_framework import permissionsclass IsOwnerOrReadOnly (permissions.BasePermission): """ 自定义权限只允许对象的所有者编辑它。 """ def has_object_permission (self, request, view, obj ): if request.method in permissions.SAFE_METHODS: return True return obj.owner == request.user
修改视图,导入新增的权限类并设置,这将只有 owner 可以修改
1 2 3 4 5 6 from snippets.permissions import IsOwnerOrReadOnlyclass SnippetDetail (generics.RetrieveUpdateDestroyAPIView): queryset = Snippet.objects.all () serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
关系和超链接 为 API 根路径设置一个入口,snippets/views.py
中增加一个函数视图,使用 reverse 返回完全限定的 URL
1 2 3 4 5 6 7 8 9 10 11 from rest_framework.decorators import api_viewfrom rest_framework.response import Responsefrom rest_framework.reverse import reverse@api_view(['GET' ] ) def api_root (request, format =None ): return Response({ 'users' : reverse('user-list' , request=request, format =format ), 'snippets' : reverse('snippet-list' , request=request, format =format ) })
修改 urls.py
给 URL 加上 name
,用于 reverse 解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from django.contrib import adminfrom django.urls import pathfrom django.conf.urls import includefrom snippets import viewsurlpatterns = [ path('admin/' , admin.site.urls), path('api-auth/' , include('rest_framework.urls' , namespace='rest_framework' )), path('snippets/' , views.SnippetList.as_view(), name='snippet-list' ), path('snippets/<int:pk>/' , views.SnippetDetail.as_view(), name='snippet-detail' ), path('snippets/<int:pk>/highlight/' , views.SnippetHighlight.as_view(), name='snippet-highlight' ), path('users/' , views.UserList.as_view(), name='user-list' ), path('users/<int:pk>/' , views.UserDetail.as_view(), name='user-detail' ), path('' , views.api_root) ]