reverse takes request as a kwarg for compatibility with django's reverse

This commit is contained in:
Tom Christie 2012-02-23 08:58:10 +00:00
parent 8e0b9e55ec
commit 2b59df004a
13 changed files with 88 additions and 67 deletions

View File

@ -214,18 +214,15 @@ else:
REASON_NO_CSRF_COOKIE = "CSRF cookie not set." REASON_NO_CSRF_COOKIE = "CSRF cookie not set."
REASON_BAD_TOKEN = "CSRF token missing or incorrect." REASON_BAD_TOKEN = "CSRF token missing or incorrect."
def _get_failure_view(): def _get_failure_view():
""" """
Returns the view to be used for CSRF rejections Returns the view to be used for CSRF rejections
""" """
return get_callable(settings.CSRF_FAILURE_VIEW) return get_callable(settings.CSRF_FAILURE_VIEW)
def _get_new_csrf_key(): def _get_new_csrf_key():
return hashlib.md5("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() return hashlib.md5("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest()
def get_token(request): def get_token(request):
""" """
Returns the the CSRF token required for a POST form. The token is an Returns the the CSRF token required for a POST form. The token is an
@ -239,7 +236,6 @@ else:
request.META["CSRF_COOKIE_USED"] = True request.META["CSRF_COOKIE_USED"] = True
return request.META.get("CSRF_COOKIE", None) return request.META.get("CSRF_COOKIE", None)
def _sanitize_token(token): def _sanitize_token(token):
# Allow only alphanum, and ensure we return a 'str' for the sake of the post # Allow only alphanum, and ensure we return a 'str' for the sake of the post
# processing middleware. # processing middleware.
@ -432,12 +428,13 @@ try:
except ImportError: except ImportError:
yaml = None yaml = None
import unittest import unittest
try: try:
import unittest.skip import unittest.skip
except ImportError: # python < 2.7 except ImportError: # python < 2.7
from unittest import TestCase from unittest import TestCase
import functools import functools
def skip(reason): def skip(reason):
# Pasted from py27/lib/unittest/case.py # Pasted from py27/lib/unittest/case.py
@ -448,26 +445,19 @@ except ImportError: # python < 2.7
if not (isinstance(test_item, type) and issubclass(test_item, TestCase)): if not (isinstance(test_item, type) and issubclass(test_item, TestCase)):
@functools.wraps(test_item) @functools.wraps(test_item)
def skip_wrapper(*args, **kwargs): def skip_wrapper(*args, **kwargs):
pass pass
test_item = skip_wrapper test_item = skip_wrapper
test_item.__unittest_skip__ = True test_item.__unittest_skip__ = True
test_item.__unittest_skip_why__ = reason test_item.__unittest_skip_why__ = reason
return test_item return test_item
return decorator return decorator
unittest.skip = skip unittest.skip = skip
# reverse_lazy (Django 1.4 onwards)
try:
from django.core.urlresolvers import reverse_lazy
except:
from django.core.urlresolvers import reverse
from django.utils.functional import lazy
reverse_lazy = lazy(reverse, str)
# xml.etree.parse only throws ParseError for python >= 2.7 # xml.etree.parse only throws ParseError for python >= 2.7
try: try:
from xml.etree import ParseError as ETParseError from xml.etree import ParseError as ETParseError
except ImportError: # python < 2.7 except ImportError: # python < 2.7
ETParseError = None ETParseError = None

View File

@ -10,7 +10,8 @@ from djangorestframework.utils import as_tuple
class BaseResource(Serializer): class BaseResource(Serializer):
""" """
Base class for all Resource classes, which simply defines the interface they provide. Base class for all Resource classes, which simply defines the interface
they provide.
""" """
fields = None fields = None
include = None include = None
@ -19,11 +20,13 @@ class BaseResource(Serializer):
def __init__(self, view=None, depth=None, stack=[], **kwargs): def __init__(self, view=None, depth=None, stack=[], **kwargs):
super(BaseResource, self).__init__(depth, stack, **kwargs) super(BaseResource, self).__init__(depth, stack, **kwargs)
self.view = view self.view = view
self.request = view.request
def validate_request(self, data, files=None): def validate_request(self, data, files=None):
""" """
Given the request content return the cleaned, validated content. Given the request content return the cleaned, validated content.
Typically raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure. Typically raises a :exc:`response.ErrorResponse` with status code 400
(Bad Request) on failure.
""" """
return data return data
@ -37,7 +40,8 @@ class BaseResource(Serializer):
class Resource(BaseResource): class Resource(BaseResource):
""" """
A Resource determines how a python object maps to some serializable data. A Resource determines how a python object maps to some serializable data.
Objects that a resource can act on include plain Python object instances, Django Models, and Django QuerySets. Objects that a resource can act on include plain Python object instances,
Django Models, and Django QuerySets.
""" """
# The model attribute refers to the Django Model which this Resource maps to. # The model attribute refers to the Django Model which this Resource maps to.
@ -355,7 +359,9 @@ class ModelResource(FormResource):
instance_attrs[param] = attr instance_attrs[param] = attr
try: try:
return reverse(self.view_callable[0], self.view.request, kwargs=instance_attrs) return reverse(self.view_callable[0],
kwargs=instance_attrs,
request=self.view.request)
except NoReverseMatch: except NoReverseMatch:
pass pass
raise _SkipField raise _SkipField

View File

@ -2,22 +2,19 @@
Provide reverse functions that return fully qualified URLs Provide reverse functions that return fully qualified URLs
""" """
from django.core.urlresolvers import reverse as django_reverse from django.core.urlresolvers import reverse as django_reverse
from djangorestframework.compat import reverse_lazy as django_reverse_lazy from django.utils.functional import lazy
def reverse(viewname, request, *args, **kwargs): def reverse(viewname, request, *args, **kwargs):
""" """
Do the same as `django.core.urlresolvers.reverse` but using Same as `django.core.urlresolvers.reverse`, but optionally takes a request
*request* to build a fully qualified URL. and returns a fully qualified URL, using the request to get the base URL.
""" """
request = kwargs.pop('request', None)
url = django_reverse(viewname, *args, **kwargs) url = django_reverse(viewname, *args, **kwargs)
return request.build_absolute_uri(url) if request:
return request.build_absolute_uri(url)
return url
def reverse_lazy(viewname, request, *args, **kwargs): reverse_lazy = lazy(reverse, str)
"""
Do the same as `django.core.urlresolvers.reverse_lazy` but using
*request* to build a fully qualified URL.
"""
url = django_reverse_lazy(viewname, *args, **kwargs)
return request.build_absolute_uri(url)

View File

@ -15,7 +15,7 @@ class MyView(View):
renderers = (JSONRenderer, ) renderers = (JSONRenderer, )
def get(self, request): def get(self, request):
return reverse('myview', request) return reverse('myview', request=request)
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^myview$', MyView.as_view(), name='myview'), url(r'^myview$', MyView.as_view(), name='myview'),

View File

@ -12,7 +12,9 @@ class BlogPostResource(ModelResource):
ordering = ('-created',) ordering = ('-created',)
def comments(self, instance): def comments(self, instance):
return reverse('comments', request, kwargs={'blogpost': instance.key}) return reverse('comments',
kwargs={'blogpost': instance.key},
request=self.request)
class CommentResource(ModelResource): class CommentResource(ModelResource):
@ -24,4 +26,6 @@ class CommentResource(ModelResource):
ordering = ('-created',) ordering = ('-created',)
def blogpost(self, instance): def blogpost(self, instance):
return reverse('blog-post', request, kwargs={'key': instance.blogpost.key}) return reverse('blog-post',
kwargs={'key': instance.blogpost.key},
request=self.request)

View File

@ -9,16 +9,17 @@ from django.conf.urls.defaults import patterns, url
class ExampleView(ResponseMixin, View): class ExampleView(ResponseMixin, View):
"""An example view using Django 1.3's class based views. """An example view using Django 1.3's class based views.
Uses djangorestframework's RendererMixin to provide support for multiple output formats.""" Uses djangorestframework's RendererMixin to provide support for multiple
output formats."""
renderers = DEFAULT_RENDERERS renderers = DEFAULT_RENDERERS
def get(self, request): def get(self, request):
url = reverse('mixin-view', request=request)
response = Response(200, {'description': 'Some example content', response = Response(200, {'description': 'Some example content',
'url': reverse('mixin-view', request)}) 'url': url})
return self.render(response) return self.render(response)
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', ExampleView.as_view(), name='mixin-view'), url(r'^$', ExampleView.as_view(), name='mixin-view'),
) )

View File

@ -2,6 +2,7 @@ from django.db import models
MAX_INSTANCES = 10 MAX_INSTANCES = 10
class MyModel(models.Model): class MyModel(models.Model):
foo = models.BooleanField() foo = models.BooleanField()
bar = models.IntegerField(help_text='Must be an integer.') bar = models.IntegerField(help_text='Must be an integer.')
@ -15,5 +16,3 @@ class MyModel(models.Model):
super(MyModel, self).save(*args, **kwargs) super(MyModel, self).save(*args, **kwargs)
while MyModel.objects.all().count() > MAX_INSTANCES: while MyModel.objects.all().count() > MAX_INSTANCES:
MyModel.objects.all().order_by('-created')[0].delete() MyModel.objects.all().order_by('-created')[0].delete()

View File

@ -1,6 +1,7 @@
from djangorestframework.resources import ModelResource from djangorestframework.resources import ModelResource
from modelresourceexample.models import MyModel from modelresourceexample.models import MyModel
class MyModelResource(ModelResource): class MyModelResource(ModelResource):
model = MyModel model = MyModel
fields = ('foo', 'bar', 'baz', 'url') fields = ('foo', 'bar', 'baz', 'url')

View File

@ -4,5 +4,5 @@ from modelresourceexample.resources import MyModelResource
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', ListOrCreateModelView.as_view(resource=MyModelResource), name='model-resource-root'), url(r'^$', ListOrCreateModelView.as_view(resource=MyModelResource), name='model-resource-root'),
url(r'^(?P<pk>[0-9]+)/$', InstanceModelView.as_view(resource=MyModelResource)), url(r'^(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=MyModelResource), name='model-resource-instance'),
) )

View File

@ -28,6 +28,20 @@ def remove_oldest_files(dir, max_files):
[os.remove(path) for path in ctime_sorted_paths[max_files:]] [os.remove(path) for path in ctime_sorted_paths[max_files:]]
def get_filename(key):
"""
Given a stored object's key returns the file's path.
"""
return os.path.join(OBJECT_STORE_DIR, key)
def get_file_url(key, request):
"""
Given a stored object's key returns the URL for the object.
"""
return reverse('stored-object', kwargs={'key': key}, request=request)
class ObjectStoreRoot(View): class ObjectStoreRoot(View):
""" """
Root of the Object Store API. Root of the Object Store API.
@ -38,20 +52,24 @@ class ObjectStoreRoot(View):
""" """
Return a list of all the stored object URLs. (Ordered by creation time, newest first) Return a list of all the stored object URLs. (Ordered by creation time, newest first)
""" """
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', request, kwargs={'key':key}) for key in ctime_sorted_basenames] return [get_file_url(key, request) for key in ctime_sorted_basenames]
def post(self, request): def post(self, request):
""" """
Create a new stored object, with a unique key. Create a new stored object, with a unique key.
""" """
key = str(uuid.uuid1()) key = str(uuid.uuid1())
pathname = os.path.join(OBJECT_STORE_DIR, key) filename = get_filename(key)
pickle.dump(self.CONTENT, open(pathname, 'wb')) pickle.dump(self.CONTENT, open(filename, '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', request, kwargs={'key':key})}) url = get_file_url(key, request)
return Response(status.HTTP_201_CREATED, self.CONTENT, {'Location': url})
class StoredObject(View): class StoredObject(View):
@ -59,29 +77,30 @@ class StoredObject(View):
Represents a stored object. Represents a stored object.
The object may be any picklable content. The object may be any picklable content.
""" """
def get(self, request, key): def get(self, request, key):
""" """
Return a stored object, by unpickling the contents of a locally stored file. Return a stored object, by unpickling the contents of a locally
stored file.
""" """
pathname = os.path.join(OBJECT_STORE_DIR, key) filename = get_filename(key)
if not os.path.exists(pathname): if not os.path.exists(filename):
return Response(status.HTTP_404_NOT_FOUND) return Response(status.HTTP_404_NOT_FOUND)
return pickle.load(open(pathname, 'rb')) return pickle.load(open(filename, 'rb'))
def put(self, request, key): def put(self, request, key):
""" """
Update/create a stored object, by pickling the request content to a locally stored file. Update/create a stored object, by pickling the request content to a
locally stored file.
""" """
pathname = os.path.join(OBJECT_STORE_DIR, key) filename = get_filename(key)
pickle.dump(self.CONTENT, open(pathname, 'wb')) pickle.dump(self.CONTENT, open(filename, 'wb'))
return self.CONTENT return self.CONTENT
def delete(self, request, key): def delete(self, request, key):
""" """
Delete a stored object, by removing it's pickled file. Delete a stored object, by removing it's pickled file.
""" """
pathname = os.path.join(OBJECT_STORE_DIR, key) filename = get_filename(key)
if not os.path.exists(pathname): if not os.path.exists(filename):
return Response(status.HTTP_404_NOT_FOUND) return Response(status.HTTP_404_NOT_FOUND)
os.remove(pathname) os.remove(filename)

View File

@ -30,9 +30,13 @@ def list_dir_sorted_by_ctime(dir):
""" """
Return a list of files sorted by creation time Return a list of files sorted by creation time
""" """
filepaths = [os.path.join(dir, file) for file in os.listdir(dir) if not file.startswith('.')] filepaths = [os.path.join(dir, file)
return [item[0] for item in sorted( [(path, os.path.getctime(path)) for path in filepaths], for file in os.listdir(dir)
key=operator.itemgetter(1), reverse=False) ] if not file.startswith('.')]
ctimes = [(path, os.path.getctime(path)) for path in filepaths]
ctimes = sorted(ctimes, key=operator.itemgetter(1), reverse=False)
return [filepath for filepath, ctime in ctimes]
def remove_oldest_files(dir, max_files): def remove_oldest_files(dir, max_files):
""" """

View File

@ -15,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', request, kwargs={'num':num}) for num in range(3)]} return {"Some other resources": [reverse('another-example', kwargs={'num':num}, request=request) for num in range(3)]}
class AnotherExampleView(View): class AnotherExampleView(View):

View File

@ -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', request)}, return [{'name': 'Simple Resource example', 'url': reverse('example-resource', request=request)},
{'name': 'Simple ModelResource example', 'url': reverse('model-resource-root', request)}, {'name': 'Simple ModelResource example', 'url': reverse('model-resource-root', request=request)},
{'name': 'Simple Mixin-only example', 'url': reverse('mixin-view', request)}, {'name': 'Simple Mixin-only example', 'url': reverse('mixin-view', request=request)},
{'name': 'Object store API', 'url': reverse('object-store-root', request)}, {'name': 'Object store API', 'url': reverse('object-store-root', request=request)},
{'name': 'Code highlighting API', 'url': reverse('pygments-root', request)}, {'name': 'Code highlighting API', 'url': reverse('pygments-root', request=request)},
{'name': 'Blog posts API', 'url': reverse('blog-posts-root', request)}, {'name': 'Blog posts API', 'url': reverse('blog-posts-root', request=request)},
{'name': 'Permissions example', 'url': reverse('permissions-example', request)} {'name': 'Permissions example', 'url': reverse('permissions-example', request=request)}
] ]