I use a variable in the base of my API url, identical to the setup found in the docs for Django REST Framework:
/api/<brand>/states/<state_pk>/
Everything after the base brand slug is a standard API format, and so I use ModelViewSets to generate all my list and detail views for my objects. Everything in the API is filtered by the brand, so this setup makes sense.
simplified project/urls.py
urlpatterns = patterns(
'',
url(r'^v2/(?P<brand_slug>\w+)/', include(router.urls, namespace='v2')),
)
simplified api/urls.py
router = routers.DefaultRouter()
router.register(r'states', StateViewSet)
router.register(r'cities', CityViewSet)
I also need hypermedia links for all models, and this is where I've run into problems. The REST framework doesn't know how to grab this brand variable and use it to generate correct links. Attempting to solve this problem by following the docs leaves me with 2 setbacks:
What are the missing elements here?
So, I figured it out.
To do this, you need to overwrite the get_serializer_context() method for your ModelViewSet, and send in the variable from your 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
Then, you can just extend all of your ModelViewSets with that class:
class StateViewSet(BrandedViewSet):
queryset = State.objects.all()
serializer_class = StateSerializer
What's nice is that even though you've injected the Serializer with this variable, it's ALSO accessible from the HyperlinkedRelatedField class, via self.context
, and that's how the next part is possible.
The docs were correct in overwriting 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)
Except, you'll notice I'm grabbing the variable from the context I set in part 1. I was unable to get the context from the object as the docs suggested, and this method turned out to be simpler.
The reason it's a mixin is because we need to extend TWO classes for this to work on all the url
hyperlinks and not just the related field 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
Now we can safely extend our serializer and the hyperlinks show the brand variable!
class StateSerializer(BrandedSerializer):
class Meta:
model = State
fields = ('url', 'slug', 'name', 'abbrev', )