[英]How to get a response of multiple objects using rest_framework and Django
[英]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 關鍵字參數filename
、 publication
的詳細 URL:
urlpatterns = [
path(
'documents/<filename:str>/<publication:str>/',
DocumentViewset.as_view({'get': 'retrieve'}),
name='document-detail',
),
...
]
現在,您將在retrieve
方法中的self.kwargs
retrieve
具有相應值的filename
和publication
鍵。
您可以添加路徑轉換器以更好地控制每個 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 中使用filename
和publication
-
作為/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 路徑,包括filename
和publication
。
以下是一個示例,與前面的方法一樣,它將匹配/documents/<filename>-<publication>/
(例如/documents/somename-foobar/
)形式的任何模式,但現在 URL 關鍵字參數將有兩個鍵: filename
和publication
。 您可以根據自己的喜好更改模式/格式。
首先,我們需要使用重寫的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_fields
和lookup_url_kwargs
以根據匹配的模式設置 Regex 關鍵字。
注冊視圖集將像往常一樣:
router = CustomRouter()
router.register('documents', DocumentViewSet)
利用上述路由器的DocumentViewset
可以像下面這樣,覆蓋了get_queryset
和lookup_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.