@api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk): """ Retrieve, update or delete a code snippet. """ try: snippet = Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = SnippetSerializer(snippet) return Response(serializer.data) elif request.method == 'PUT': serializer = SnippetSerializer(snippet, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT)
在我们的URL中添加可选的格式后缀
为了利用我们的响应不再硬连接到单个内容类型这一事实,我们将API格式后缀添加到API端点。使用格式后缀为我们提供了明确引用给定格式的URL,这意味着我们的API将能够处理诸如http://example.com/api/items/4.json之类的 URL 。
首先format在两个视图中添加关键字参数,如下所示。
def snippet_list(request, format=None):
和
def snippet_detail(request, pk, format=None):
现在snippets/urls.py稍微更新文件,以附加format_suffix_patterns现有URL之外的一组。
from django.urls import path from rest_framework.urlpatterns import format_suffix_patterns from snippets import views urlpatterns = [ path('snippets/', views.snippet_list), path('snippets/<int:pk>', views.snippet_detail), ] urlpatterns = format_suffix_patterns(urlpatterns)
我们不一定需要添加这些额外的url模式,但它为我们提供了一种简单,干净的方式来引用特定的格式。
同样,我们可以使用Content-Type标头控制我们发送的请求的格式。
# POST using form data http --form POST http://127.0.0.1:8000/snippets/ code="print(123)" { "id": 3, "title": "", "code": "print(123)", "linenos": false, "language": "python", "style": "friendly" } # POST using JSON http --json POST http://127.0.0.1:8000/snippets/ code="print(456)" { "id": 4, "title": "", "code": "print(456)", "linenos": false, "language": "python", "style": "friendly" }
如果您--debug在http上面的请求中添加了一个开关,您将能够在请求标头中看到请求类型。
现在,通过访问http://127.0.0.1:8000/snippets/,在Web浏览器中打开API 。
使用基于类的视图重写我们的API
到现在为止还挺好。它看起来与前一种情况非常相似,但我们在不同的HTTP方法之间有了更好的分离。我们还需要更新实例视图views.py。
class SnippetDetail(APIView): """ Retrieve, update or delete a snippet instance. """ def get_object(self, pk): try: return Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: raise Http404 def get(self, request, pk, format=None): snippet = self.get_object(pk) serializer = SnippetSerializer(snippet) return Response(serializer.data) def put(self, request, pk, format=None): snippet = self.get_object(pk) serializer = SnippetSerializer(snippet, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def delete(self, request, pk, format=None): snippet = self.get_object(pk) snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT)
那看起来不错。同样,它现在仍然非常类似于基于函数的视图。
我们还需要snippets/urls.py稍微重构一下,因为我们正在使用基于类的视图。
from django.urls import path from rest_framework.urlpatterns import format_suffix_patterns from snippets import views urlpatterns = [ path('snippets/', views.SnippetList.as_view()), path('snippets/<int:pk>/', views.SnippetDetail.as_view()), ] urlpatterns = format_suffix_patterns(urlpatterns)
好的,我们已经完成了。如果你运行开发服务器,一切都应该像以前一样工作。
使用mixins
使用基于类的视图的一大胜利是它允许我们轻松地编写可重用的行为。
到目前为止,我们一直使用的创建/检索/更新/删除操作对于我们创建的任何模型支持的API视图都非常相似。这些常见行为在REST框架的mixin类中实现。
让我们看一下如何使用mixin类组合视图。这是我们的views.py模块。
from snippets.models import Snippet from snippets.serializers import SnippetSerializer from rest_framework import mixins from rest_framework import generics class 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)
我们将花点时间仔细研究这里发生了什么。我们正在使用的建筑我们的观点GenericAPIView,并在加ListModelMixin和CreateModelMixin。
基类提供核心功能,mixin类提供.list()和.create()操作。然后我们将这些get和post方法明确地绑定到适当的操作上。到目前为止简单的东西。
class SnippetDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs)
很相似。同样,我们正在使用的GenericAPIView类来提供核心功能,并混入增加提供.retrieve(),.update()和.destroy()行动。
使用基于类的通用视图
使用mixin类,我们重写了视图,使用的代码比以前略少,但我们可以更进一步。REST框架提供了一组已经混合的通用视图,我们可以使用它来进一步减少我们的views.py模块。
from snippets.models import Snippet from snippets.serializers import SnippetSerializer from rest_framework import generics class SnippetList(generics.ListCreateAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer
哇,这很简洁。我们免费获得了大量金额,而且我们的代码看起来很好,干净,惯用的Django。
接下来我们将进入本教程的第4部分,在那里我们将了解如何处理API的身份验证和权限。
将代码片段与用户关联起来
现在,如果我们创建了代码段,则无法将创建代码段的用户与代码段实例相关联。用户不是作为序列化表示的一部分发送的,而是传入请求的属性。
我们处理的.perform_create()方法是通过覆盖我们的代码段视图上的方法,允许我们修改实例保存的管理方式,并处理传入请求或请求的URL中隐含的任何信息。
在SnippetList视图类上,添加以下方法:
def perform_create(self, serializer): serializer.save(owner=self.request.user)
create()我们的序列化器的方法现在将传递一个额外的'owner'字段,以及来自请求的验证数据。
视图集中附加action的声明
from rest_framework.decorators import action # 追加action:返回书记的倒叙地0个书籍的信息 @action(methods=['get'],detail=False) def latest(self, request): """ # 追加action 修改图书的阅读数量 @action(methods=['put'],detail=True) def read(self, request, pk): ...
其中:
@action() action装饰器可以接收两个参数: methods: 声明该action对应的请求方式,列表传递 detail: 声明该action的路径是否与单一资源对应,及是否是xxx/<pk>/action方法名/ True 表示路径格式是xxx/<pk>/action方法名/ False 表示路径格式是xxx/action方法名/
基于类的视图
Django的基于阶级的观点是对旧式观点的欢迎。
- Reinout van Rees
REST框架提供了一个APIView类,它是Django View类的子类。
APIView类View通过以下方式与常规类不同:
- 传递给处理程序方法的请求将是REST框架的Request实例,而不是Django的HttpRequest实例。
- 处理程序方法可以返回REST框架Response,而不是Django HttpResponse。该视图将管理内容协商并在响应上设置正确的渲染器。
- 任何APIException例外都将被捕获并调解为适当的响应。
- 将对传入的请求进行身份验证,并在将请求分派给处理程序方法之前运行适当的权限和/或限制检查。
使用APIView该类与使用常规View类几乎相同,像往常一样,传入的请求被分派到适当的处理程序方法,如.get()或.post()。另外,可以在控制API策略的各个方面的类上设置许多属性。
例如:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import authentication, permissions from django.contrib.auth.models import User class ListUsers(APIView): """ View to list all users in the system. * Requires token authentication. * Only admin users are able to access this view. """ authentication_classes = [authentication.TokenAuthentication] permission_classes = [permissions.IsAdminUser] def get(self, request, format=None): """ Return a list of all users. """ usernames = [user.username for user in User.objects.all()] return Response(usernames)
注:完整的方法,属性,并与Django的REST框架的关系APIView,GenericAPIView各种Mixins,并且Viewsets可以初步复杂。除了此处的文档之外,Classy Django REST Framework资源还为Django REST Framework的每个基于类的视图提供了可浏览的引用,包括完整的方法和属性。
Dispatch methods
The following methods are called directly by the view's .dispatch() method. These perform any actions that need to occur before or after calling the handler methods such as .get(), .post(), put(), patch() and .delete().
保存和删除挂钩:
mixin类提供了以下方法,并提供了对象保存或删除行为的轻松覆盖。
- perform_create(self, serializer)- CreateModelMixin保存新对象实例时调用。
- perform_update(self, serializer)- UpdateModelMixin保存现有对象实例时调用。
- perform_destroy(self, instance)- DestroyModelMixin删除对象实例时调用。
这些挂钩对于设置请求中隐含的属性特别有用,但不是请求数据的一部分。例如,您可以根据请求用户或基于URL关键字参数在对象上设置属性。
def perform_create(self, serializer): serializer.save(user=self.request.user)
你也可以使用这些钩子提供额外的验证,通过提高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方法,这将不再可用。
反思ViewSet操作
在调度期间,可以使用以下属性ViewSet。
- basename - 用于创建的URL名称的基础。
- action- 当前操作的名称(例如list,create)。
- detail - 布尔值,指示是否为列表或详细信息视图配置了当前操作。
- suffix- 视图集类型的显示后缀 - 镜像detail属性。
- name - 视图集的显示名称。这个论点是互相排斥的suffix。
- description - 视图集的单个视图的显示说明。
您可以检查这些属性以根据当前操作调整行为。例如,您可以限制除以下list操作之外的所有内容的权限:
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]
ModelViewSet
的ModelViewSet类所继承GenericAPIView,并包括用于各种动作实现方式中,通过在各种混入类的行为混合。
由提供的动作ModelViewSet类是.list(),.retrieve(), .create(),.update(),.partial_update(),和.destroy()。
例
因为ModelViewSet扩展GenericAPIView,您通常需要至少提供queryset和serializer_class属性。例如:
class AccountViewSet(viewsets.ModelViewSet): """ A simple ViewSet for viewing and editing accounts. """ queryset = Account.objects.all() serializer_class = AccountSerializer permission_classes = [IsAccountAdminOrReadOnly]
请注意,您可以使用由提供的任何标准属性或方法覆盖GenericAPIView。例如,要使用ViewSet动态确定它应该操作的查询集,您可以执行以下操作:
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()
但请注意,queryset从您的属性中删除属性后ViewSet,任何关联的路由器都将无法自动派生模型的基本名称,因此您必须将basenamekwarg 指定为路由器注册的一部分。
另请注意,虽然此类默认提供完整的create / list / retrieve / update / destroy操作集,但您可以使用标准权限类来限制可用操作。
自定义ViewSet基类
您可能需要提供ViewSet没有完整操作集的自定义类ModelViewSet,或者以其他方式自定义行为。
例
要创建基础视图集类,提供create,list和retrieve操作,继承GenericViewSet和混入所需的操作:
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
通过创建自己的基ViewSet类,您可以提供可在API中的多个视图集中重用的常见行为。
路由器
资源路由允许您快速声明给定资源控制器的所有公共路由。而不是为索引声明单独的路由......资源丰富的路由在一行代码中声明它们。
- Ruby on Rails文档
某些Web框架(如Rails)提供了自动确定应用程序的URL应如何映射到处理传入请求的逻辑的功能。
REST框架增加了对Django自动URL路由的支持,并为您提供了一种简单,快速和一致的方法,将您的视图逻辑连接到一组URL。
用法
这是一个使用简单URL conf的示例SimpleRouter。
from rest_framework import routers router = routers.SimpleRouter() router.register(r'users', UserViewSet) router.register(r'accounts', AccountViewSet) urlpatterns = router.urls
该方法有两个必需参数register():
- prefix - 用于此组路由的URL前缀。
- viewset - 视图集类。
(可选)您还可以指定其他参数:
- basename - 用于创建的URL名称的基础。如果未设置,将根据queryset视图集的属性自动生成基本名称(如果有)。请注意,如果视图集不包含queryset属性,则必须basename在注册视图集时进行设置。
上面的示例将生成以下URL模式:
- 网址格式:^users/$ 名称:'user-list'
- 网址格式:^users/{pk}/$ 名称:'user-detail'
- 网址格式:^accounts/$ 名称:'account-list'
- 网址格式:^accounts/{pk}/$ 名称:'account-detail'
注意:该basename参数用于指定视图名称模式的初始部分。在上面的例子中,这是user或account部分。
通常,您不需要指定basename参数,但如果您有一个已定义自定义get_queryset方法的视图.queryset集,则视图集可能没有设置属性。如果您尝试注册该视图集,您将看到如下错误:
'basename' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute.
这意味着您需要basename在注册视图集时显式设置参数,因为它无法从模型名称自动确定。
保存实例
如果我们希望能够根据验证的数据返回完整的对象实例,那么我们需要实现一个或两个.create()和.update()方法。例如:
class CommentSerializer(serializers.Serializer): email = serializers.EmailField() content = serializers.CharField(max_length=200) created = serializers.DateTimeField() def create(self, validated_data): return Comment(**validated_data) def update(self, instance, validated_data): instance.email = validated_data.get('email', instance.email) instance.content = validated_data.get('content', instance.content) instance.created = validated_data.get('created', instance.created) return instance
如果您的对象实例对应于Django模型,您还需要确保这些方法将对象保存到数据库。例如,如果Comment是Django模型,则方法可能如下所示:
def create(self, validated_data): return Comment.objects.create(**validated_data) def update(self, instance, validated_data): instance.email = validated_data.get('email', instance.email) instance.content = validated_data.get('content', instance.content) instance.created = validated_data.get('created', instance.created) instance.save() return instance
现在,在反序列化数据时,我们可以.save()根据验证的数据调用返回对象实例。
comment = serializer.save()
调用.save()将创建新实例或更新现有实例,具体取决于实例化序列化程序类时是否传递了现有实例:
# .save() will create a new instance. serializer = CommentSerializer(data=data) # .save() will update the existing `comment` instance. serializer = CommentSerializer(comment, data=data)
无论是.create()和.update()方法是可选的。您可以实现它们中的一个,一个或两个,具体取决于序列化程序类的用例。
while True:
for i in range(1, 10): if i ==4: continue elif i == 8: break
输出结果是1 2 3 5 6 7 8