diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index ba4d60881..a2f54835d 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -25,10 +25,13 @@ from rest_framework.compat import ( ) from rest_framework.exceptions import ParseError from rest_framework.request import is_form_media_type, override_method +from rest_framework.reverse import reverse from rest_framework.settings import api_settings -from rest_framework.utils import encoders +from rest_framework.utils import encoders, model_meta from rest_framework.utils.breadcrumbs import get_breadcrumbs -from rest_framework.utils.field_mapping import ClassLookupDict +from rest_framework.utils.field_mapping import ( + ClassLookupDict, get_detail_view_name +) def zero_as_none(value): @@ -730,10 +733,13 @@ class AdminRenderer(BrowsableAPIRenderer): return ret - def get_context(self, data, accepted_media_type, renderer_context): + def get_context(self, data, accepted_media_type, renderer_context, reverse_func=None): """ Render the HTML for the browsable API representation. """ + if not reverse_func: + reverse_func = reverse + context = super(AdminRenderer, self).get_context( data, accepted_media_type, renderer_context ) @@ -757,6 +763,31 @@ class AdminRenderer(BrowsableAPIRenderer): header = results style = 'detail' + if style == 'list' and results: + serializer_class = getattr(context['view'], 'serializer_class', None) + fields = getattr(serializer_class.Meta, 'fields', None) + + if issubclass(serializer_class, serializers.ModelSerializer) and fields \ + and 'url' not in fields: + request = context['request'] + + def add_url(item): + info = model_meta.get_field_info(serializer_class.Meta.model) + pk = None + + for field_name, field in info.fields_and_pk.items(): + if field.primary_key and (field_name in item): + pk = item[field_name] + + if pk: + view_name = get_detail_view_name(serializer_class.Meta.model) + url = reverse_func(view_name, kwargs={'pk': pk}, request=request) + item['url'] = url + + return item + + results = [add_url(result) for result in results] + columns = [key for key in header.keys() if key != 'url'] details = [key for key in header.keys() if key != 'url'] diff --git a/tests/test_renderers.py b/tests/test_renderers.py index b4b2db22e..b7e152b46 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -14,12 +14,14 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import permissions, serializers, status from rest_framework.renderers import ( - BaseRenderer, BrowsableAPIRenderer, HTMLFormRenderer, JSONRenderer + AdminRenderer, BaseRenderer, BrowsableAPIRenderer, HTMLFormRenderer, + JSONRenderer ) from rest_framework.response import Response from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory from rest_framework.views import APIView +from tests.utils import mock_reverse DUMMYSTATUS = status.HTTP_200_OK DUMMYCONTENT = 'dummycontent' @@ -42,6 +44,12 @@ class DummyTestModel(models.Model): name = models.CharField(max_length=42, default='') +class DummySerializer(serializers.ModelSerializer): + class Meta: + model = DummyTestModel + fields = ('pk', 'name',) + + class BasicRendererTests(TestCase): def test_expected_results(self): for value, renderer_cls, expected in expected_results: @@ -103,6 +111,12 @@ class HTMLView1(APIView): def get(self, request, **kwargs): return Response('text') + +class AdminRendererView(APIView): + renderer_classes = (AdminRenderer, JSONRenderer) + serializer_class = DummySerializer + + urlpatterns = [ url(r'^.*\.(?P.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])), url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])), @@ -111,6 +125,7 @@ urlpatterns = [ url(r'^html$', HTMLView.as_view()), url(r'^html1$', HTMLView1.as_view()), url(r'^empty$', EmptyGETView.as_view()), + url(r'^admin_renderer$', AdminRendererView.as_view()), url(r'^api', include('rest_framework.urls', namespace='rest_framework')) ] @@ -459,3 +474,19 @@ class TestHiddenFieldHTMLFormRenderer(TestCase): field = serializer['published'] rendered = renderer.render_field(field, {}) assert rendered == '' + + +class TestAdminRenderer(TestCase): + def test_admin_renderer_adds_url(self): + """AdminRenderer should add a URL to the results if they come from a ModelSerializer""" + renderer = AdminRenderer() + data = [OrderedDict([('pk', 1), ('name', 'dummyname')])] + view = AdminRendererView() + request = view.initialize_request(APIRequestFactory().request()) + context = {'view': view, 'request': request, + 'response': view.as_view()(request)} + + rendered_context = renderer.get_context(data, 'text/html', context, reverse_func=mock_reverse) + + results = rendered_context['results'][0] + assert results['url'] == 'http://example.org/dummytestmodel-detail/1/'