Skip to content

Commit 8393908

Browse files
author
Ryan P Kilby
committed
Add 'AdminRenderer.get_result_url'
Attempts to reverse the result's detail view URL.
1 parent ecc48ad commit 8393908

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed

rest_framework/renderers.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from django.http.multipartparser import parse_header
1919
from django.template import engines, loader
2020
from django.test.client import encode_multipart
21+
from django.urls import NoReverseMatch
2122
from django.utils import six
2223
from django.utils.html import mark_safe
2324

@@ -808,6 +809,12 @@ def get_context(self, data, accepted_media_type, renderer_context):
808809
columns = [key for key in header if key != 'url']
809810
details = [key for key in header if key != 'url']
810811

812+
if isinstance(results, list) and 'view' in renderer_context:
813+
for result in results:
814+
url = self.get_result_url(result, context['view'])
815+
if url is not None:
816+
result.setdefault('url', url)
817+
811818
context['style'] = style
812819
context['columns'] = columns
813820
context['details'] = details
@@ -816,6 +823,26 @@ def get_context(self, data, accepted_media_type, renderer_context):
816823
context['error_title'] = getattr(self, 'error_title', None)
817824
return context
818825

826+
def get_result_url(self, result, view):
827+
"""
828+
Attempt to reverse the result's detail view URL.
829+
830+
This only works with views that are generic-like (has `.lookup_field`)
831+
and viewset-like (has `.basename` / `.reverse_action()`).
832+
"""
833+
if not hasattr(view, 'reverse_action') or \
834+
not hasattr(view, 'lookup_field'):
835+
return
836+
837+
lookup_field = view.lookup_field
838+
lookup_url_kwarg = getattr(view, 'lookup_url_kwarg', None) or lookup_field
839+
840+
try:
841+
kwargs = {lookup_url_kwarg: result[lookup_field]}
842+
return view.reverse_action('detail', kwargs=kwargs)
843+
except (KeyError, NoReverseMatch):
844+
return
845+
819846

820847
class DocumentationRenderer(BaseRenderer):
821848
media_type = 'text/html'

tests/test_renderers.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,75 @@ def get(self, request):
708708
response.render()
709709
self.assertContains(response, '<tr><th>Iteritems</th><td>a string</td></tr>', html=True)
710710

711+
def test_get_result_url(self):
712+
factory = APIRequestFactory()
713+
714+
class DummyGenericViewsetLike(APIView):
715+
lookup_field = 'test'
716+
717+
def reverse_action(view, *args, **kwargs):
718+
self.assertEqual(kwargs['kwargs']['test'], 1)
719+
return '/example/'
720+
721+
# get the view instance instead of the view function
722+
view = DummyGenericViewsetLike.as_view()
723+
request = factory.get('/')
724+
response = view(request)
725+
view = response.renderer_context['view']
726+
727+
self.assertEqual(self.renderer.get_result_url({'test': 1}, view), '/example/')
728+
self.assertIsNone(self.renderer.get_result_url({}, view))
729+
730+
def test_get_result_url_no_result(self):
731+
factory = APIRequestFactory()
732+
733+
class DummyView(APIView):
734+
lookup_field = 'test'
735+
736+
# get the view instance instead of the view function
737+
view = DummyView.as_view()
738+
request = factory.get('/')
739+
response = view(request)
740+
view = response.renderer_context['view']
741+
742+
self.assertIsNone(self.renderer.get_result_url({'test': 1}, view))
743+
self.assertIsNone(self.renderer.get_result_url({}, view))
744+
745+
def test_get_context_result_urls(self):
746+
factory = APIRequestFactory()
747+
748+
class DummyView(APIView):
749+
lookup_field = 'test'
750+
751+
def reverse_action(view, url_name, args=None, kwargs=None):
752+
return '/%s/%d' % (url_name, kwargs['test'])
753+
754+
# get the view instance instead of the view function
755+
view = DummyView.as_view()
756+
request = factory.get('/')
757+
response = view(request)
758+
759+
data = [
760+
{'test': 1},
761+
{'url': '/example', 'test': 2},
762+
{'url': None, 'test': 3},
763+
{},
764+
]
765+
context = {
766+
'view': DummyView(),
767+
'request': Request(request),
768+
'response': response
769+
}
770+
771+
context = self.renderer.get_context(data, None, context)
772+
results = context['results']
773+
774+
self.assertEqual(len(results), 4)
775+
self.assertEqual(results[0]['url'], '/detail/1')
776+
self.assertEqual(results[1]['url'], '/example')
777+
self.assertEqual(results[2]['url'], None)
778+
self.assertNotIn('url', results[3])
779+
711780

712781
@pytest.mark.skipif(not coreapi, reason='coreapi is not installed')
713782
class TestDocumentationRenderer(TestCase):

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy