如何获得自定义超链接字段以包含额外的 url 变量并适用于所有 ModelViewSets?
How do I get a custom hyperlinked field to include extra url variable and work for all ModelViewSets?
我在 API url 的基础上使用了一个变量,与 the docs 中为 Django REST Framework 找到的设置相同:
/api/<brand>/states/<state_pk>/
基本品牌 slug 之后的所有内容都是标准 API 格式,因此我使用 ModelViewSets 为我的对象生成所有列表和详细视图。 API 中的所有内容均按品牌过滤,因此此设置很有意义。
简化project/urls.py
urlpatterns = patterns(
'',
url(r'^v2/(?P<brand_slug>\w+)/', include(router.urls, namespace='v2')),
)
简化api/urls.py
router = routers.DefaultRouter()
router.register(r'states', StateViewSet)
router.register(r'cities', CityViewSet)
我还需要所有模型的超媒体链接,这就是我 运行 遇到问题的地方。 REST 框架不知道如何获取这个品牌变量并使用它来生成正确的链接。尝试按照文档解决此问题给我带来了 2 个挫折:
- 虽然文档解释了如何覆盖 HyperlinkRelatedField class,但他们从未说明将那个 class 放在哪里以便它与我的序列化器一起工作。
- 没有提及如何将品牌变量从 URL 实际获取到 HyperlinkRelatedField class。
这里缺少什么元素?
所以,我想通了。
将 URL 变量放入序列化程序
为此,您需要覆盖 ModelViewSet 的 get_serializer_context() 方法,并从您的 kwargs
发送变量
class BrandedViewSet(viewsets.ModelViewSet):
def get_serializer_context(self):
context = super().get_serializer_context()
context['brand_slug'] = self.kwargs.get('brand_slug')
return context
然后,您可以使用 class:
扩展所有 ModelViewSet
class StateViewSet(BrandedViewSet):
queryset = State.objects.all()
serializer_class = StateSerializer
很好的是,即使您已将此变量注入到 Serializer 中,也可以通过 self.context
从 HyperlinkedRelatedField class 访问它,这就是下一部分是可能的。
使用额外的 URL 变量构建自定义超媒体 link
文档在覆盖方面是正确的 get_url()
:
class BrandedHyperlinkMixin(object):
def get_url(self, obj, view_name, request, format):
""" Extract brand from url
"""
if hasattr(obj, 'pk') and obj.pk is None:
return None
lookup_value = getattr(obj, self.lookup_field)
kwargs = {self.lookup_url_kwarg: lookup_value}
kwargs['brand_slug'] = self.context['brand_slug']
return reverse(
view_name, kwargs=kwargs, request=request, format=format)
除此之外,您会注意到我正在从我在第 1 部分中设置的上下文中获取变量。我无法按照文档的建议从对象中获取上下文,结果证明此方法更简单。
它是一个 mixin 的原因是因为我们需要扩展两个 classes 才能在所有 url
hyperlinks 而不仅仅是相关字段 hyperlinks.
class BrandedHyperlinkedIdentityField(BrandedHyperlinkMixin,
serializers.HyperlinkedIdentityField):
pass
class BrandedHyperlinkedRelatedField(BrandedHyperlinkMixin,
serializers.HyperlinkedRelatedField):
pass
class BrandedSerializer(serializers.HyperlinkedModelSerializer):
serializer_related_field = BrandedHyperlinkedRelatedField
serializer_url_field = BrandedHyperlinkedIdentityField
现在我们可以安全地扩展我们的序列化程序并且 hyperlinks 显示品牌变量!
class StateSerializer(BrandedSerializer):
class Meta:
model = State
fields = ('url', 'slug', 'name', 'abbrev', )
我在 API url 的基础上使用了一个变量,与 the docs 中为 Django REST Framework 找到的设置相同:
/api/<brand>/states/<state_pk>/
基本品牌 slug 之后的所有内容都是标准 API 格式,因此我使用 ModelViewSets 为我的对象生成所有列表和详细视图。 API 中的所有内容均按品牌过滤,因此此设置很有意义。
简化project/urls.py
urlpatterns = patterns(
'',
url(r'^v2/(?P<brand_slug>\w+)/', include(router.urls, namespace='v2')),
)
简化api/urls.py
router = routers.DefaultRouter()
router.register(r'states', StateViewSet)
router.register(r'cities', CityViewSet)
我还需要所有模型的超媒体链接,这就是我 运行 遇到问题的地方。 REST 框架不知道如何获取这个品牌变量并使用它来生成正确的链接。尝试按照文档解决此问题给我带来了 2 个挫折:
- 虽然文档解释了如何覆盖 HyperlinkRelatedField class,但他们从未说明将那个 class 放在哪里以便它与我的序列化器一起工作。
- 没有提及如何将品牌变量从 URL 实际获取到 HyperlinkRelatedField class。
这里缺少什么元素?
所以,我想通了。
将 URL 变量放入序列化程序
为此,您需要覆盖 ModelViewSet 的 get_serializer_context() 方法,并从您的 kwargs
发送变量class BrandedViewSet(viewsets.ModelViewSet):
def get_serializer_context(self):
context = super().get_serializer_context()
context['brand_slug'] = self.kwargs.get('brand_slug')
return context
然后,您可以使用 class:
扩展所有 ModelViewSetclass StateViewSet(BrandedViewSet):
queryset = State.objects.all()
serializer_class = StateSerializer
很好的是,即使您已将此变量注入到 Serializer 中,也可以通过 self.context
从 HyperlinkedRelatedField class 访问它,这就是下一部分是可能的。
使用额外的 URL 变量构建自定义超媒体 link
文档在覆盖方面是正确的 get_url()
:
class BrandedHyperlinkMixin(object):
def get_url(self, obj, view_name, request, format):
""" Extract brand from url
"""
if hasattr(obj, 'pk') and obj.pk is None:
return None
lookup_value = getattr(obj, self.lookup_field)
kwargs = {self.lookup_url_kwarg: lookup_value}
kwargs['brand_slug'] = self.context['brand_slug']
return reverse(
view_name, kwargs=kwargs, request=request, format=format)
除此之外,您会注意到我正在从我在第 1 部分中设置的上下文中获取变量。我无法按照文档的建议从对象中获取上下文,结果证明此方法更简单。
它是一个 mixin 的原因是因为我们需要扩展两个 classes 才能在所有 url
hyperlinks 而不仅仅是相关字段 hyperlinks.
class BrandedHyperlinkedIdentityField(BrandedHyperlinkMixin,
serializers.HyperlinkedIdentityField):
pass
class BrandedHyperlinkedRelatedField(BrandedHyperlinkMixin,
serializers.HyperlinkedRelatedField):
pass
class BrandedSerializer(serializers.HyperlinkedModelSerializer):
serializer_related_field = BrandedHyperlinkedRelatedField
serializer_url_field = BrandedHyperlinkedIdentityField
现在我们可以安全地扩展我们的序列化程序并且 hyperlinks 显示品牌变量!
class StateSerializer(BrandedSerializer):
class Meta:
model = State
fields = ('url', 'slug', 'name', 'abbrev', )