diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 2a10e9af5..9462f2c9d 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -319,6 +319,16 @@ class HyperlinkedRelatedField(RelatedField): view_name = self.view_name request = self.context.get('request', None) format = self.format or self.context.get('format', None) + pk = getattr(obj, 'pk', None) + if pk is None: + return + kwargs = {self.pk_url_kwarg: pk} + new_kwargs = None + + visit = self.context.get('view', None) + if visit: + new_kwargs = kwargs.copy() + new_kwargs.update(visit.kwargs) if request is None: warnings.warn("Using `HyperlinkedRelatedField` without including the " @@ -326,15 +336,16 @@ class HyperlinkedRelatedField(RelatedField): "Add `context={'request': request}` when instantiating the serializer.", PendingDeprecationWarning, stacklevel=4) - pk = getattr(obj, 'pk', None) - if pk is None: - return - kwargs = {self.pk_url_kwarg: pk} try: return reverse(view_name, kwargs=kwargs, request=request, format=format) except NoReverseMatch: pass + try: + return reverse(view_name, kwargs=new_kwargs, request=request, format=format) + except NoReverseMatch: + pass + slug = getattr(obj, self.slug_field, None) if not slug: @@ -434,6 +445,12 @@ class HyperlinkedIdentityField(Field): format = self.context.get('format', None) view_name = self.view_name or self.parent.opts.view_name kwargs = {self.pk_url_kwarg: obj.pk} + new_kwargs = None + + visit = self.context.get('view', None) + if visit: + new_kwargs = kwargs.copy() + new_kwargs.update(visit.kwargs) if request is None: warnings.warn("Using `HyperlinkedIdentityField` without including the " @@ -458,6 +475,11 @@ class HyperlinkedIdentityField(Field): except NoReverseMatch: pass + try: + return reverse(view_name, kwargs=new_kwargs, request=request, format=format) + except NoReverseMatch: + pass + slug = getattr(obj, self.slug_field, None) if not slug: diff --git a/rest_framework/tests/hyperlinkedserializers.py b/rest_framework/tests/hyperlinkedserializers.py index 9a61f299a..cb479c3e9 100644 --- a/rest_framework/tests/hyperlinkedserializers.py +++ b/rest_framework/tests/hyperlinkedserializers.py @@ -4,7 +4,7 @@ from django.test import TestCase from django.test.client import RequestFactory from rest_framework import generics, status, serializers from rest_framework.compat import patterns, url -from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel +from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel, ExtraKwargModel factory = RequestFactory() @@ -80,6 +80,14 @@ class OptionalRelationDetail(generics.RetrieveUpdateDestroyAPIView): model_serializer_class = serializers.HyperlinkedModelSerializer +class ExtraKwargDetailSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = ExtraKwargModel + +class ExtraKwargDetail(generics.RetrieveUpdateDestroyAPIView): + model = ExtraKwargModel + serializer_class = ExtraKwargDetailSerializer + urlpatterns = patterns('', url(r'^basic/$', BasicList.as_view(), name='basicmodel-list'), url(r'^basic/(?P\d+)/$', BasicDetail.as_view(), name='basicmodel-detail'), @@ -92,6 +100,7 @@ urlpatterns = patterns('', url(r'^albums/(?P\w[\w-]*)/$', AlbumDetail.as_view(), name='album-detail'), url(r'^photos/$', PhotoListCreate.as_view(), name='photo-list'), url(r'^optionalrelation/(?P<pk>\d+)/$', OptionalRelationDetail.as_view(), name='optionalrelationmodel-detail'), + url(r'^root/(?P<extra_kwarg>\d+)/extrakwarg/(?P<pk>\d+)/$', ExtraKwargDetail.as_view(), name='extrakwargmodel-detail'), ) @@ -261,3 +270,23 @@ class TestOptionalRelationHyperlinkedView(TestCase): data=json.dumps(self.data), content_type='application/json') self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class TestNestedRelationHyperlinkedView(TestCase): + urls = 'rest_framework.tests.hyperlinkedserializers' + + def setUp(self): + """ + Create 1 ExtraKwargModel instance. + """ + self.basic = BasicModel.objects.create() + self.model = ExtraKwargModel.objects.create(basic=self.basic) + self.objects = ExtraKwargModel.objects + self.detail_view = ExtraKwargDetail.as_view() + + def test_get_detail_view(self): + """ + GET requests to a detail url with more than one kwarg should still resolve using reverse + """ + response = self.client.get('/root/2/extrakwarg/1/') + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index f2117538c..66359d3da 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -123,6 +123,9 @@ class BlankFieldModel(RESTFrameworkModel): class OptionalRelationModel(RESTFrameworkModel): other = models.ForeignKey('OptionalRelationModel', blank=True, null=True) +# Model to test multiple kwargs in url +class ExtraKwargModel(RESTFrameworkModel): + basic = models.ForeignKey(BasicModel) # Model for RegexField class Book(RESTFrameworkModel):