Repository: encode/django-rest-framework

Issue:
lookup_url_kwarg not correctly used by Router
[`lookup_url_kwarg`](https://github.com/tomchristie/django-rest-framework/blob/8b0f25aa0a91cb7b56f9ce4dde4330fe5daaad9b/rest_framework/generics.py#L85) does not appear to be used by the router when generating URLs. See:
https://github.com/tomchristie/django-rest-framework/blob/ba7dca893cd55a1d5ee928c4b10878c92c44c4f5/rest_framework/routers.py#L228



Discussion:
Does look like a defect, yes.
If anyone's able to together a minimal failing test case that's probable the next thing needed to progress this issue.


Resolving patch:
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -218,14 +218,15 @@ def get_lookup_regex(self, viewset, lookup_prefix=''):
 
         https://github.com/alanjds/drf-nested-routers
         """
-        base_regex = '(?P<{lookup_prefix}{lookup_field}>{lookup_value})'
+        base_regex = '(?P<{lookup_prefix}{lookup_url_kwarg}>{lookup_value})'
         # Use `pk` as default field, unset set.  Default regex should not
         # consume `.json` style suffixes and should break at '/' boundaries.
         lookup_field = getattr(viewset, 'lookup_field', 'pk')
+        lookup_url_kwarg = getattr(viewset, 'lookup_url_kwarg', None) or lookup_field
         lookup_value = getattr(viewset, 'lookup_value_regex', '[^/.]+')
         return base_regex.format(
             lookup_prefix=lookup_prefix,
-            lookup_field=lookup_field,
+            lookup_url_kwarg=lookup_url_kwarg,
             lookup_value=lookup_value
         )
 


Test patch:
diff --git a/tests/test_routers.py b/tests/test_routers.py
--- a/tests/test_routers.py
+++ b/tests/test_routers.py
@@ -32,6 +32,13 @@ class NoteViewSet(viewsets.ModelViewSet):
     lookup_field = 'uuid'
 
 
+class KWargedNoteViewSet(viewsets.ModelViewSet):
+    queryset = RouterTestModel.objects.all()
+    serializer_class = NoteSerializer
+    lookup_field = 'text__contains'
+    lookup_url_kwarg = 'text'
+
+
 class MockViewSet(viewsets.ModelViewSet):
     queryset = None
     serializer_class = None
@@ -40,6 +47,9 @@ class MockViewSet(viewsets.ModelViewSet):
 notes_router = SimpleRouter()
 notes_router.register(r'notes', NoteViewSet)
 
+kwarged_notes_router = SimpleRouter()
+kwarged_notes_router.register(r'notes', KWargedNoteViewSet)
+
 namespaced_router = DefaultRouter()
 namespaced_router.register(r'example', MockViewSet, base_name='example')
 
@@ -47,6 +57,7 @@ class MockViewSet(viewsets.ModelViewSet):
     url(r'^non-namespaced/', include(namespaced_router.urls)),
     url(r'^namespaced/', include(namespaced_router.urls, namespace='example')),
     url(r'^example/', include(notes_router.urls)),
+    url(r'^example2/', include(kwarged_notes_router.urls)),
 ]
 
 
@@ -177,6 +188,33 @@ def test_urls_limited_by_lookup_value_regex(self):
             self.assertEqual(expected[idx], self.urls[idx].regex.pattern)
 
 
+class TestLookupUrlKwargs(TestCase):
+    """
+    Ensure the router honors lookup_url_kwarg.
+
+    Setup a deep lookup_field, but map it to a simple URL kwarg.
+    """
+    urls = 'tests.test_routers'
+
+    def setUp(self):
+        RouterTestModel.objects.create(uuid='123', text='foo bar')
+
+    def test_custom_lookup_url_kwarg_route(self):
+        detail_route = kwarged_notes_router.urls[-1]
+        detail_url_pattern = detail_route.regex.pattern
+        self.assertIn('^notes/(?P<text>', detail_url_pattern)
+
+    def test_retrieve_lookup_url_kwarg_detail_view(self):
+        response = self.client.get('/example2/notes/fo/')
+        self.assertEqual(
+            response.data,
+            {
+                "url": "http://testserver/example/notes/123/",
+                "uuid": "123", "text": "foo bar"
+            }
+        )
+
+
 class TestTrailingSlashIncluded(TestCase):
     def setUp(self):
         class NoteViewSet(viewsets.ModelViewSet):