aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--djangorestframework/resources.py6
-rw-r--r--djangorestframework/tests/reverse.py4
-rw-r--r--djangorestframework/utils/__init__.py21
-rw-r--r--djangorestframework/views.py10
-rw-r--r--docs/howto/reverse.rst47
-rw-r--r--docs/library/utils.rst5
-rw-r--r--examples/blogpost/resources.py6
-rw-r--r--examples/blogpost/tests.py3
-rw-r--r--examples/mixin/urls.py4
-rw-r--r--examples/objectstore/views.py6
-rw-r--r--examples/permissionsexample/views.py6
-rw-r--r--examples/pygments_api/views.py6
-rw-r--r--examples/resourceexample/views.py5
-rw-r--r--examples/sandbox/views.py16
14 files changed, 103 insertions, 42 deletions
diff --git a/djangorestframework/resources.py b/djangorestframework/resources.py
index cc338cc0..5e126def 100644
--- a/djangorestframework/resources.py
+++ b/djangorestframework/resources.py
@@ -1,10 +1,10 @@
from django import forms
-from django.core.urlresolvers import reverse, get_urlconf, get_resolver, NoReverseMatch
+from django.core.urlresolvers import get_urlconf, get_resolver, NoReverseMatch
from django.db import models
from djangorestframework.response import ErrorResponse
from djangorestframework.serializer import Serializer, _SkipField
-from djangorestframework.utils import as_tuple
+from djangorestframework.utils import as_tuple, reverse
class BaseResource(Serializer):
@@ -354,7 +354,7 @@ class ModelResource(FormResource):
instance_attrs[param] = attr
try:
- return reverse(self.view_callable[0], kwargs=instance_attrs)
+ return reverse(self.view_callable[0], self.view.request, kwargs=instance_attrs)
except NoReverseMatch:
pass
raise _SkipField
diff --git a/djangorestframework/tests/reverse.py b/djangorestframework/tests/reverse.py
index 2d1ca79e..3ac04d8c 100644
--- a/djangorestframework/tests/reverse.py
+++ b/djangorestframework/tests/reverse.py
@@ -1,8 +1,8 @@
from django.conf.urls.defaults import patterns, url
-from django.core.urlresolvers import reverse
from django.test import TestCase
from django.utils import simplejson as json
+from djangorestframework.utils import reverse
from djangorestframework.views import View
@@ -11,7 +11,7 @@ class MockView(View):
permissions = ()
def get(self, request):
- return reverse('another')
+ return reverse('another', request)
urlpatterns = patterns('',
url(r'^$', MockView.as_view()),
diff --git a/djangorestframework/utils/__init__.py b/djangorestframework/utils/__init__.py
index 634d0d68..fc8bae92 100644
--- a/djangorestframework/utils/__init__.py
+++ b/djangorestframework/utils/__init__.py
@@ -1,6 +1,7 @@
+import django
from django.utils.encoding import smart_unicode
from django.utils.xmlutils import SimplerXMLGenerator
-from django.core.urlresolvers import resolve
+from django.core.urlresolvers import resolve, reverse as django_reverse
from django.conf import settings
from djangorestframework.compat import StringIO
@@ -173,3 +174,21 @@ class XMLRenderer():
def dict2xml(input):
return XMLRenderer().dict2xml(input)
+
+
+def reverse(viewname, request, *args, **kwargs):
+ """
+ Do the same as :py:func:`django.core.urlresolvers.reverse` but using
+ *request* to build a fully qualified URL.
+ """
+ return request.build_absolute_uri(django_reverse(viewname, *args, **kwargs))
+
+if django.VERSION >= (1, 4):
+ from django.core.urlresolvers import reverse_lazy as django_reverse_lazy
+
+ def reverse_lazy(viewname, request, *args, **kwargs):
+ """
+ Do the same as :py:func:`django.core.urlresolvers.reverse_lazy` but using
+ *request* to build a fully qualified URL.
+ """
+ return request.build_absolute_uri(django_reverse_lazy(viewname, *args, **kwargs))
diff --git a/djangorestframework/views.py b/djangorestframework/views.py
index 32d2437c..3e8bf356 100644
--- a/djangorestframework/views.py
+++ b/djangorestframework/views.py
@@ -181,20 +181,12 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
Required if you want to do things like set `request.upload_handlers` before
the authentication and dispatch handling is run.
"""
- # Calls to 'reverse' will not be fully qualified unless we set the
- # scheme/host/port here.
- self.orig_prefix = get_script_prefix()
- if not (self.orig_prefix.startswith('http:') or self.orig_prefix.startswith('https:')):
- prefix = '%s://%s' % (request.is_secure() and 'https' or 'http', request.get_host())
- set_script_prefix(prefix + self.orig_prefix)
+ return request
def final(self, request, response, *args, **kargs):
"""
Hook for any code that needs to run after everything else in the view.
"""
- # Restore script_prefix.
- set_script_prefix(self.orig_prefix)
-
# Always add these headers.
response.headers['Allow'] = ', '.join(self.allowed_methods)
# sample to allow caching using Vary http header
diff --git a/docs/howto/reverse.rst b/docs/howto/reverse.rst
new file mode 100644
index 00000000..e4efbbca
--- /dev/null
+++ b/docs/howto/reverse.rst
@@ -0,0 +1,47 @@
+Returning URIs from your Web APIs
+=================================
+
+ "The central feature that distinguishes the REST architectural style from
+ other network-based styles is its emphasis on a uniform interface between
+ components."
+
+ -- Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures
+
+As a rule, it's probably better practice to return absolute URIs from you web APIs, e.g. "http://example.com/foobar", rather than returning relative URIs, e.g. "/foobar".
+
+The advantages of doing so are:
+
+* It's more explicit.
+* It leaves less work for your API clients.
+* There's no ambiguity about the meaning of the string when it's found in representations such as JSON that do not have a native URI type.
+* It allows us to easily do things like markup HTML representations with hyperlinks.
+
+Django REST framework provides two utility functions to make it simpler to return absolute URIs from your Web API.
+
+There's no requirement for you to use them, but if you do then the self-describing API will be able to automatically hyperlink its output for you, which makes browsing the API much easier.
+
+reverse(viewname, request, ...)
+-------------------------------
+
+The :py:func:`~utils.reverse` function has the same behavior as :py:func:`django.core.urlresolvers.reverse` [1]_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port::
+
+ from djangorestframework.utils import reverse
+ from djangorestframework.views import View
+
+ class MyView(View):
+ def get(self, request):
+ context = {
+ 'url': reverse('year-summary', request, args=[1945])
+ }
+
+ return Response(context)
+
+reverse_lazy(viewname, request, ...)
+------------------------------------
+
+The :py:func:`~utils.reverse_lazy` function has the same behavior as :py:func:`django.core.urlresolvers.reverse_lazy` [2]_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port.
+
+.. rubric:: Footnotes
+
+.. [1] https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse
+.. [2] https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy
diff --git a/docs/library/utils.rst b/docs/library/utils.rst
new file mode 100644
index 00000000..653f24fd
--- /dev/null
+++ b/docs/library/utils.rst
@@ -0,0 +1,5 @@
+:mod:`utils`
+==============
+
+.. automodule:: utils
+ :members:
diff --git a/examples/blogpost/resources.py b/examples/blogpost/resources.py
index 5a3c1ce2..d4e0594d 100644
--- a/examples/blogpost/resources.py
+++ b/examples/blogpost/resources.py
@@ -1,5 +1,5 @@
-from django.core.urlresolvers import reverse
from djangorestframework.resources import ModelResource
+from djangorestframework.utils import reverse
from blogpost.models import BlogPost, Comment
@@ -12,7 +12,7 @@ class BlogPostResource(ModelResource):
ordering = ('-created',)
def comments(self, instance):
- return reverse('comments', kwargs={'blogpost': instance.key})
+ return reverse('comments', request, kwargs={'blogpost': instance.key})
class CommentResource(ModelResource):
@@ -24,4 +24,4 @@ class CommentResource(ModelResource):
ordering = ('-created',)
def blogpost(self, instance):
- return reverse('blog-post', kwargs={'key': instance.blogpost.key})
+ return reverse('blog-post', request, kwargs={'key': instance.blogpost.key})
diff --git a/examples/blogpost/tests.py b/examples/blogpost/tests.py
index 5aa4f89f..9f72e686 100644
--- a/examples/blogpost/tests.py
+++ b/examples/blogpost/tests.py
@@ -1,12 +1,11 @@
"""Test a range of REST API usage of the example application.
"""
-from django.core.urlresolvers import reverse
from django.test import TestCase
-from django.core.urlresolvers import reverse
from django.utils import simplejson as json
from djangorestframework.compat import RequestFactory
+from djangorestframework.utils import reverse
from djangorestframework.views import InstanceModelView, ListOrCreateModelView
from blogpost import models, urls
diff --git a/examples/mixin/urls.py b/examples/mixin/urls.py
index a3da3b2c..1f8c93b2 100644
--- a/examples/mixin/urls.py
+++ b/examples/mixin/urls.py
@@ -2,9 +2,9 @@ from djangorestframework.compat import View # Use Django 1.3's django.views.gen
from djangorestframework.mixins import ResponseMixin
from djangorestframework.renderers import DEFAULT_RENDERERS
from djangorestframework.response import Response
+from djangorestframework.utils import reverse
from django.conf.urls.defaults import patterns, url
-from django.core.urlresolvers import reverse
class ExampleView(ResponseMixin, View):
@@ -14,7 +14,7 @@ class ExampleView(ResponseMixin, View):
def get(self, request):
response = Response(200, {'description': 'Some example content',
- 'url': reverse('mixin-view')})
+ 'url': reverse('mixin-view', request)})
return self.render(response)
diff --git a/examples/objectstore/views.py b/examples/objectstore/views.py
index d85ed9f4..61d51b1b 100644
--- a/examples/objectstore/views.py
+++ b/examples/objectstore/views.py
@@ -1,6 +1,6 @@
from django.conf import settings
-from django.core.urlresolvers import reverse
+from djangorestframework.utils import reverse
from djangorestframework.views import View
from djangorestframework.response import Response
from djangorestframework import status
@@ -41,7 +41,7 @@ class ObjectStoreRoot(View):
filepaths = [os.path.join(OBJECT_STORE_DIR, file) for file in os.listdir(OBJECT_STORE_DIR) if not file.startswith('.')]
ctime_sorted_basenames = [item[0] for item in sorted([(os.path.basename(path), os.path.getctime(path)) for path in filepaths],
key=operator.itemgetter(1), reverse=True)]
- return [reverse('stored-object', kwargs={'key':key}) for key in ctime_sorted_basenames]
+ return [reverse('stored-object', request, kwargs={'key':key}) for key in ctime_sorted_basenames]
def post(self, request):
"""
@@ -51,7 +51,7 @@ class ObjectStoreRoot(View):
pathname = os.path.join(OBJECT_STORE_DIR, key)
pickle.dump(self.CONTENT, open(pathname, 'wb'))
remove_oldest_files(OBJECT_STORE_DIR, MAX_FILES)
- return Response(status.HTTP_201_CREATED, self.CONTENT, {'Location': reverse('stored-object', kwargs={'key':key})})
+ return Response(status.HTTP_201_CREATED, self.CONTENT, {'Location': reverse('stored-object', request, kwargs={'key':key})})
class StoredObject(View):
diff --git a/examples/permissionsexample/views.py b/examples/permissionsexample/views.py
index 86f458f8..83ef0fd9 100644
--- a/examples/permissionsexample/views.py
+++ b/examples/permissionsexample/views.py
@@ -1,6 +1,6 @@
from djangorestframework.views import View
from djangorestframework.permissions import PerUserThrottling, IsAuthenticated
-from django.core.urlresolvers import reverse
+from djangorestframework.utils import reverse
class PermissionsExampleView(View):
@@ -12,11 +12,11 @@ class PermissionsExampleView(View):
return [
{
'name': 'Throttling Example',
- 'url': reverse('throttled-resource')
+ 'url': reverse('throttled-resource', request)
},
{
'name': 'Logged in example',
- 'url': reverse('loggedin-resource')
+ 'url': reverse('loggedin-resource', request)
},
]
diff --git a/examples/pygments_api/views.py b/examples/pygments_api/views.py
index ffea60ae..01710bd5 100644
--- a/examples/pygments_api/views.py
+++ b/examples/pygments_api/views.py
@@ -1,10 +1,10 @@
from __future__ import with_statement # for python 2.5
from django.conf import settings
-from django.core.urlresolvers import reverse
from djangorestframework.resources import FormResource
from djangorestframework.response import Response
from djangorestframework.renderers import BaseRenderer
+from djangorestframework.utils import reverse
from djangorestframework.views import View
from djangorestframework import status
@@ -61,7 +61,7 @@ class PygmentsRoot(View):
Return a list of all currently existing snippets.
"""
unique_ids = [os.path.split(f)[1] for f in list_dir_sorted_by_ctime(HIGHLIGHTED_CODE_DIR)]
- return [reverse('pygments-instance', args=[unique_id]) for unique_id in unique_ids]
+ return [reverse('pygments-instance', request, args=[unique_id]) for unique_id in unique_ids]
def post(self, request):
"""
@@ -81,7 +81,7 @@ class PygmentsRoot(View):
remove_oldest_files(HIGHLIGHTED_CODE_DIR, MAX_FILES)
- return Response(status.HTTP_201_CREATED, headers={'Location': reverse('pygments-instance', args=[unique_id])})
+ return Response(status.HTTP_201_CREATED, headers={'Location': reverse('pygments-instance', request, args=[unique_id])})
class PygmentsInstance(View):
diff --git a/examples/resourceexample/views.py b/examples/resourceexample/views.py
index e6b5eeb8..2f623c39 100644
--- a/examples/resourceexample/views.py
+++ b/examples/resourceexample/views.py
@@ -1,5 +1,4 @@
-from django.core.urlresolvers import reverse
-
+from djangorestframework.utils import reverse
from djangorestframework.views import View
from djangorestframework.response import Response
from djangorestframework import status
@@ -16,7 +15,7 @@ class ExampleView(View):
"""
Handle GET requests, returning a list of URLs pointing to 3 other views.
"""
- return {"Some other resources": [reverse('another-example', kwargs={'num':num}) for num in range(3)]}
+ return {"Some other resources": [reverse('another-example', request, kwargs={'num':num}) for num in range(3)]}
class AnotherExampleView(View):
diff --git a/examples/sandbox/views.py b/examples/sandbox/views.py
index f7a3542d..3872b503 100644
--- a/examples/sandbox/views.py
+++ b/examples/sandbox/views.py
@@ -1,6 +1,6 @@
"""The root view for the examples provided with Django REST framework"""
-from django.core.urlresolvers import reverse
+from djangorestframework.utils import reverse
from djangorestframework.views import View
@@ -27,11 +27,11 @@ class Sandbox(View):
Please feel free to browse, create, edit and delete the resources in these examples."""
def get(self, request):
- return [{'name': 'Simple Resource example', 'url': reverse('example-resource')},
- {'name': 'Simple ModelResource example', 'url': reverse('model-resource-root')},
- {'name': 'Simple Mixin-only example', 'url': reverse('mixin-view')},
- {'name': 'Object store API', 'url': reverse('object-store-root')},
- {'name': 'Code highlighting API', 'url': reverse('pygments-root')},
- {'name': 'Blog posts API', 'url': reverse('blog-posts-root')},
- {'name': 'Permissions example', 'url': reverse('permissions-example')}
+ return [{'name': 'Simple Resource example', 'url': reverse('example-resource', request)},
+ {'name': 'Simple ModelResource example', 'url': reverse('model-resource-root', request)},
+ {'name': 'Simple Mixin-only example', 'url': reverse('mixin-view', request)},
+ {'name': 'Object store API', 'url': reverse('object-store-root', request)},
+ {'name': 'Code highlighting API', 'url': reverse('pygments-root', request)},
+ {'name': 'Blog posts API', 'url': reverse('blog-posts-root', request)},
+ {'name': 'Permissions example', 'url': reverse('permissions-example', request)}
]