我想了解如何使用 Django DRF 来注册:
- 两个不同的端点,以及
- 只有一个端点使用自定义字段
请显示使用视图集和泛型的区别。
这些是我的尝试。
我来自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
选项 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
我了解 DRF 的好处可能是一致性并阅读了文档, 但发现很麻烦,并希望更清楚地了解如何利用 ModelsViewSet 和泛型来查看 VS 烧瓶的好处。
请帮助获取用户列表和最后一个用户的示例。
>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')
# views.py
class PersonViewSet( viewsets.ModelViewSet):
queryset = Person.objects.all().order_by('name')
serializer_class = PersonSerializer
我了解类PersonViewSet
应该准备好公开方法以获取列表以及检索项目,因为ModelViewSet
: https://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)),
]
当我浏览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
在urls.py
:
router.register(r'people', views.PersonViewSet)
router.register(r'last-person', views.LastPersonView)
但这会产生错误: 属性错误: 类型对象 'LastPersonView' 没有属性 'get_extra_actions 因为泛型没有get_extra_actions,而是视图集
如何在我的路由器中注册两个视图,在第二个示例中?
Django DRF - 第三次尝试(具有基本名称的模型视图集)
https://stackoverflow.com/a/40759051/305883
我还可以通过分配一个基本名称来指示视图集在我的urls.py
中注册不同的端点:
router.register(r'people', views.PersonViewSet)
router.register(r'last-person', views.PersonViewSet, basename='lastperson')
并使用相同的ModelViewSet
:
class PersonViewSet( viewsets.ModelViewSet):
queryset = Person.objects.all().order_by('name')
serializer_class = PersonSerializer
我知道这种方法的好处更"简单",因为查询集总是相同的。
但是我可以注册一个方法来检索最后一个对象,并将该方法映射到我的路由器(include(router.urls)
)中吗?
是否可以提供使用ModelViewSets,泛型和更显式方法的示例,该方法在视图中声明方法并在端点中调用这些方法?
您能否说明哪种方法可能更好:
- 使用 viewSet 处理列表,并公开从列表中检索项目的方法
- 使用两个单独的视图,一个用于列表,一个用于项目
- 从一个视图或两个单独的视图映射路由器中的两个不同端点
- 将一个带有字段选项的端点映射到我的路由器,从一个视图
我将介绍您采取的每种方法,并提供一种解决方案来满足您基于该方法的需求。所以让我们滚...
您的第一种方法:
您的第一种方法使用非常典型的 DRF 设置,其中为 DRF 本身PersonViewSet
的各种操作生成路由。
现在,您需要添加一个新的 URL 端点,该端点将解析为queryset
的最后一个对象:
Person.objects.all().order_by('name')
据推测,终结点应驻留在person
基名称下。
我们可以利用这里的action
装饰器将 HTTP GET 映射到要映射到视图集中的方法的特定 URL 上,并且从该方法我们可以将视图集实例的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)
现在,如果您在/people/last/
端点上请求,您将获得最后一个检索到的对象的响应。
请注意,如果您有与pk
不同的lookup_url_kwargs
和lookup_field
,则需要按照您的想象在kwargs
中使用设置它们。此外,您可以将操作retrieve
自己而不是委托给retrieve
但让我们保持干燥。
FWIW,如果你也想让这个端点用于 PUT/PATCH/DELETE,你需要在action
中添加方法,并根据方法名称将它们委托给相应的操作。
您的第二种方法:
视图不是视图集(查看两者的基类:rest_framework.views.View
和rest_framework.viewsets.ViewSet
);DRF 路由器为视图集创建端点,而不是视图。有一些方法可以将视图转换为视图集,基本上只需从viewsets.GenericViewSet
继承 - 它从viewsets.ViewSetMixin
继承并generics.GenericAPIView
继承。
viewsets.ViewSetMixin
具有通过在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)
generics.RetrieveAPIView
的get
方法将请求委托给retrieve
方法,因此我们在这里覆盖了retrieve
。
现在,我们需要将路由定义为常规端点,而不是在 DRF 路由器中:
urlpatterns = router.urls + [
path('last-person/', LastPersonView.as_view()),
]
您的第三种方法:
您已为同一视图集注册了两个不同的前缀(同样,在此处使用 view 不起作用,必须是视图集),因此具有所有相同 CRUD 操作的两组不同的 URL 映射。我不认为这不是你想要的,所以我不会进一步讨论这种方法,但我认为你从上面得到了为什么它不相关的想法。
现在,如果我必须选择,我更喜欢第一种方法,因为所有内容都在相同的视图集、前缀和基本名称下,并且您不需要使用 URLConf。
我希望以上澄清一两件事。
首先,您可以通过ViewSet
中的@action
装饰器完成在烧瓶中创建list_route
或detail_route
的工作。路由器将根据函数的名称为您创建端点。例如
class PersonViewSet( viewsets.ModelViewSet):
queryset = Person.objects.all().order_by('name')
serializer_class = PersonSerializer
@action(detail=True, methods=['post']) # detail route
def set_password(self, request, pk=None):
# your code
@action(detail=False) # list route
def last_person(self, request):
# your code
此代码将创建一个people/last_person
和一个people/<id>/set_password
有关路由器如何创建路径的详细信息:https://www.django-rest-framework.org/api-guide/routers/#routing-for-extra-actions 使用此额外操作,您可以为不同的操作使用不同的序列化程序,从而公开不同的字段。
这里有一些关于您的疑问的澄清:
ModelViewSet
只是一个"快捷方式",当您想要公开模型的完整 CRUD 接口时可以使用它。它基于GenericsViewSet
和其他 mixin,您有机会编写很少的代码来使其正常工作。如果没有模型,则应使用ViewSet
来构建 API。如果您只想公开某些操作,例如,仅列出或检索,则可以使用 mixins 构建您的 API,在本例中为mixins.ListModelMixin
和mixins.RetrieveModelMixin
以及GenericViewSet
相反,您使用的泛型只是
View
,而不是ViewSet
。这意味着您无法通过路由器注册它们。你应该把它们直接添加到 Django URL 中,就像任何其他视图一样。根据 drf 文档:Django REST 框架允许你将一组相关视图的逻辑组合到一个类中,称为 ViewSet。
文档到视图集 https://www.django-rest-framework.org/api-guide/viewsets/,文档到视图 https://www.django-rest-framework.org/api-guide/views/
basename
不会阻止您编写两个不同的ViewSet
。如果您使用相同的ViewSet
则在两个不同的终端节点上将具有相同的功能