This commit is contained in:
Don Spaulding 2014-01-13 07:39:41 -08:00
commit 4489777be0
6 changed files with 88 additions and 11 deletions

View File

@ -11,6 +11,7 @@ from django.http import Http404
from rest_framework import status
from rest_framework.response import Response
from rest_framework.request import clone_request
from rest_framework.settings import api_settings
import warnings
@ -60,7 +61,7 @@ class CreateModelMixin(object):
def get_success_headers(self, data):
try:
return {'Location': data['url']}
return {'Location': data[api_settings.URL_FIELD_NAME]}
except (TypeError, KeyError):
return {}

View File

@ -332,6 +332,7 @@ class HyperlinkedRelatedField(RelatedField):
self.lookup_field = kwargs.pop('lookup_field', self.lookup_field)
self.format = kwargs.pop('format', None)
self.force_absolute = kwargs.pop('absolute', False)
# These are pending deprecation
if 'pk_url_kwarg' in kwargs:
@ -361,7 +362,10 @@ class HyperlinkedRelatedField(RelatedField):
lookup_field = getattr(obj, self.lookup_field)
kwargs = {self.lookup_field: lookup_field}
try:
return reverse(view_name, kwargs=kwargs, request=request, format=format)
return reverse(
view_name, kwargs=kwargs, request=request,
format=format, force_absolute=self.force_absolute
)
except NoReverseMatch:
pass
@ -371,7 +375,10 @@ class HyperlinkedRelatedField(RelatedField):
pk = obj.pk
kwargs = {self.pk_url_kwarg: pk}
try:
return reverse(view_name, kwargs=kwargs, request=request, format=format)
return reverse(
view_name, kwargs=kwargs, request=request,
format=format, force_absolute=self.force_absolute
)
except NoReverseMatch:
pass
@ -380,7 +387,10 @@ class HyperlinkedRelatedField(RelatedField):
# Only try slug if it corresponds to an attribute on the object.
kwargs = {self.slug_url_kwarg: slug}
try:
ret = reverse(view_name, kwargs=kwargs, request=request, format=format)
ret = reverse(
view_name, kwargs=kwargs, request=request,
format=format, force_absolute=self.force_absolute
)
if self.slug_field == 'slug' and self.slug_url_kwarg == 'slug':
# If the lookup succeeds using the default slug params,
# then `slug_field` is being used implicitly, and we
@ -503,6 +513,7 @@ class HyperlinkedIdentityField(Field):
self.format = kwargs.pop('format', None)
lookup_field = kwargs.pop('lookup_field', None)
self.lookup_field = lookup_field or self.lookup_field
self.force_absolute = kwargs.pop('force_absolute', False)
# These are pending deprecation
if 'pk_url_kwarg' in kwargs:
@ -572,7 +583,10 @@ class HyperlinkedIdentityField(Field):
return None
try:
return reverse(view_name, kwargs=kwargs, request=request, format=format)
return reverse(
view_name, kwargs=kwargs, request=request,
format=format, force_absolute=self.force_absolute
)
except NoReverseMatch:
pass
@ -581,7 +595,10 @@ class HyperlinkedIdentityField(Field):
# Otherwise, the default `lookup_field = 'pk'` has us covered.
kwargs = {self.pk_url_kwarg: obj.pk}
try:
return reverse(view_name, kwargs=kwargs, request=request, format=format)
return reverse(
view_name, kwargs=kwargs, request=request,
format=format, force_absolute=self.force_absolute
)
except NoReverseMatch:
pass
@ -590,7 +607,10 @@ class HyperlinkedIdentityField(Field):
# Only use slug lookup if a slug field exists on the model
kwargs = {self.slug_url_kwarg: slug}
try:
return reverse(view_name, kwargs=kwargs, request=request, format=format)
return reverse(
view_name, kwargs=kwargs, request=request,
format=format, force_absolute=self.force_absolute
)
except NoReverseMatch:
pass

View File

@ -4,9 +4,10 @@ Provide reverse functions that return fully qualified URLs
from __future__ import unicode_literals
from django.core.urlresolvers import reverse as django_reverse
from django.utils.functional import lazy
from rest_framework.settings import api_settings
def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra):
def reverse(viewname, args=None, kwargs=None, request=None, format=None, force_absolute=False, **extra):
"""
Same as `django.core.urlresolvers.reverse`, but optionally takes a request
and returns a fully qualified URL, using the request to get the base URL.
@ -15,6 +16,9 @@ def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra
kwargs = kwargs or {}
kwargs['format'] = format
url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)
if api_settings.RELATIVE_URLS and not force_absolute:
return url
if request:
return request.build_absolute_uri(url)
return url

View File

@ -990,6 +990,7 @@ class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
super(HyperlinkedModelSerializerOptions, self).__init__(meta)
self.view_name = getattr(meta, 'view_name', None)
self.lookup_field = getattr(meta, 'lookup_field', None)
self.url_field_name = getattr(meta, 'url_field_name', api_settings.URL_FIELD_NAME)
class HyperlinkedModelSerializer(ModelSerializer):
@ -1008,13 +1009,13 @@ class HyperlinkedModelSerializer(ModelSerializer):
if self.opts.view_name is None:
self.opts.view_name = self._get_default_view_name(self.opts.model)
if 'url' not in fields:
if self.opts.url_field_name not in fields:
url_field = self._hyperlink_identify_field_class(
view_name=self.opts.view_name,
lookup_field=self.opts.lookup_field
)
ret = self._dict_class()
ret['url'] = url_field
ret[self.opts.url_field_name] = url_field
ret.update(fields)
fields = ret
@ -1050,7 +1051,7 @@ class HyperlinkedModelSerializer(ModelSerializer):
We need to override the default, to use the url as the identity.
"""
try:
return data.get('url', None)
return data.get(self.opts.url_field_name, None)
except AttributeError:
return None

View File

@ -95,6 +95,8 @@ DEFAULTS = {
'URL_FORMAT_OVERRIDE': 'format',
'FORMAT_SUFFIX_KWARG': 'format',
'URL_FIELD_NAME': 'url',
'RELATIVE_URLS': False,
# Input and output formats
'DATE_INPUT_FORMATS': (

View File

@ -3,6 +3,7 @@ import json
from django.test import TestCase
from rest_framework import generics, status, serializers
from rest_framework.compat import patterns, url
from rest_framework.settings import api_settings
from rest_framework.test import APIRequestFactory
from rest_framework.tests.models import (
Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment,
@ -331,3 +332,51 @@ class TestOverriddenURLField(TestCase):
serializer.data,
{'title': 'New blog post', 'url': 'foo bar'}
)
class TestGlobalURLOverrides(TestCase):
urls = 'rest_framework.tests.test_hyperlinkedserializers'
def setUp(self):
self.old_url_fname = api_settings.URL_FIELD_NAME
self.old_relative_urls = api_settings.RELATIVE_URLS
api_settings.URL_FIELD_NAME = 'global_url_field'
api_settings.RELATIVE_URLS = True
class StandardSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = BlogPost
fields = ('title', 'global_url_field')
self.Serializer = StandardSerializer
self.obj = BlogPost.objects.create(title="New blog post")
self.context = {'request': factory.get('/basic/')}
def tearDown(self):
api_settings.URL_FIELD_NAME = self.old_url_fname
api_settings.RELATIVE_URLS = self.old_relative_urls
def test_serializer_overridden_url_field_name(self):
"""
The url field name should respect overriding at the serializer level.
"""
class URLFieldNameSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = BlogPost
fields = ('title', 'serializer_url_field')
url_field_name = "serializer_url_field"
serializer = URLFieldNameSerializer(self.obj, context=self.context)
self.assertIn('serializer_url_field', serializer.data)
def test_globally_overridden_url_field_name(self):
"""
The url field name should respect overriding for all serializers.
"""
serializer = self.Serializer(self.obj, context=self.context)
self.assertIn('global_url_field', serializer.data)
def test_relative_urls(self):
"""
Test whether url fields can be made relative across the board.
"""
serializer = self.Serializer(self.obj, context=self.context)
self.assertTrue(serializer.data['global_url_field'].startswith('/'))