Skip to content

Commit ce14751

Browse files
Ryan P KilbyPierre Chiquet
authored andcommitted
Admin renderer urls (encode#5988)
* Make admin detail link have small width * Disable admin detail link when no URL * Add 'AdminRenderer.get_result_url' Attempts to reverse the result's detail view URL.
1 parent 03bb565 commit ce14751

File tree

3 files changed

+101
-1
lines changed

3 files changed

+101
-1
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

@@ -815,6 +816,12 @@ def get_context(self, data, accepted_media_type, renderer_context):
815816
columns = [key for key in header if key != 'url']
816817
details = [key for key in header if key != 'url']
817818

819+
if isinstance(results, list) and 'view' in renderer_context:
820+
for result in results:
821+
url = self.get_result_url(result, context['view'])
822+
if url is not None:
823+
result.setdefault('url', url)
824+
818825
context['style'] = style
819826
context['columns'] = columns
820827
context['details'] = details
@@ -823,6 +830,26 @@ def get_context(self, data, accepted_media_type, renderer_context):
823830
context['error_title'] = getattr(self, 'error_title', None)
824831
return context
825832

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

827854
class DocumentationRenderer(BaseRenderer):
828855
media_type = 'text/html'

rest_framework/templates/rest_framework/admin/list.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% load rest_framework %}
22
<table class="table table-striped">
33
<thead>
4-
<tr>{% for column in columns%}<th>{{ column|capfirst }}</th>{% endfor %}<th></th></tr>
4+
<tr>{% for column in columns%}<th>{{ column|capfirst }}</th>{% endfor %}<th class="col-xs-1"></th></tr>
55
</thead>
66
<tbody>
77
{% for row in results %}
@@ -14,7 +14,11 @@
1414
{% endif %}
1515
{% endfor %}
1616
<td>
17+
{% if row.url %}
1718
<a href="{{ row.url }}"><span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span></a>
19+
{% else %}
20+
<span class="glyphicon glyphicon-chevron-right text-muted" aria-hidden="true"></span>
21+
{% endif %}
1822
</td>
1923
</tr>
2024
{% endfor %}

tests/test_renderers.py

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

731+
def test_get_result_url(self):
732+
factory = APIRequestFactory()
733+
734+
class DummyGenericViewsetLike(APIView):
735+
lookup_field = 'test'
736+
737+
def reverse_action(view, *args, **kwargs):
738+
self.assertEqual(kwargs['kwargs']['test'], 1)
739+
return '/example/'
740+
741+
# get the view instance instead of the view function
742+
view = DummyGenericViewsetLike.as_view()
743+
request = factory.get('/')
744+
response = view(request)
745+
view = response.renderer_context['view']
746+
747+
self.assertEqual(self.renderer.get_result_url({'test': 1}, view), '/example/')
748+
self.assertIsNone(self.renderer.get_result_url({}, view))
749+
750+
def test_get_result_url_no_result(self):
751+
factory = APIRequestFactory()
752+
753+
class DummyView(APIView):
754+
lookup_field = 'test'
755+
756+
# get the view instance instead of the view function
757+
view = DummyView.as_view()
758+
request = factory.get('/')
759+
response = view(request)
760+
view = response.renderer_context['view']
761+
762+
self.assertIsNone(self.renderer.get_result_url({'test': 1}, view))
763+
self.assertIsNone(self.renderer.get_result_url({}, view))
764+
765+
def test_get_context_result_urls(self):
766+
factory = APIRequestFactory()
767+
768+
class DummyView(APIView):
769+
lookup_field = 'test'
770+
771+
def reverse_action(view, url_name, args=None, kwargs=None):
772+
return '/%s/%d' % (url_name, kwargs['test'])
773+
774+
# get the view instance instead of the view function
775+
view = DummyView.as_view()
776+
request = factory.get('/')
777+
response = view(request)
778+
779+
data = [
780+
{'test': 1},
781+
{'url': '/example', 'test': 2},
782+
{'url': None, 'test': 3},
783+
{},
784+
]
785+
context = {
786+
'view': DummyView(),
787+
'request': Request(request),
788+
'response': response
789+
}
790+
791+
context = self.renderer.get_context(data, None, context)
792+
results = context['results']
793+
794+
self.assertEqual(len(results), 4)
795+
self.assertEqual(results[0]['url'], '/detail/1')
796+
self.assertEqual(results[1]['url'], '/example')
797+
self.assertEqual(results[2]['url'], None)
798+
self.assertNotIn('url', results[3])
799+
731800

732801
@pytest.mark.skipif(not coreapi, reason='coreapi is not installed')
733802
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