在 Django DRF 中注册多个路由 - 在 ModelViewSets 或泛型中使用和调用方法

use*_*883 5 python generics django routes django-rest-framework

我想了解如何使用Django DRF进行注册:

  • 两个不同的端点,以及
  • 只有一个端点使用自定义字段

请说明使用 ViewSet 和泛型的区别。

这些是我的尝试。

我来自Flask,发现使用装饰器定义多个端点非常清楚。

Flask - 获取对象列表的示例端点,最后一个对象

选项1

@application.route('/people/', endpoint='people')
def people():
   # return a list of people
   pass

@application.route('/last/', endpoint='last_person')
def last_person():
   # return last person
   pass
Run Code Online (Sandbox Code Playgroud)

选项 2

@application.route('/people/', endpoint='people')
def people():
   field = request.args.get('last', None)
   if field:
      # return last person from list of people
   else:
     # return list of people
Run Code Online (Sandbox Code Playgroud)

我理解DRF的好处可能是一致性和阅读文档,但发现很麻烦,希望更清楚地了解如何使用ModelsViewSets和Generics来查看VSflask的好处。

请帮忙举个例子来获取用户列表和最后一个用户。


Django DRF - 第一种方法(ModelViewSet)

# serializer.py
from rest_framework import serializers
from .models import Person

class PersonSerializer( serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Person
        fields = ('name', 'nickname', 'timestamp')
Run Code Online (Sandbox Code Playgroud)
# views.py
class PersonViewSet( viewsets.ModelViewSet):
    queryset = Person.objects.all().order_by('name')
    serializer_class = PersonSerializer
Run Code Online (Sandbox Code Playgroud)

我知道该类PersonViewSet应该准备好公开获取列表和检索项目的方法,因为ModelViewSethttps : //www.django-rest-framework.org/api-guide/viewsets/#modelviewset

但是如何在注册端点时明确地看到这一点?

例子:

# urls.py
router = routers.DefaultRouter()
router.register(r'people', views.PersonViewSet)

app_name = 'myapi'

urlpatterns = [
   path('', include(router.urls)),
]
Run Code Online (Sandbox Code Playgroud)

当我浏览时http://127.0.0.1:8000/api/,我只能看到一个端点。是的,如果我尝试http://127.0.0.1:8000/api/1/它会自行检索用户,但是如何在上面的代码中读取它?如何从PersonViewSet(类似PersonViewSet.get_last_person())映射方法以指示使用相同的端点获取最后一个条目?

Django DRF - 第二种方法(泛型)

我读过Generics模型公开适合检索单个项目的 API 模型,而不是列表:

https://www.django-rest-framework.org/api-guide/generic-views/#retrieveapiview

我试图添加,在views.py

# I make use of RetrieveAPIView now
class LastPersonView( generics.RetrieveAPIView):
    queryset = Person.objects.all()
    serializer_class = PersonSerializer
Run Code Online (Sandbox Code Playgroud)

并在urls.py

router.register(r'people', views.PersonViewSet)
router.register(r'last-person', views.LastPersonView)
Run Code Online (Sandbox Code Playgroud)

但这会产生错误: AttributeError: type object 'LastPersonView' has no attribute 'get_extra_actions 因为泛型没有 get_extra_actions ,而不是 ViewSet

在第二个示例中,如何在我的路由器中注册两个视图?

Django DRF - 第三次尝试(ModelViewSet with basename)

/sf/answers/2853133601/

我还可以urls.py通过分配基本名称来指示 ViewSet 在我的 中注册不同的端点:

router.register(r'people', views.PersonViewSet)
router.register(r'last-person', views.PersonViewSet,  basename='lastperson')
Run Code Online (Sandbox Code Playgroud)

并使用相同的ModelViewSet

class PersonViewSet( viewsets.ModelViewSet):
    queryset = Person.objects.all().order_by('name')
    serializer_class = PersonSerializer
Run Code Online (Sandbox Code Playgroud)

我明白这种方法的好处是更“简单”,因为查询集总是相同的。

但是我可以注册一个方法来检索最后一个对象,并将该方法映射到我的路由器 ( include(router.urls)) 中吗?


能否提供使用 ModelViewSet、泛型和更明确的方法的示例,这些方法在视图中声明方法并在端点中调用这些方法?

您能否说明哪种方法可能更好:

  • 使用 viewSet 来处理列表,并公开一个方法来从列表中检索一个项目
  • 使用两个单独的视图,一个用于列表,一个用于项目
  • 从一个视图或两个单独的视图映射路由器中的两个不同端点
  • 从一个视图将一个带有字段选项的端点映射到我的路由器

hee*_*ayl 8

我将介绍您采用的每种方法,并将提供一个解决方案来满足您基于该方法的需求。所以让我们滚...


你的第一种方法:

您的第一种方法使用非常典型的 DRF 设置,并为PersonViewSetDRF 本身的各种操作生成路由。

现在,您需要添加一个新的 URL 端点,该端点将解析为 的最后一个对象queryset

Person.objects.all().order_by('name')
Run Code Online (Sandbox Code Playgroud)

据推测,端点应该位于person基本名称下。

我们可以利用action这里的装饰器将特定 URL 上的 HTTP GET 映射到要映射到视图集中的方法,从该方法我们可以将kwargs视图集实例的属性设置pk为最后一个对象的属性,最后发送对retrieve方法本身的请求,例如:

from rest_framework.decorators import action

class PersonViewSet(viewsets.ModelViewSet):
    queryset = Person.objects.all().order_by('name')
    serializer_class = PersonSerializer

    @action(
        methods=['get'],
        detail=False,
        url_path='last',
        url_name='last',
    )
    def get_last_person(self, request, *args, **kwargs):
        last_pk = self.queryset.all().last().pk 
        self.kwargs.update(pk=last_pk)
        return self.retrieve(request, *args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

现在,如果您在/people/last/端点上发出请求,您将获得最后检索到的对象的响应。

请注意,如果您有lookup_url_kwargs并且lookup_field不同于pk,则需要kwargs像您想象的那样使用 set 。此外,您可以将操作retrieve留给自己而不是委托给,retrieve但让我们保持它DRY

FWIW,如果您也想为 PUT/PATCH/DELETE 使用此端点,则需要添加方法action并将它们委托给基于方法名称的相应操作。


你的第二种方法:

视图不是视图集(查看 :rest_framework.views.View和的基类rest_framework.viewsets.ViewSet);DRF 路由器为视图集创建端点,而不是视图。有很多方法可以将视图转换为视图集,基本上只是通过继承自viewsets.GenericViewSet- 它转而继承自viewsets.ViewSetMixingenerics.GenericAPIView

viewsets.ViewSetMixin通过在classmethod 中提供所有必要的 action-method 映射,具有将视图转换为视图集的实际魔力as_view

为了使这种方法起作用,我们需要定义一个retrieve方法来发送来自序列化程序的响应:

from rest_framework import generics
from rest_framework.response import Response

class LastPersonView(generics.RetrieveAPIView):

    serializer_class = PersonSerializer
    queryset = Person.objects.all().order_by('name')

    def retrieve(self, request, *args, **kwargs):
        instance = self.queryset.all().last()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)
Run Code Online (Sandbox Code Playgroud)

generics.RetrieveAPIViewget方法将请求委托给retrieve方法,因此我们在retrieve此处进行了覆盖。

现在,我们需要将路由定义为常规端点,而不是在 DRF 路由器中:

urlpatterns = router.urls + [
    path('last-person/', LastPersonView.as_view()),
]
Run Code Online (Sandbox Code Playgroud)

你的第三种方法:

您已经为同一个视图集注册了两个不同的前缀(在这里再次使用视图不起作用,必须是一个视图集),因此两个不同的 URL 映射集具有所有相同的 CRUD 操作。根据您的期望,我认为这不是您想要的,所以我不会进一步讨论这种方法,但我认为您从上面得到了为什么它不相关的想法。


现在,如果我必须选择,我更喜欢第一种方法,因为所有内容都在相同的视图集、前缀和基名下,并且您不需要与 URLConf 混在一起。

我希望以上能澄清一两件事。