簡體   English   中英

使用多個關鍵字對象引用的 Django Rest Framework 對象

[英]Django Rest Framework objects referenced using multiple keyword objects

我有一個對兩個字段有唯一約束的模型:

class Document(models.Model):
    filename = models.CharField(max_length=255)
    publication = models.CharField(max_length=8)

    class Meta:
        constraints = [
                models.UniqueConstraint(
                    fields=['filename', 'publication'], name='document_key')

根據DRF GenericAPIView get_object 方法中的文檔:

    """
    Returns the object the view is displaying.
    You may want to override this if you need to provide non-standard
    queryset lookups.  Eg if objects are referenced using multiple
    keyword arguments in the url conf.
    """

使用多個關鍵字參數進行引用正是我想要做的。 我已經開始覆蓋 get_object 方法

class DocumentViewset(viewsets.ModelViewSet):
    serializer_class = serializers.ActSerializer 
    lookup_fields = ('filename', 'publication')

    def get_object(self):
        """As per the example in the DRF docstring itself,
        objects are referenced using multiple keyword arguments
        in the URL conf. Therefore, we need to override. 
        """
        queryset = self.filter_queryset(self.get_queryset())
        lookup_url_kwargs = self.lookup_fields

        print(lookup_url_kwargs)
        print(self.kwargs)

這給了我:

('filename', 'publication')
{'pk': '17'}

您可以看到問題是我的 lookup_url_kwargs 不會在 self.kwargs 中(在下一行進行了驗證)。 如果設置了 'lookup_url_kwarg',那么 self.kwargs 就是那個。 但是沒有它,self.kwargs 默認為 'pk'。 如何覆蓋此行為以便在 URL 中需要兩個字段?? 謝謝你。

)

有幾種方法可以獲得所需的行為,復雜性依次增加:


第一種方式:

不要像往常一樣使用 DRF 路由器並手動定義 URL 端點。 此外,請參考正確的映射,例如使用 URL 關鍵字參數filenamepublication的詳細 URL:

urlpatterns = [
    path(
        'documents/<filename:str>/<publication:str>/',
        DocumentViewset.as_view({'get': 'retrieve'}),
        name='document-detail',
    ),
    ...
]

現在,您將在retrieve方法中的self.kwargs retrieve具有相應值的filenamepublication鍵。

您可以添加路徑轉換器以更好地控制每個 URL 關鍵字匹配所允許的模式。 例如,如果您用-而不是/ (因為/通常表示子資源)分隔它們,您的 URL 在邏輯上可能會更好看。 在這里,我們創建了兩個轉換器來獲取破折號 ( - ) 前后的部分:

class BaseDashConverter:  
    def to_python(self, value):
        return value

    def to_url(self, value):
        return value

class BeforeDashConverter(BaseDashConverter):
    regex = '[^-]+'

class AfterDashConverter(BaseDashConverter):
    regex = '[^/]+'

現在,是時候注冊這兩個了:

register_converter(BeforeDashConverter, 'before-dash')
register_converter(AfterDashConverter, 'after-dash')

然后在urlpatterns ,您可以執行以下操作:

urlpatterns = [
    path(
        'documents/<filename:before-dash>-<publication:after-dash>/',
        DocumentViewset.as_view({'get': 'retrieve'}),
        name='document-detail',
    ),
    ...
]

您還可以直接將re_path與 Regex 一起使用,而不是創建轉換器並使用path

urlpatterns = [
    re_path(
        'documents/(?P<filename>[^-]+)-(?P<publication>[^/]+)/',
        DocumentViewset.as_view({'get': 'retrieve'}),
        name='document-detail',
    ),
    ...
]

FWIW,您需要為所有方法-操作映射添加 URL 路徑,就像我為get - retrieve所做的那樣。


第二種方式:

假設lookup_fields是您的自定義屬性(視圖集實際上使用由屬性lookup_field引用的單個字段),您可以將lookup_kwargs為類似combined名稱,並在 URL 中使用filenamepublication -作為/documents/<filename>-<publication>/例如/documents/somename-foobar/ DRF Router的詳細信息查找匹配一個或多個字符,除了. /在前綴之后,所以這將被匹配。

如果這樣做,則在get_object您可以添加自定義邏輯,例如:

class DocumentViewset(viewsets.ModelViewSet):
    serializer_class = serializers.ActSerializer 

    lookup_fields = ('filename', 'publication')
    lookup_url_kwarg = 'combined'

    def get_object(self):
        queryset = self.filter_queryset(self.get_queryset())

        lookup_url_kwarg = self.lookup_url_kwarg

        assert lookup_url_kwarg in self.kwargs, (
            'Expected view %s to be called with a URL keyword argument '
            'named "%s". Fix your URL conf, or set the `.lookup_field` '
            'attribute on the view correctly.' %
            (self.__class__.__name__, lookup_url_kwarg)
        )

        combined = self.kwargs[lookup_url_kwarg]
        filter_kwargs = dict(zip(self.lookup_fields, combined.partition('-')[::2]))
        obj = get_object_or_404(queryset, **filter_kwargs)

        self.check_object_permissions(self.request, obj)

        return obj

第三種方式:

覆蓋 DRF 的Router並在get_lookup_regex方法中添加自定義 Regex 以匹配 URL 路徑,包括filenamepublication

以下是一個示例,與前面的方法一樣,它將匹配/documents/<filename>-<publication>/ (例如/documents/somename-foobar/ )形式的任何模式,但現在 URL 關鍵字參數將有兩個鍵: filenamepublication 您可以根據自己的喜好更改模式/格式。

首先,我們需要使用重寫的get_lookup_regex定義自定義路由器:

from rest_framework.routers import DefaultRouter

class CustomRouter(DefaultRouter):
    def get_lookup_regex(self, viewset, lookup_prefix=''):
        lookup_fields = getattr(viewset, 'lookup_fields', ('filename', 'publication'))
        lookup_url_kwargs = getattr(viewset, 'lookup_url_kwargs', lookup_fields)
        return (
            rf'(?P<{lookup_prefix}{lookup_url_kwargs[0]}>[^-]+)-'
            rf'(?P<{lookup_prefix}{lookup_url_kwargs[1]}>[^/.]+)'
        )

因此,這將檢查ViewSet類中的lookup_fieldslookup_url_kwargs以根據匹配的模式設置 Regex 關鍵字。

注冊視圖集將像往常一樣:

router = CustomRouter()
router.register('documents', DocumentViewSet)

利用上述路由器的DocumentViewset可以像下面這樣,覆蓋了get_querysetlookup_fields / lookup_url_kwargs集:

class DocumentViewset(viewsets.ModelViewSet):
    serializer_class = serializers.ActSerializer 

    lookup_fields = ('filename', 'publication')
    lookup_url_kwargs = ('filename', 'publication')

    def get_object(self):
        queryset = self.filter_queryset(self.get_queryset())

        lookup_url_kwargs = self.lookup_url_kwargs or self.lookup_fields

        assert all(
            lookup_kwarg in self.kwargs
            for lookup_kwarg in lookup_url_kwargs
        ), (
            'Expected view %s to be called with URL keyword arguments '
            'named "%s". Fix your URL conf, or set the `.lookup_fields` '
            'attribute on the view correctly.' %
            (self.__class__.__name__, ','.join(lookup_url_kwargs))
        )

        field_values = (self.kwargs[lookup_kwarg] for lookup_kwarg in lookup_url_kwargs)
        filter_kwargs = dict(zip(self.lookup_fields, field_values))
        obj = get_object_or_404(queryset, **filter_kwargs)

        # May raise a permission denied
        self.check_object_permissions(self.request, obj)

        return obj

以上每一項都會讓您獲得所需的行為。 選擇最適合您的那一款。

注意:使用-作為示例中的分隔符,您可以選擇自己的分隔符。 但是如果你想使用/. 作為分隔符,您需要使用第一種或第三種方式,因為第二種方式使用DefaultRouter.get_lookup_regex在其中模式被數學計算到下一個/. .

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM