This commit is contained in:
Chris Shucksmith 2018-02-25 19:45:25 +00:00 committed by GitHub
commit be3d8c10df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 109 additions and 1 deletions

View File

@ -309,6 +309,9 @@ class HyperlinkedRelatedField(RelatedField):
if hasattr(obj, 'pk') and obj.pk in (None, ''):
return None
if hasattr(request, 'app_name') and request.app_name is not None:
view_name = request.app_name + ':' + view_name
lookup_value = getattr(obj, self.lookup_field)
kwargs = {self.lookup_url_kwarg: lookup_value}
return self.reverse(view_name, kwargs=kwargs, request=request, format=format)

View File

@ -147,8 +147,9 @@ class SimpleRouter(BaseRouter):
),
]
def __init__(self, trailing_slash=True):
def __init__(self, trailing_slash=True, app_name=None):
self.trailing_slash = '/' if trailing_slash else ''
self.app_name = app_name
super(SimpleRouter, self).__init__()
def get_default_base_name(self, viewset):
@ -285,6 +286,7 @@ class SimpleRouter(BaseRouter):
initkwargs.update({
'basename': basename,
'detail': route.detail,
'router': self,
})
view = viewset.as_view(mapping, **initkwargs)

View File

@ -385,9 +385,14 @@ class URLPatternsTestCase(testcases.SimpleTestCase):
if hasattr(cls._module, 'urlpatterns'):
cls._module_urlpatterns = cls._module.urlpatterns
if hasattr(cls._module, 'app_name'):
cls._module_app_name = cls._module.app_name
cls._module.urlpatterns = cls.urlpatterns
if hasattr(cls, 'app_name'):
cls._module.app_name = cls.app_name
cls._override.enable()
super(URLPatternsTestCase, cls).setUpClass()
@ -400,3 +405,9 @@ class URLPatternsTestCase(testcases.SimpleTestCase):
cls._module.urlpatterns = cls._module_urlpatterns
else:
del cls._module.urlpatterns
if hasattr(cls, '_module_app_name'):
cls._module.app_name = cls._module_app_name
else:
if hasattr(cls._module, 'app_name'):
del cls._module.app_name

View File

@ -63,6 +63,9 @@ class ViewSetMixin(object):
# value is provided by the router through the initkwargs.
cls.basename = None
# Setting a router allows optional resolution of the app_name
cls.router = None
# actions must not be empty
if not actions:
raise TypeError("The `actions` argument must be provided when "
@ -99,6 +102,9 @@ class ViewSetMixin(object):
self.args = args
self.kwargs = kwargs
if self.router is not None:
request.app_name = self.router.app_name
# And continue as usual
return self.dispatch(request, *args, **kwargs)
@ -115,6 +121,7 @@ class ViewSetMixin(object):
view.cls = cls
view.initkwargs = initkwargs
view.suffix = initkwargs.get('suffix', None)
view.router = initkwargs.get('router', None)
view.actions = actions
return csrf_exempt(view)

View File

@ -0,0 +1,85 @@
from __future__ import unicode_literals
import pytest
from django.conf.urls import include, url
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.test import TestCase
from rest_framework import routers, serializers, viewsets
from rest_framework.test import APIRequestFactory, URLPatternsTestCase
from rest_framework.utils import json
factory = APIRequestFactory()
request = factory.get('/') # Just to ensure we have a request in the serializer context
class Wine(models.Model):
title = models.CharField(max_length=100)
class WineSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Wine
fields = ('url', 'title')
class WineViewSet(viewsets.ModelViewSet):
queryset = Wine.objects.all()
serializer_class = WineSerializer
router = routers.DefaultRouter()
router.register(r'wines', WineViewSet)
class TestHyperlinkedRouterNoName(URLPatternsTestCase, TestCase):
urlpatterns = [
url(r'^api/', include(router.urls)),
]
def test_no_name_works(self):
w = Wine(title="Shiraz")
w.save()
response = self.client.get('/api/wines/')
assert response.status_code == 200
assert json.loads(response.content.decode('utf-8')) == [{'title': 'Shiraz', 'url': 'http://testserver/api/wines/1/'}]
# Failing case with Django 2.0 and HyperlinkedModelSerializer
class TestHyperlinkedRouterFailsWithName(URLPatternsTestCase, TestCase):
urlpatterns = [
url(r'^api2/', include((router.urls, 'appname2'))),
]
def test_hyperlink_fails(self):
w = Wine(title="Shiraz")
w.save()
with pytest.raises(
ImproperlyConfigured,
message='Could not resolve URL for hyperlinked relationship using view '
'name "wine-detail". You may have failed to include the related model in '
'your API, or incorrectly configured the `lookup_field` attribute on this field.'):
self.client.get('/api2/wines/')
router2 = routers.DefaultRouter(app_name='appname2')
router2.register(r'wines', WineViewSet)
class TestHyperlinkedRouterConfigured(URLPatternsTestCase, TestCase):
urlpatterns = [
url(r'^api2/', include((router2.urls, 'appname2'))),
]
def test_regex_url_path_list(self):
w = Wine(title="Shiraz")
w.save()
response = self.client.get('/api2/wines/')
assert response.status_code == 200
assert json.loads(response.content.decode('utf-8')) == [{'title': 'Shiraz', 'url': 'http://testserver/api2/wines/1/'}]