diff options
| author | Tom Christie | 2012-02-21 06:09:30 -0800 | 
|---|---|---|
| committer | Tom Christie | 2012-02-21 06:09:30 -0800 | 
| commit | 49ebaf106d4c4357f51f6b2bbe533512340ea113 (patch) | |
| tree | ddb6cb637899a327253bffc2888e376eeab4e9f1 | |
| parent | 5a5f24f263c218f4f04a6ea54bfc48a2794e152b (diff) | |
| parent | f17f3886f42c2068e6e8a41c6e5fe6bfe1408add (diff) | |
| download | django-rest-framework-49ebaf106d4c4357f51f6b2bbe533512340ea113.tar.bz2 | |
Merge pull request #175 from izquierdo/custom_reverse
Custom reverse() and drop set_script_prefix
| -rw-r--r-- | djangorestframework/resources.py | 6 | ||||
| -rw-r--r-- | djangorestframework/tests/reverse.py | 4 | ||||
| -rw-r--r-- | djangorestframework/utils/__init__.py | 21 | ||||
| -rw-r--r-- | djangorestframework/views.py | 10 | ||||
| -rw-r--r-- | docs/howto/reverse.rst | 47 | ||||
| -rw-r--r-- | docs/library/utils.rst | 5 | ||||
| -rw-r--r-- | examples/blogpost/resources.py | 6 | ||||
| -rw-r--r-- | examples/blogpost/tests.py | 3 | ||||
| -rw-r--r-- | examples/mixin/urls.py | 4 | ||||
| -rw-r--r-- | examples/objectstore/views.py | 6 | ||||
| -rw-r--r-- | examples/permissionsexample/views.py | 6 | ||||
| -rw-r--r-- | examples/pygments_api/views.py | 6 | ||||
| -rw-r--r-- | examples/resourceexample/views.py | 5 | ||||
| -rw-r--r-- | examples/sandbox/views.py | 16 | 
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)}                  ]  | 
