mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-03 12:00:12 +03:00
Merge 1d161687a2
into d2994e0596
This commit is contained in:
commit
be3d8c10df
|
@ -309,6 +309,9 @@ class HyperlinkedRelatedField(RelatedField):
|
||||||
if hasattr(obj, 'pk') and obj.pk in (None, ''):
|
if hasattr(obj, 'pk') and obj.pk in (None, ''):
|
||||||
return 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)
|
lookup_value = getattr(obj, self.lookup_field)
|
||||||
kwargs = {self.lookup_url_kwarg: lookup_value}
|
kwargs = {self.lookup_url_kwarg: lookup_value}
|
||||||
return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
|
return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
|
||||||
|
|
|
@ -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.trailing_slash = '/' if trailing_slash else ''
|
||||||
|
self.app_name = app_name
|
||||||
super(SimpleRouter, self).__init__()
|
super(SimpleRouter, self).__init__()
|
||||||
|
|
||||||
def get_default_base_name(self, viewset):
|
def get_default_base_name(self, viewset):
|
||||||
|
@ -285,6 +286,7 @@ class SimpleRouter(BaseRouter):
|
||||||
initkwargs.update({
|
initkwargs.update({
|
||||||
'basename': basename,
|
'basename': basename,
|
||||||
'detail': route.detail,
|
'detail': route.detail,
|
||||||
|
'router': self,
|
||||||
})
|
})
|
||||||
|
|
||||||
view = viewset.as_view(mapping, **initkwargs)
|
view = viewset.as_view(mapping, **initkwargs)
|
||||||
|
|
|
@ -385,9 +385,14 @@ class URLPatternsTestCase(testcases.SimpleTestCase):
|
||||||
|
|
||||||
if hasattr(cls._module, 'urlpatterns'):
|
if hasattr(cls._module, 'urlpatterns'):
|
||||||
cls._module_urlpatterns = 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
|
cls._module.urlpatterns = cls.urlpatterns
|
||||||
|
|
||||||
|
if hasattr(cls, 'app_name'):
|
||||||
|
cls._module.app_name = cls.app_name
|
||||||
|
|
||||||
cls._override.enable()
|
cls._override.enable()
|
||||||
super(URLPatternsTestCase, cls).setUpClass()
|
super(URLPatternsTestCase, cls).setUpClass()
|
||||||
|
|
||||||
|
@ -400,3 +405,9 @@ class URLPatternsTestCase(testcases.SimpleTestCase):
|
||||||
cls._module.urlpatterns = cls._module_urlpatterns
|
cls._module.urlpatterns = cls._module_urlpatterns
|
||||||
else:
|
else:
|
||||||
del cls._module.urlpatterns
|
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
|
||||||
|
|
|
@ -63,6 +63,9 @@ class ViewSetMixin(object):
|
||||||
# value is provided by the router through the initkwargs.
|
# value is provided by the router through the initkwargs.
|
||||||
cls.basename = None
|
cls.basename = None
|
||||||
|
|
||||||
|
# Setting a router allows optional resolution of the app_name
|
||||||
|
cls.router = None
|
||||||
|
|
||||||
# actions must not be empty
|
# actions must not be empty
|
||||||
if not actions:
|
if not actions:
|
||||||
raise TypeError("The `actions` argument must be provided when "
|
raise TypeError("The `actions` argument must be provided when "
|
||||||
|
@ -99,6 +102,9 @@ class ViewSetMixin(object):
|
||||||
self.args = args
|
self.args = args
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
if self.router is not None:
|
||||||
|
request.app_name = self.router.app_name
|
||||||
|
|
||||||
# And continue as usual
|
# And continue as usual
|
||||||
return self.dispatch(request, *args, **kwargs)
|
return self.dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@ -115,6 +121,7 @@ class ViewSetMixin(object):
|
||||||
view.cls = cls
|
view.cls = cls
|
||||||
view.initkwargs = initkwargs
|
view.initkwargs = initkwargs
|
||||||
view.suffix = initkwargs.get('suffix', None)
|
view.suffix = initkwargs.get('suffix', None)
|
||||||
|
view.router = initkwargs.get('router', None)
|
||||||
view.actions = actions
|
view.actions = actions
|
||||||
return csrf_exempt(view)
|
return csrf_exempt(view)
|
||||||
|
|
||||||
|
|
85
tests/test_relations_hyperlink_appname.py
Normal file
85
tests/test_relations_hyperlink_appname.py
Normal 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/'}]
|
Loading…
Reference in New Issue
Block a user