Merge pull request #175 from izquierdo/custom_reverse

Custom reverse() and drop set_script_prefix
This commit is contained in:
Tom Christie 2012-02-21 06:09:30 -08:00
commit 49ebaf106d
14 changed files with 103 additions and 42 deletions

View File

@ -1,10 +1,10 @@
from django import forms 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 django.db import models
from djangorestframework.response import ErrorResponse from djangorestframework.response import ErrorResponse
from djangorestframework.serializer import Serializer, _SkipField from djangorestframework.serializer import Serializer, _SkipField
from djangorestframework.utils import as_tuple from djangorestframework.utils import as_tuple, reverse
class BaseResource(Serializer): class BaseResource(Serializer):
@ -354,7 +354,7 @@ class ModelResource(FormResource):
instance_attrs[param] = attr instance_attrs[param] = attr
try: try:
return reverse(self.view_callable[0], kwargs=instance_attrs) return reverse(self.view_callable[0], self.view.request, kwargs=instance_attrs)
except NoReverseMatch: except NoReverseMatch:
pass pass
raise _SkipField raise _SkipField

View File

@ -1,8 +1,8 @@
from django.conf.urls.defaults import patterns, url from django.conf.urls.defaults import patterns, url
from django.core.urlresolvers import reverse
from django.test import TestCase from django.test import TestCase
from django.utils import simplejson as json from django.utils import simplejson as json
from djangorestframework.utils import reverse
from djangorestframework.views import View from djangorestframework.views import View
@ -11,7 +11,7 @@ class MockView(View):
permissions = () permissions = ()
def get(self, request): def get(self, request):
return reverse('another') return reverse('another', request)
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', MockView.as_view()), url(r'^$', MockView.as_view()),

View File

@ -1,6 +1,7 @@
import django
from django.utils.encoding import smart_unicode from django.utils.encoding import smart_unicode
from django.utils.xmlutils import SimplerXMLGenerator 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 django.conf import settings
from djangorestframework.compat import StringIO from djangorestframework.compat import StringIO
@ -173,3 +174,21 @@ class XMLRenderer():
def dict2xml(input): def dict2xml(input):
return XMLRenderer().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))

View File

@ -181,20 +181,12 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
Required if you want to do things like set `request.upload_handlers` before Required if you want to do things like set `request.upload_handlers` before
the authentication and dispatch handling is run. the authentication and dispatch handling is run.
""" """
# Calls to 'reverse' will not be fully qualified unless we set the return request
# 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)
def final(self, request, response, *args, **kargs): def final(self, request, response, *args, **kargs):
""" """
Hook for any code that needs to run after everything else in the view. 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. # Always add these headers.
response.headers['Allow'] = ', '.join(self.allowed_methods) response.headers['Allow'] = ', '.join(self.allowed_methods)
# sample to allow caching using Vary http header # sample to allow caching using Vary http header

47
docs/howto/reverse.rst Normal file
View File

@ -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

5
docs/library/utils.rst Normal file
View File

@ -0,0 +1,5 @@
:mod:`utils`
==============
.. automodule:: utils
:members:

View File

@ -1,5 +1,5 @@
from django.core.urlresolvers import reverse
from djangorestframework.resources import ModelResource from djangorestframework.resources import ModelResource
from djangorestframework.utils import reverse
from blogpost.models import BlogPost, Comment from blogpost.models import BlogPost, Comment
@ -12,7 +12,7 @@ class BlogPostResource(ModelResource):
ordering = ('-created',) ordering = ('-created',)
def comments(self, instance): def comments(self, instance):
return reverse('comments', kwargs={'blogpost': instance.key}) return reverse('comments', request, kwargs={'blogpost': instance.key})
class CommentResource(ModelResource): class CommentResource(ModelResource):
@ -24,4 +24,4 @@ class CommentResource(ModelResource):
ordering = ('-created',) ordering = ('-created',)
def blogpost(self, instance): def blogpost(self, instance):
return reverse('blog-post', kwargs={'key': instance.blogpost.key}) return reverse('blog-post', request, kwargs={'key': instance.blogpost.key})

View File

@ -1,12 +1,11 @@
"""Test a range of REST API usage of the example application. """Test a range of REST API usage of the example application.
""" """
from django.core.urlresolvers import reverse
from django.test import TestCase from django.test import TestCase
from django.core.urlresolvers import reverse
from django.utils import simplejson as json from django.utils import simplejson as json
from djangorestframework.compat import RequestFactory from djangorestframework.compat import RequestFactory
from djangorestframework.utils import reverse
from djangorestframework.views import InstanceModelView, ListOrCreateModelView from djangorestframework.views import InstanceModelView, ListOrCreateModelView
from blogpost import models, urls from blogpost import models, urls

View File

@ -2,9 +2,9 @@ from djangorestframework.compat import View # Use Django 1.3's django.views.gen
from djangorestframework.mixins import ResponseMixin from djangorestframework.mixins import ResponseMixin
from djangorestframework.renderers import DEFAULT_RENDERERS from djangorestframework.renderers import DEFAULT_RENDERERS
from djangorestframework.response import Response from djangorestframework.response import Response
from djangorestframework.utils import reverse
from django.conf.urls.defaults import patterns, url from django.conf.urls.defaults import patterns, url
from django.core.urlresolvers import reverse
class ExampleView(ResponseMixin, View): class ExampleView(ResponseMixin, View):
@ -14,7 +14,7 @@ class ExampleView(ResponseMixin, View):
def get(self, request): def get(self, request):
response = Response(200, {'description': 'Some example content', response = Response(200, {'description': 'Some example content',
'url': reverse('mixin-view')}) 'url': reverse('mixin-view', request)})
return self.render(response) return self.render(response)

View File

@ -1,6 +1,6 @@
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse
from djangorestframework.utils import reverse
from djangorestframework.views import View from djangorestframework.views import View
from djangorestframework.response import Response from djangorestframework.response import Response
from djangorestframework import status 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('.')] 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], 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)] 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): def post(self, request):
""" """
@ -51,7 +51,7 @@ class ObjectStoreRoot(View):
pathname = os.path.join(OBJECT_STORE_DIR, key) pathname = os.path.join(OBJECT_STORE_DIR, key)
pickle.dump(self.CONTENT, open(pathname, 'wb')) pickle.dump(self.CONTENT, open(pathname, 'wb'))
remove_oldest_files(OBJECT_STORE_DIR, MAX_FILES) 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): class StoredObject(View):

View File

@ -1,6 +1,6 @@
from djangorestframework.views import View from djangorestframework.views import View
from djangorestframework.permissions import PerUserThrottling, IsAuthenticated from djangorestframework.permissions import PerUserThrottling, IsAuthenticated
from django.core.urlresolvers import reverse from djangorestframework.utils import reverse
class PermissionsExampleView(View): class PermissionsExampleView(View):
@ -12,11 +12,11 @@ class PermissionsExampleView(View):
return [ return [
{ {
'name': 'Throttling Example', 'name': 'Throttling Example',
'url': reverse('throttled-resource') 'url': reverse('throttled-resource', request)
}, },
{ {
'name': 'Logged in example', 'name': 'Logged in example',
'url': reverse('loggedin-resource') 'url': reverse('loggedin-resource', request)
}, },
] ]

View File

@ -1,10 +1,10 @@
from __future__ import with_statement # for python 2.5 from __future__ import with_statement # for python 2.5
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse
from djangorestframework.resources import FormResource from djangorestframework.resources import FormResource
from djangorestframework.response import Response from djangorestframework.response import Response
from djangorestframework.renderers import BaseRenderer from djangorestframework.renderers import BaseRenderer
from djangorestframework.utils import reverse
from djangorestframework.views import View from djangorestframework.views import View
from djangorestframework import status from djangorestframework import status
@ -61,7 +61,7 @@ class PygmentsRoot(View):
Return a list of all currently existing snippets. 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)] 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): def post(self, request):
""" """
@ -81,7 +81,7 @@ class PygmentsRoot(View):
remove_oldest_files(HIGHLIGHTED_CODE_DIR, MAX_FILES) 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): class PygmentsInstance(View):

View File

@ -1,5 +1,4 @@
from django.core.urlresolvers import reverse from djangorestframework.utils import reverse
from djangorestframework.views import View from djangorestframework.views import View
from djangorestframework.response import Response from djangorestframework.response import Response
from djangorestframework import status from djangorestframework import status
@ -16,7 +15,7 @@ class ExampleView(View):
""" """
Handle GET requests, returning a list of URLs pointing to 3 other views. 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): class AnotherExampleView(View):

View File

@ -1,6 +1,6 @@
"""The root view for the examples provided with Django REST framework""" """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 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.""" Please feel free to browse, create, edit and delete the resources in these examples."""
def get(self, request): def get(self, request):
return [{'name': 'Simple Resource example', 'url': reverse('example-resource')}, return [{'name': 'Simple Resource example', 'url': reverse('example-resource', request)},
{'name': 'Simple ModelResource example', 'url': reverse('model-resource-root')}, {'name': 'Simple ModelResource example', 'url': reverse('model-resource-root', request)},
{'name': 'Simple Mixin-only example', 'url': reverse('mixin-view')}, {'name': 'Simple Mixin-only example', 'url': reverse('mixin-view', request)},
{'name': 'Object store API', 'url': reverse('object-store-root')}, {'name': 'Object store API', 'url': reverse('object-store-root', request)},
{'name': 'Code highlighting API', 'url': reverse('pygments-root')}, {'name': 'Code highlighting API', 'url': reverse('pygments-root', request)},
{'name': 'Blog posts API', 'url': reverse('blog-posts-root')}, {'name': 'Blog posts API', 'url': reverse('blog-posts-root', request)},
{'name': 'Permissions example', 'url': reverse('permissions-example')} {'name': 'Permissions example', 'url': reverse('permissions-example', request)}
] ]