【问题标题】:Multiple lookup_fields for django rest frameworkdjango rest框架的多个lookup_fields
【发布时间】:2016-07-19 14:28:24
【问题描述】:

我有多个 API 过去使用 id 作为查找字段:

/api/organization/10

我有一个使用这些 api 的前端。

我正在构建一个新界面,出于某些原因,我想使用 slug 代替 id:

/api/organization/my-orga

API 是使用 Django Rest Framework 构建的。除了查找字段的变化外,api 的行为应该保持不变。

有没有一种解决方案可以让我的 API 同时使用 slugpk ?这两条路径应该给它们相同的结果:

/api/organization/10
/api/organization/my-orga

这是我的 API 定义:

# urls.py
router = DefaultRouter()
router.register(r'organization', Organization)
urlpatterns = router.urls

#view.py
class Organization(viewsets.ModelViewSet):
    queryset = OrganisationGroup.objects.all()
    serializer_class = OrganizationSerializer

# serializer.py
class OrganizationSerializer(PermissionsSerializer):
    class Meta:
        model = Organization

【问题讨论】:

标签: python django rest django-rest-framework


【解决方案1】:

试试这个

from django.db.models import Q
import operator
from functools import reduce
from django.shortcuts import get_object_or_404

class MultipleFieldLookupMixin(object):
    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:
            filter[field] = self.kwargs[field]
        q = reduce(operator.or_, (Q(x) for x in filter.items()))
        return get_object_or_404(queryset, q)

然后在视图中

class Organization(MultipleFieldLookupMixin, viewsets.ModelViewSet):
    queryset = OrganisationGroup.objects.all()
    serializer_class = OrganizationSerializer
    lookup_fields = ('pk', 'another field')

【讨论】:

  • 我认为它不能回答问题,因为在这种情况下,URL 必须同时包含 pk 和其他字段。
  • 使用MultiFieldLookupMixin需要如何修改URL?我正在使用路由器来定义我的 URL。任何人都可以为此分享代码吗?谢谢。
  • 要将其与路由器一起使用,您必须将lookup_field 留在视图集类中,然后将此行filter[field] = self.kwargs[field] 更改为此filter[field] = self.kwargs[self.lookup_field]
【解决方案2】:

我通过覆盖retrieve 方法解决了类似的问题,并针对任何模式检查pk 字段的值。例如,如果它仅由数字组成。

def retrieve(self, request, *args, **kwargs):
    if kwargs['pk'].isdigit():
        return super(Organization, self).retrieve(request, *args, **kwargs)
    else:
        # get and return object however you want here.

【讨论】:

  • 这应该是最好的解决方案
  • 好吧,这个灵魂真的很干净,但也很短视 - 如果 slug 是一个数字,它将无法正常工作
  • Afaik,这个解决方案完全绕过了检查对象权限的 GenericAPIView.get_object。
  • 使用相同逻辑更新解决方案:``` def retrieve(self, request, *args, **kwargs): if not kwargs['pk'].isdigit(): self.lookup_url_kwarg = 'pk' self.lookup_field = 'slug' return super().retrieve(request, *args, **kwargs) ``
【解决方案3】:

我知道你很久以前问过这个问题,但这是我从所有答案中得到的完整解决方案,同时考虑了视图和网址:

把它放在你的views.py中:(通过drf稍作修改)

class MultipleFieldLookupMixin(object):

def get_object(self):
    queryset = self.get_queryset()             
    queryset = self.filter_queryset(queryset)  
    filter = {}
    for field in self.lookup_fields:
        if self.kwargs.get(field, None):  
            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 继承你的视图,并添加你想要的字段到 lookup_fields。像这样:

class YourDetailView(MultipleFieldLookupMixin, RetrieveUpdateAPIView):
    ...
    lookup_fields = ['pk', 'slug','code']

在 urls.py 中:

re_path(r'^organization/(?P<pk>[0-9]+)/$',
        YourDetailView),
re_path(r'^organization/(?P<slug>[-a-zA-Z0-9_]+)/$',
        YourDetailView),
re_path(r'^organization/sth_else/(?P<code>[0-9]+)/$',
        YourDetailView),

【讨论】:

  • 很好的答案。再补充一点,如果您使用的是较新的 django,则可以使用路径转换器而不是正则表达式。如果您使用 ViewSets 而不是 Views,则必须指定操作映射。对代码格式中的注释限制表示歉意:path( 'releases/&lt;str:lookup_1&gt;/&lt;int:lookup_2&gt;/', YourDetailViewset.as_view( { 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' } ),
  • @SuperFunkyMonkey 如果我有一些不需要的操作(例如列表),我应该为列表操作添加另一个路径吗?我不能再使用 router.register 了吗?
【解决方案4】:

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)
        filter = {}
        for field in self.lookup_fields:
            if self.kwargs[field]:  # Ignore empty fields.
                filter[field] = self.kwargs[field]
        return get_object_or_404(queryset, **filter)  # Lookup the object


class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_fields = ('account', 'username')

【讨论】:

  • 嗨@D。王,试过这个解决方案,但似乎路由有问题。出现错误 ParameterError: {'account': 'Unknown parameter.', 'username': 'Unknown parameter.', 'id': 'This parameter is required.'}。视图仍然需要默认 id
【解决方案5】:

我认为最好的方法是覆盖get_object(self) 方法

class Organization(generics.RetrieveAPIView):
    serializer_class = OrganizationSerializer
    queryset = Organization.objects.all()
    multiple_lookup_fields = ['pk', 'slug']

    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

【讨论】:

    【解决方案6】:

    我认为根本的答案是,这不是好的 REST/API 设计,也不是 DRF 能够实现的。

    【讨论】:

      【解决方案7】:

      官方文档在https://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins 上有一个示例

      另外,您需要修改urls.py,为同一视图添加一条新路由,但使用新的字段名称。

      【讨论】:

        【解决方案8】:

        这里已经有很多答案,但没有一个提供完整的描述,包括 mixin、view 和 url 配置。这个答案可以。

        这是效果最好的 mixin,它从 https://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins 稍作修改,不会在不存在的字段上出错。

        class MultipleFieldLookupMixin:
            """
            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.
        
            Source: https://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins
            Modified to not error out for not providing all fields in the url.
            """
        
            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.get(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,否则get_object方法不会被覆盖:

        class RudAPIView(MultipleFieldLookupMixin, generics.RetrieveUpdateDestroyAPIView):
            ...
            lookup_fields = ['pk', 'other_field']
        

        现在,对于网址,我们使用默认转换器。重要的是 int 首先出现,因为它实际上会检查它是否是 int,如果不是则回退到 str。如果您有更复杂的字段,则需要使用正则表达式。

        path('efficiency/<int:pk>/', views.RudAPIView.as_view(), name='something-rud'),
        path('efficiency/<string:other_field>/', views.RudAPIView.as_view(), name='something-rud'),
        

        【讨论】:

          猜你喜欢
          • 2016-05-30
          • 1970-01-01
          • 2018-01-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-10-15
          • 2019-09-18
          • 2017-01-07
          相关资源
          最近更新 更多