Add a settings based override of relative vs. absolute URL behavior as well as the URL_FIELD_NAME.

This commit is contained in:
Don Spaulding 2013-06-28 18:09:49 -05:00
parent d64be3f079
commit c03614df28
6 changed files with 78 additions and 9 deletions

View File

@ -10,6 +10,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
@ -59,7 +60,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

@ -335,6 +335,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:
@ -364,7 +365,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
@ -374,7 +378,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
@ -383,7 +390,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
@ -506,6 +516,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:
@ -570,7 +581,10 @@ class HyperlinkedIdentityField(Field):
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
@ -579,7 +593,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
@ -588,7 +605,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

@ -893,7 +893,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', 'url')
self.url_field_name = getattr(meta, 'url_field_name', api_settings.URL_FIELD_NAME)
class HyperlinkedModelSerializer(ModelSerializer):

View File

@ -81,6 +81,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

@ -4,6 +4,7 @@ from django.test import TestCase
from django.test.client import RequestFactory
from rest_framework import generics, status, serializers
from rest_framework.compat import patterns, url
from rest_framework.settings import api_settings
from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel
factory = RequestFactory()
@ -328,3 +329,44 @@ class TestOverriddenURLField(TestCase):
serializer.data,
{'title': 'New blog post', 'url': 'foo bar'}
)
class TestGlobalURLOverrides(TestCase):
def setUp(self):
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")
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)
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)
import pdb; pdb.set_trace()
print serializer.data
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)
self.assertTrue(serializer.data['global_url_field'].startswith('/'))