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 import status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.request import clone_request from rest_framework.request import clone_request
from rest_framework.settings import api_settings
import warnings import warnings
@ -59,7 +60,7 @@ class CreateModelMixin(object):
def get_success_headers(self, data): def get_success_headers(self, data):
try: try:
return {'Location': data['url']} return {'Location': data[api_settings.URL_FIELD_NAME]}
except (TypeError, KeyError): except (TypeError, KeyError):
return {} return {}

View File

@ -335,6 +335,7 @@ class HyperlinkedRelatedField(RelatedField):
self.lookup_field = kwargs.pop('lookup_field', self.lookup_field) self.lookup_field = kwargs.pop('lookup_field', self.lookup_field)
self.format = kwargs.pop('format', None) self.format = kwargs.pop('format', None)
self.force_absolute = kwargs.pop('absolute', False)
# These are pending deprecation # These are pending deprecation
if 'pk_url_kwarg' in kwargs: if 'pk_url_kwarg' in kwargs:
@ -364,7 +365,10 @@ class HyperlinkedRelatedField(RelatedField):
lookup_field = getattr(obj, self.lookup_field) lookup_field = getattr(obj, self.lookup_field)
kwargs = {self.lookup_field: lookup_field} kwargs = {self.lookup_field: lookup_field}
try: 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: except NoReverseMatch:
pass pass
@ -374,7 +378,10 @@ class HyperlinkedRelatedField(RelatedField):
pk = obj.pk pk = obj.pk
kwargs = {self.pk_url_kwarg: pk} kwargs = {self.pk_url_kwarg: pk}
try: 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: except NoReverseMatch:
pass pass
@ -383,7 +390,10 @@ class HyperlinkedRelatedField(RelatedField):
# Only try slug if it corresponds to an attribute on the object. # Only try slug if it corresponds to an attribute on the object.
kwargs = {self.slug_url_kwarg: slug} kwargs = {self.slug_url_kwarg: slug}
try: 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 self.slug_field == 'slug' and self.slug_url_kwarg == 'slug':
# If the lookup succeeds using the default slug params, # If the lookup succeeds using the default slug params,
# then `slug_field` is being used implicitly, and we # then `slug_field` is being used implicitly, and we
@ -506,6 +516,7 @@ class HyperlinkedIdentityField(Field):
self.format = kwargs.pop('format', None) self.format = kwargs.pop('format', None)
lookup_field = kwargs.pop('lookup_field', None) lookup_field = kwargs.pop('lookup_field', None)
self.lookup_field = lookup_field or self.lookup_field self.lookup_field = lookup_field or self.lookup_field
self.force_absolute = kwargs.pop('force_absolute', False)
# These are pending deprecation # These are pending deprecation
if 'pk_url_kwarg' in kwargs: if 'pk_url_kwarg' in kwargs:
@ -570,7 +581,10 @@ class HyperlinkedIdentityField(Field):
lookup_field = getattr(obj, self.lookup_field) lookup_field = getattr(obj, self.lookup_field)
kwargs = {self.lookup_field: lookup_field} kwargs = {self.lookup_field: lookup_field}
try: 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: except NoReverseMatch:
pass pass
@ -579,7 +593,10 @@ class HyperlinkedIdentityField(Field):
# Otherwise, the default `lookup_field = 'pk'` has us covered. # Otherwise, the default `lookup_field = 'pk'` has us covered.
kwargs = {self.pk_url_kwarg: obj.pk} kwargs = {self.pk_url_kwarg: obj.pk}
try: 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: except NoReverseMatch:
pass pass
@ -588,7 +605,10 @@ class HyperlinkedIdentityField(Field):
# Only use slug lookup if a slug field exists on the model # Only use slug lookup if a slug field exists on the model
kwargs = {self.slug_url_kwarg: slug} kwargs = {self.slug_url_kwarg: slug}
try: 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: except NoReverseMatch:
pass pass

View File

@ -4,9 +4,10 @@ Provide reverse functions that return fully qualified URLs
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.urlresolvers import reverse as django_reverse from django.core.urlresolvers import reverse as django_reverse
from django.utils.functional import lazy 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 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. 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 = kwargs or {}
kwargs['format'] = format kwargs['format'] = format
url = django_reverse(viewname, args=args, kwargs=kwargs, **extra) url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)
if api_settings.RELATIVE_URLS and not force_absolute:
return url
if request: if request:
return request.build_absolute_uri(url) return request.build_absolute_uri(url)
return url return url

View File

@ -893,7 +893,7 @@ class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
super(HyperlinkedModelSerializerOptions, self).__init__(meta) super(HyperlinkedModelSerializerOptions, self).__init__(meta)
self.view_name = getattr(meta, 'view_name', None) self.view_name = getattr(meta, 'view_name', None)
self.lookup_field = getattr(meta, 'lookup_field', 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): class HyperlinkedModelSerializer(ModelSerializer):

View File

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

View File

@ -4,6 +4,7 @@ from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from rest_framework import generics, status, serializers from rest_framework import generics, status, serializers
from rest_framework.compat import patterns, url 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 from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel
factory = RequestFactory() factory = RequestFactory()
@ -328,3 +329,44 @@ class TestOverriddenURLField(TestCase):
serializer.data, serializer.data,
{'title': 'New blog post', 'url': 'foo bar'} {'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('/'))