mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-04 09:57:55 +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