I have a model that more or less looks like this:
class Starship(models.Model):
id = models.UUIDField(default=uuid4, editable=False, primary_key=True)
name = models.CharField(max_length=128)
hull_no = models.CharField(max_length=12, unique=True)
I have an unremarkable StarshipDetailSerialiser
and StarshipListSerialiser
(I want to eventually show different fields but for now they're identical), both subclassing serializers.ModelSerializer
. It has a HyperlinkedIdentityField
that refers back to the (UU)ID, using a home-brew class very similar to the original HyperlinkedIdentityField
but with capability to normalise and handle UUIDs:
class StarshipListSerializer(HyperlinkedModelSerializer):
uri = UUIDHyperlinkedIdentityField(view_name='starships:starship-detail', format='html')
class Meta:
model = Starship
fields = ('uri', 'name', 'hull_no')
Finally, there's a list view (a ListAPIView
) and a detail view that looks like this:
class StarshipDetail(APIView):
"""
Retrieves a single starship by UUID primary key.
"""
def get_object(self, pk):
try:
return Starship.objects.get(pk=pk)
except Starship.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
vessel = self.get_object(pk)
serializer = StarshipDetailSerialiser(vessel, context={'request': request})
return Response(serializer.data)
The detail view's URL schema is currently invoking the view based on the UUID:
...
url(r'vessels/id/(?P<pk>[0-9A-Fa-f\-]+)/$', StarshipDetail.as_view(), name='starship-detail'),
...
I now want users to be able to navigate and find the same vessel not just by UUID but also by their hull number, so that e.g. vessels/id/abcde1345...and so on.../
and vessels/hull/H1025/
would be able to resolve to the same entity. And ideally, regardless of whether one arrived at the detail view from ID or hull number, the serialiser, which is used with slight alterations in lists as well, should be able to have the ID hyperlinked to the ID-based link and the hull hyperlinked to a hull number based link (vessels/hull/H1025/
). Is this at all possible? And if so, how would I go about it?
# in urls.py
urlpatterns = [
...,
url(r'vessels/id/(?P<pk>[0-9A-Fa-f\-]+)/$', StarshipDetail.as_view(), name='starship-detail-pk'),
url(r'vessels/hull/(?P<hull_no>[0-9A-Za-z]+)/$', StarshipDetail.as_view(), name='starship-detail-hull'),
]
Tweak the regex for hull_no
as you want. Note that I gave distinct names to each route, starship-detail-pk
and starship-detail-hull
.
# in serializers.py
class StarshipListSerialiser(HyperlinkedModelSerializer):
uri = UUIDHyperlinkedIdentityField(view_name='starship-detail-pk', format='html')
hull_no = UUIDHyperlinkedIdentityField(view_name='starship-detail-hull', format='html', lookup_field='hull_no')
class Meta:
model = Starship
fields = ('uri', 'name', 'hull_no')
# in serializers.py
from django.shortcuts import get_object_or_404
from rest_framework.views import APIView, Response
from starwars.serializers import StarshipDetailSerialiser
from starwars.models import Starship
class StarshipDetail(APIView):
def get(self, request, pk=None, hull_no=None, format=None):
lookup = {'hull_no': hull_no} if pk is None else {'pk': pk}
vessel = get_object_or_404(Starship, **lookup)
serializer = StarshipDetailSerialiser(vessel, context={'request': request})
return Response(serializer.data)
That should be enough to get you going with the detail view:
As a final note, you should be aware that it's not RESTful for the same resource to be available at two different URLs like this. Perhaps, as an alternate design decision, you might like to consider just defining the "one true route" for a resource, and adding in a "convenience" redirect from the other locator to the canonical URL.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With