From 2b59df004a5bb7449aa4c07277ac846c330a79f7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 23 Feb 2012 08:58:10 +0000 Subject: [PATCH] reverse takes request as a kwarg for compatibility with django's reverse --- djangorestframework/compat.py | 22 +++------- djangorestframework/resources.py | 14 ++++-- djangorestframework/reverse.py | 19 ++++---- djangorestframework/tests/reverse.py | 2 +- examples/blogpost/resources.py | 8 +++- examples/mixin/urls.py | 7 +-- examples/modelresourceexample/models.py | 3 +- examples/modelresourceexample/resources.py | 1 + examples/modelresourceexample/urls.py | 2 +- examples/objectstore/views.py | 51 +++++++++++++++------- examples/pygments_api/views.py | 10 +++-- examples/resourceexample/views.py | 2 +- examples/sandbox/views.py | 14 +++--- 13 files changed, 88 insertions(+), 67 deletions(-) diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py index e81b428f3..83d26f1ff 100644 --- a/djangorestframework/compat.py +++ b/djangorestframework/compat.py @@ -214,18 +214,15 @@ else: REASON_NO_CSRF_COOKIE = "CSRF cookie not set." REASON_BAD_TOKEN = "CSRF token missing or incorrect." - def _get_failure_view(): """ Returns the view to be used for CSRF rejections """ return get_callable(settings.CSRF_FAILURE_VIEW) - def _get_new_csrf_key(): return hashlib.md5("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() - def get_token(request): """ 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 return request.META.get("CSRF_COOKIE", None) - def _sanitize_token(token): # Allow only alphanum, and ensure we return a 'str' for the sake of the post # processing middleware. @@ -432,12 +428,13 @@ try: except ImportError: yaml = None + import unittest try: import unittest.skip -except ImportError: # python < 2.7 +except ImportError: # python < 2.7 from unittest import TestCase - import functools + import functools def skip(reason): # 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)): @functools.wraps(test_item) def skip_wrapper(*args, **kwargs): - pass + pass test_item = skip_wrapper test_item.__unittest_skip__ = True test_item.__unittest_skip_why__ = reason return test_item return decorator - + 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 try: from xml.etree import ParseError as ETParseError -except ImportError: # python < 2.7 +except ImportError: # python < 2.7 ETParseError = None diff --git a/djangorestframework/resources.py b/djangorestframework/resources.py index 2b84ee09d..71c4d2b95 100644 --- a/djangorestframework/resources.py +++ b/djangorestframework/resources.py @@ -10,7 +10,8 @@ from djangorestframework.utils import as_tuple 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 include = None @@ -19,11 +20,13 @@ class BaseResource(Serializer): def __init__(self, view=None, depth=None, stack=[], **kwargs): super(BaseResource, self).__init__(depth, stack, **kwargs) self.view = view + self.request = view.request def validate_request(self, data, files=None): """ 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 @@ -37,7 +40,8 @@ class BaseResource(Serializer): class Resource(BaseResource): """ 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. @@ -355,7 +359,9 @@ class ModelResource(FormResource): instance_attrs[param] = attr 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: pass raise _SkipField diff --git a/djangorestframework/reverse.py b/djangorestframework/reverse.py index ad06f9664..056431576 100644 --- a/djangorestframework/reverse.py +++ b/djangorestframework/reverse.py @@ -2,22 +2,19 @@ Provide reverse functions that return fully qualified URLs """ 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): """ - Do the same as `django.core.urlresolvers.reverse` but using - *request* to build a fully qualified URL. + 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. """ + request = kwargs.pop('request', None) 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): - """ - 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) +reverse_lazy = lazy(reverse, str) diff --git a/djangorestframework/tests/reverse.py b/djangorestframework/tests/reverse.py index 9c0aee796..3dd13ef83 100644 --- a/djangorestframework/tests/reverse.py +++ b/djangorestframework/tests/reverse.py @@ -15,7 +15,7 @@ class MyView(View): renderers = (JSONRenderer, ) def get(self, request): - return reverse('myview', request) + return reverse('myview', request=request) urlpatterns = patterns('', url(r'^myview$', MyView.as_view(), name='myview'), diff --git a/examples/blogpost/resources.py b/examples/blogpost/resources.py index d11c5615c..ac00a7342 100644 --- a/examples/blogpost/resources.py +++ b/examples/blogpost/resources.py @@ -12,7 +12,9 @@ class BlogPostResource(ModelResource): ordering = ('-created',) 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): @@ -24,4 +26,6 @@ class CommentResource(ModelResource): ordering = ('-created',) 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) diff --git a/examples/mixin/urls.py b/examples/mixin/urls.py index 9652cfda4..900c532e0 100644 --- a/examples/mixin/urls.py +++ b/examples/mixin/urls.py @@ -9,16 +9,17 @@ from django.conf.urls.defaults import patterns, url class ExampleView(ResponseMixin, View): """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 def get(self, request): + url = reverse('mixin-view', request=request) response = Response(200, {'description': 'Some example content', - 'url': reverse('mixin-view', request)}) + 'url': url}) return self.render(response) urlpatterns = patterns('', url(r'^$', ExampleView.as_view(), name='mixin-view'), ) - diff --git a/examples/modelresourceexample/models.py b/examples/modelresourceexample/models.py index ff0179c88..11f3eae22 100644 --- a/examples/modelresourceexample/models.py +++ b/examples/modelresourceexample/models.py @@ -2,6 +2,7 @@ from django.db import models MAX_INSTANCES = 10 + class MyModel(models.Model): foo = models.BooleanField() bar = models.IntegerField(help_text='Must be an integer.') @@ -15,5 +16,3 @@ class MyModel(models.Model): super(MyModel, self).save(*args, **kwargs) while MyModel.objects.all().count() > MAX_INSTANCES: MyModel.objects.all().order_by('-created')[0].delete() - - diff --git a/examples/modelresourceexample/resources.py b/examples/modelresourceexample/resources.py index 634ea6b30..05090f8f3 100644 --- a/examples/modelresourceexample/resources.py +++ b/examples/modelresourceexample/resources.py @@ -1,6 +1,7 @@ from djangorestframework.resources import ModelResource from modelresourceexample.models import MyModel + class MyModelResource(ModelResource): model = MyModel fields = ('foo', 'bar', 'baz', 'url') diff --git a/examples/modelresourceexample/urls.py b/examples/modelresourceexample/urls.py index b6a16542a..c680dc655 100644 --- a/examples/modelresourceexample/urls.py +++ b/examples/modelresourceexample/urls.py @@ -4,5 +4,5 @@ from modelresourceexample.resources import MyModelResource urlpatterns = patterns('', url(r'^$', ListOrCreateModelView.as_view(resource=MyModelResource), name='model-resource-root'), - url(r'^(?P[0-9]+)/$', InstanceModelView.as_view(resource=MyModelResource)), + url(r'^(?P[0-9]+)/$', InstanceModelView.as_view(resource=MyModelResource), name='model-resource-instance'), ) diff --git a/examples/objectstore/views.py b/examples/objectstore/views.py index dd7114820..880bd3fcb 100644 --- a/examples/objectstore/views.py +++ b/examples/objectstore/views.py @@ -28,6 +28,20 @@ def remove_oldest_files(dir, 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): """ 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) """ - 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], 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): """ Create a new stored object, with a unique key. """ key = str(uuid.uuid1()) - pathname = os.path.join(OBJECT_STORE_DIR, key) - pickle.dump(self.CONTENT, open(pathname, 'wb')) + filename = get_filename(key) + pickle.dump(self.CONTENT, open(filename, 'wb')) + 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): @@ -59,29 +77,30 @@ class StoredObject(View): Represents a stored object. The object may be any picklable content. """ - 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) - if not os.path.exists(pathname): + filename = get_filename(key) + if not os.path.exists(filename): return Response(status.HTTP_404_NOT_FOUND) - return pickle.load(open(pathname, 'rb')) + return pickle.load(open(filename, 'rb')) 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) - pickle.dump(self.CONTENT, open(pathname, 'wb')) + filename = get_filename(key) + pickle.dump(self.CONTENT, open(filename, 'wb')) return self.CONTENT def delete(self, request, key): """ Delete a stored object, by removing it's pickled file. """ - pathname = os.path.join(OBJECT_STORE_DIR, key) - if not os.path.exists(pathname): + filename = get_filename(key) + if not os.path.exists(filename): return Response(status.HTTP_404_NOT_FOUND) - os.remove(pathname) + os.remove(filename) diff --git a/examples/pygments_api/views.py b/examples/pygments_api/views.py index b53966813..3dd55115c 100644 --- a/examples/pygments_api/views.py +++ b/examples/pygments_api/views.py @@ -30,9 +30,13 @@ def list_dir_sorted_by_ctime(dir): """ 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('.')] - return [item[0] for item in sorted( [(path, os.path.getctime(path)) for path in filepaths], - key=operator.itemgetter(1), reverse=False) ] + filepaths = [os.path.join(dir, file) + for file in os.listdir(dir) + 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): """ diff --git a/examples/resourceexample/views.py b/examples/resourceexample/views.py index 1b5b8e9c3..fcd0b2737 100644 --- a/examples/resourceexample/views.py +++ b/examples/resourceexample/views.py @@ -15,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', 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): diff --git a/examples/sandbox/views.py b/examples/sandbox/views.py index 78e00f463..8e3b3a10a 100644 --- a/examples/sandbox/views.py +++ b/examples/sandbox/views.py @@ -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', 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)} + return [{'name': 'Simple Resource example', 'url': reverse('example-resource', request=request)}, + {'name': 'Simple ModelResource example', 'url': reverse('model-resource-root', request=request)}, + {'name': 'Simple Mixin-only example', 'url': reverse('mixin-view', request=request)}, + {'name': 'Object store API', 'url': reverse('object-store-root', request=request)}, + {'name': 'Code highlighting API', 'url': reverse('pygments-root', request=request)}, + {'name': 'Blog posts API', 'url': reverse('blog-posts-root', request=request)}, + {'name': 'Permissions example', 'url': reverse('permissions-example', request=request)} ]