mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-22 01:26:53 +03:00
Admin renderer urls (#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.
This commit is contained in:
parent
3578bd6883
commit
f89cc066bc
|
@ -18,6 +18,7 @@ from django.core.paginator import Page
|
||||||
from django.http.multipartparser import parse_header
|
from django.http.multipartparser import parse_header
|
||||||
from django.template import engines, loader
|
from django.template import engines, loader
|
||||||
from django.test.client import encode_multipart
|
from django.test.client import encode_multipart
|
||||||
|
from django.urls import NoReverseMatch
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.html import mark_safe
|
from django.utils.html import mark_safe
|
||||||
|
|
||||||
|
@ -815,6 +816,12 @@ class AdminRenderer(BrowsableAPIRenderer):
|
||||||
columns = [key for key in header if key != 'url']
|
columns = [key for key in header if key != 'url']
|
||||||
details = [key for key in header if key != 'url']
|
details = [key for key in header if key != 'url']
|
||||||
|
|
||||||
|
if isinstance(results, list) and 'view' in renderer_context:
|
||||||
|
for result in results:
|
||||||
|
url = self.get_result_url(result, context['view'])
|
||||||
|
if url is not None:
|
||||||
|
result.setdefault('url', url)
|
||||||
|
|
||||||
context['style'] = style
|
context['style'] = style
|
||||||
context['columns'] = columns
|
context['columns'] = columns
|
||||||
context['details'] = details
|
context['details'] = details
|
||||||
|
@ -823,6 +830,26 @@ class AdminRenderer(BrowsableAPIRenderer):
|
||||||
context['error_title'] = getattr(self, 'error_title', None)
|
context['error_title'] = getattr(self, 'error_title', None)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def get_result_url(self, result, view):
|
||||||
|
"""
|
||||||
|
Attempt to reverse the result's detail view URL.
|
||||||
|
|
||||||
|
This only works with views that are generic-like (has `.lookup_field`)
|
||||||
|
and viewset-like (has `.basename` / `.reverse_action()`).
|
||||||
|
"""
|
||||||
|
if not hasattr(view, 'reverse_action') or \
|
||||||
|
not hasattr(view, 'lookup_field'):
|
||||||
|
return
|
||||||
|
|
||||||
|
lookup_field = view.lookup_field
|
||||||
|
lookup_url_kwarg = getattr(view, 'lookup_url_kwarg', None) or lookup_field
|
||||||
|
|
||||||
|
try:
|
||||||
|
kwargs = {lookup_url_kwarg: result[lookup_field]}
|
||||||
|
return view.reverse_action('detail', kwargs=kwargs)
|
||||||
|
except (KeyError, NoReverseMatch):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
class DocumentationRenderer(BaseRenderer):
|
class DocumentationRenderer(BaseRenderer):
|
||||||
media_type = 'text/html'
|
media_type = 'text/html'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% load rest_framework %}
|
{% load rest_framework %}
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>{% for column in columns%}<th>{{ column|capfirst }}</th>{% endfor %}<th></th></tr>
|
<tr>{% for column in columns%}<th>{{ column|capfirst }}</th>{% endfor %}<th class="col-xs-1"></th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in results %}
|
{% for row in results %}
|
||||||
|
@ -14,7 +14,11 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<td>
|
<td>
|
||||||
|
{% if row.url %}
|
||||||
<a href="{{ row.url }}"><span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span></a>
|
<a href="{{ row.url }}"><span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span></a>
|
||||||
|
{% else %}
|
||||||
|
<span class="glyphicon glyphicon-chevron-right text-muted" aria-hidden="true"></span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -728,6 +728,75 @@ class AdminRendererTests(TestCase):
|
||||||
response.render()
|
response.render()
|
||||||
self.assertContains(response, '<tr><th>Iteritems</th><td>a string</td></tr>', html=True)
|
self.assertContains(response, '<tr><th>Iteritems</th><td>a string</td></tr>', html=True)
|
||||||
|
|
||||||
|
def test_get_result_url(self):
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
class DummyGenericViewsetLike(APIView):
|
||||||
|
lookup_field = 'test'
|
||||||
|
|
||||||
|
def reverse_action(view, *args, **kwargs):
|
||||||
|
self.assertEqual(kwargs['kwargs']['test'], 1)
|
||||||
|
return '/example/'
|
||||||
|
|
||||||
|
# get the view instance instead of the view function
|
||||||
|
view = DummyGenericViewsetLike.as_view()
|
||||||
|
request = factory.get('/')
|
||||||
|
response = view(request)
|
||||||
|
view = response.renderer_context['view']
|
||||||
|
|
||||||
|
self.assertEqual(self.renderer.get_result_url({'test': 1}, view), '/example/')
|
||||||
|
self.assertIsNone(self.renderer.get_result_url({}, view))
|
||||||
|
|
||||||
|
def test_get_result_url_no_result(self):
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
class DummyView(APIView):
|
||||||
|
lookup_field = 'test'
|
||||||
|
|
||||||
|
# get the view instance instead of the view function
|
||||||
|
view = DummyView.as_view()
|
||||||
|
request = factory.get('/')
|
||||||
|
response = view(request)
|
||||||
|
view = response.renderer_context['view']
|
||||||
|
|
||||||
|
self.assertIsNone(self.renderer.get_result_url({'test': 1}, view))
|
||||||
|
self.assertIsNone(self.renderer.get_result_url({}, view))
|
||||||
|
|
||||||
|
def test_get_context_result_urls(self):
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
class DummyView(APIView):
|
||||||
|
lookup_field = 'test'
|
||||||
|
|
||||||
|
def reverse_action(view, url_name, args=None, kwargs=None):
|
||||||
|
return '/%s/%d' % (url_name, kwargs['test'])
|
||||||
|
|
||||||
|
# get the view instance instead of the view function
|
||||||
|
view = DummyView.as_view()
|
||||||
|
request = factory.get('/')
|
||||||
|
response = view(request)
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{'test': 1},
|
||||||
|
{'url': '/example', 'test': 2},
|
||||||
|
{'url': None, 'test': 3},
|
||||||
|
{},
|
||||||
|
]
|
||||||
|
context = {
|
||||||
|
'view': DummyView(),
|
||||||
|
'request': Request(request),
|
||||||
|
'response': response
|
||||||
|
}
|
||||||
|
|
||||||
|
context = self.renderer.get_context(data, None, context)
|
||||||
|
results = context['results']
|
||||||
|
|
||||||
|
self.assertEqual(len(results), 4)
|
||||||
|
self.assertEqual(results[0]['url'], '/detail/1')
|
||||||
|
self.assertEqual(results[1]['url'], '/example')
|
||||||
|
self.assertEqual(results[2]['url'], None)
|
||||||
|
self.assertNotIn('url', results[3])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not coreapi, reason='coreapi is not installed')
|
@pytest.mark.skipif(not coreapi, reason='coreapi is not installed')
|
||||||
class TestDocumentationRenderer(TestCase):
|
class TestDocumentationRenderer(TestCase):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user