diff options
| -rw-r--r-- | docs/api-guide/relations.md | 6 | ||||
| -rw-r--r-- | rest_framework/relations.py | 21 | ||||
| -rw-r--r-- | rest_framework/tests/hyperlinkedserializers.py | 40 | 
3 files changed, 61 insertions, 6 deletions
| diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index 756e1562..155c89de 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -196,15 +196,13 @@ Would serialize to a representation like this:          'artist': 'Thom Yorke'          'track_listing': 'http://www.example.com/api/track_list/12/',      } -  +  This field is always read-only.  **Arguments**:  * `view_name` - The view name that should be used as the target of the relationship.  **required**. -* `slug_field` - The field on the target that should be used for the lookup. Default is `'slug'`. -* `pk_url_kwarg` - The named url parameter for the pk field lookup. Default is `pk`. -* `slug_url_kwarg` - The named url parameter for the slug field lookup. Default is to use the same value as given for `slug_field`. +* `lookup_field` - The field on the target that should be used for the lookup.  Should correspond to a URL keyword argument on the referenced view. Default is `'pk'`.  * `format` - If using format suffixes, hyperlinked fields will use the same format suffix for the target unless overridden by using the `format` argument.  --- diff --git a/rest_framework/relations.py b/rest_framework/relations.py index fc5054b2..c4b790d4 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -465,10 +465,13 @@ class HyperlinkedIdentityField(Field):      """      Represents the instance, or a property on the instance, using hyperlinking.      """ +    lookup_field = 'pk' +    read_only = True + +    # These are all pending deprecation      pk_url_kwarg = 'pk'      slug_field = 'slug'      slug_url_kwarg = None  # Defaults to same as `slug_field` unless overridden -    read_only = True      def __init__(self, *args, **kwargs):          # TODO: Make view_name mandatory, and have the @@ -477,6 +480,19 @@ class HyperlinkedIdentityField(Field):          # Optionally the format of the target hyperlink may be specified          self.format = kwargs.pop('format', None) +        self.lookup_field = kwargs.pop('lookup_field', self.lookup_field) + +        # These are pending deprecation +        if 'pk_url_kwarg' in kwargs: +            msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.' +            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) +        if 'slug_url_kwarg' in kwargs: +            msg = 'slug_url_kwarg is pending deprecation. Use lookup_field instead.' +            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) +        if 'slug_field' in kwargs: +            msg = 'slug_field is pending deprecation. Use lookup_field instead.' +            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) +          self.slug_field = kwargs.pop('slug_field', self.slug_field)          default_slug_kwarg = self.slug_url_kwarg or self.slug_field          self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg) @@ -488,7 +504,8 @@ class HyperlinkedIdentityField(Field):          request = self.context.get('request', None)          format = self.context.get('format', None)          view_name = self.view_name or self.parent.opts.view_name -        kwargs = {self.pk_url_kwarg: obj.pk} +        lookup_field = getattr(obj, self.lookup_field) +        kwargs = {self.lookup_field: lookup_field}          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 9a61f299..8fc6ba77 100644 --- a/rest_framework/tests/hyperlinkedserializers.py +++ b/rest_framework/tests/hyperlinkedserializers.py @@ -27,6 +27,14 @@ class PhotoSerializer(serializers.Serializer):          return Photo(**attrs) +class AlbumSerializer(serializers.ModelSerializer): +    url = serializers.HyperlinkedIdentityField(view_name='album-detail', lookup_field='title') + +    class Meta: +        model = Album +        fields = ('title', 'url') + +  class BasicList(generics.ListCreateAPIView):      model = BasicModel      model_serializer_class = serializers.HyperlinkedModelSerializer @@ -73,6 +81,8 @@ class PhotoListCreate(generics.ListCreateAPIView):  class AlbumDetail(generics.RetrieveAPIView):      model = Album +    serializer_class = AlbumSerializer +    lookup_field = 'title'  class OptionalRelationDetail(generics.RetrieveUpdateDestroyAPIView): @@ -180,6 +190,36 @@ class TestManyToManyHyperlinkedView(TestCase):          self.assertEqual(response.data, self.data[0]) +class TestHyperlinkedIdentityFieldLookup(TestCase): +    urls = 'rest_framework.tests.hyperlinkedserializers' + +    def setUp(self): +        """ +        Create 3 Album instances. +        """ +        titles = ['foo', 'bar', 'baz'] +        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`. +        """ +        for album in Album.objects.all(): +            request = factory.get('/albums/{0}/'.format(album.title)) +            response = self.detail_view(request, title=album.title) +            self.assertEqual(response.status_code, status.HTTP_200_OK) +            self.assertEqual(response.data, self.data[album.title]) + +  class TestCreateWithForeignKeys(TestCase):      urls = 'rest_framework.tests.hyperlinkedserializers' | 
