diff --git a/rest_framework/relations.py b/rest_framework/relations.py index c4271e337..76391ba61 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -514,12 +514,20 @@ class HyperlinkedIdentityField(Field): super(HyperlinkedIdentityField, self).__init__(*args, **kwargs) + def get_lookup_value(self, obj): + """Return the value of the lookup field. Subclasses can override this + for custom behavior which is more complex than returning a pk or other + named attribute of the object. + + """ + return getattr(obj, self.lookup_field) + def field_to_native(self, obj, field_name): request = self.context.get('request', None) format = self.context.get('format', None) view_name = self.view_name or self.parent.opts.view_name - lookup_field = getattr(obj, self.lookup_field) - kwargs = {self.lookup_field: lookup_field} + + kwargs = {self.lookup_field: self.get_lookup_value(obj)} if request is None: warnings.warn("Using `HyperlinkedIdentityField` without including the " diff --git a/rest_framework/tests/hyperlinkedserializers.py b/rest_framework/tests/hyperlinkedserializers.py index 8fc6ba773..5ca8a73c0 100644 --- a/rest_framework/tests/hyperlinkedserializers.py +++ b/rest_framework/tests/hyperlinkedserializers.py @@ -35,6 +35,15 @@ class AlbumSerializer(serializers.ModelSerializer): fields = ('title', 'url') +class UpperCaseTitleField(serializers.HyperlinkedIdentityField): + def get_lookup_value(self, obj): + return obj.title.upper() + + +class UpperCaseAlbumSerializer(AlbumSerializer): + url = UpperCaseTitleField(view_name='album-detail', lookup_field='title') + + class BasicList(generics.ListCreateAPIView): model = BasicModel model_serializer_class = serializers.HyperlinkedModelSerializer @@ -85,6 +94,10 @@ class AlbumDetail(generics.RetrieveAPIView): lookup_field = 'title' +class UpperCaseAlbumDetail(AlbumDetail): + serializer_class = UpperCaseAlbumSerializer + + class OptionalRelationDetail(generics.RetrieveUpdateDestroyAPIView): model = OptionalRelationModel model_serializer_class = serializers.HyperlinkedModelSerializer @@ -201,23 +214,41 @@ class TestHyperlinkedIdentityFieldLookup(TestCase): for title in titles: album = Album(title=title) album.save() - self.detail_view = AlbumDetail.as_view() - self.data = { - 'foo': {'title': 'foo', 'url': 'http://testserver/albums/foo/'}, - 'bar': {'title': 'bar', 'url': 'http://testserver/albums/bar/'}, - 'baz': {'title': 'baz', 'url': 'http://testserver/albums/baz/'} - } def test_lookup_field(self): """ GET requests to AlbumDetail view should return serialized Albums with a url field keyed by `title`. """ + detail_view = AlbumDetail.as_view() + data = { + 'foo': {'title': 'foo', 'url': 'http://testserver/albums/foo/'}, + 'bar': {'title': 'bar', 'url': 'http://testserver/albums/bar/'}, + 'baz': {'title': 'baz', 'url': 'http://testserver/albums/baz/'} + } for album in Album.objects.all(): request = factory.get('/albums/{0}/'.format(album.title)) - response = self.detail_view(request, title=album.title) + response = detail_view(request, title=album.title) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data, self.data[album.title]) + self.assertEqual(response.data, data[album.title]) + + def test_lookup_field_function(self): + """ + GET requests to UpperCaseAlbumDetail which runs with a custom + HyperlinkedIdentityField, tranforming the title to uppercase via an + overridden method. + """ + uppercase_view = UpperCaseAlbumDetail.as_view() + data = { + 'foo': {'title': 'foo', 'url': 'http://testserver/albums/FOO/'}, + 'bar': {'title': 'bar', 'url': 'http://testserver/albums/BAR/'}, + 'baz': {'title': 'baz', 'url': 'http://testserver/albums/BAZ/'} + } + for album in Album.objects.all(): + request = factory.get('/albums/{0}/'.format(album.title)) + response = uppercase_view(request, title=album.title) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data, data[album.title]) class TestCreateWithForeignKeys(TestCase):