diff options
| author | Honza Král <honza.kral@gmail.com> | 2009-10-12 10:16:17 +0000 |
|---|---|---|
| committer | Honza Král <honza.kral@gmail.com> | 2009-10-12 10:16:17 +0000 |
| commit | dfe495fbe8e360ee3b3cd8b29e55ee19d86fc9d2 (patch) | |
| tree | 16bccad252c6fd2b00e734f275594ae159596e70 /django | |
| parent | 83a3588ff712d5fe44e9692f5cb6a1d020f3ab2f (diff) | |
[soc2009/model-validation] Merged to trunk at r11603
SECURITY ALERT: Corrected regular expressions for URL and email fields.
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@11617 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
| -rw-r--r-- | django/conf/locale/he/LC_MESSAGES/django.mo | bin | 72623 -> 72636 bytes | |||
| -rw-r--r-- | django/conf/locale/he/LC_MESSAGES/django.po | 2 | ||||
| -rw-r--r-- | django/contrib/auth/decorators.py | 64 | ||||
| -rw-r--r-- | django/contrib/comments/admin.py | 1 | ||||
| -rw-r--r-- | django/contrib/gis/db/models/sql/aggregates.py | 29 | ||||
| -rw-r--r-- | django/contrib/gis/shortcuts.py | 3 | ||||
| -rw-r--r-- | django/contrib/gis/tests/geoapp/test_regress.py | 19 | ||||
| -rw-r--r-- | django/contrib/localflavor/fr/forms.py | 2 | ||||
| -rw-r--r-- | django/core/handlers/modpython.py | 4 | ||||
| -rw-r--r-- | django/core/management/__init__.py | 77 | ||||
| -rw-r--r-- | django/core/validators.py | 4 | ||||
| -rw-r--r-- | django/db/models/sql/expressions.py | 2 | ||||
| -rw-r--r-- | django/forms/fields.py | 1 | ||||
| -rw-r--r-- | django/utils/decorators.py | 122 | ||||
| -rw-r--r-- | django/views/decorators/cache.py | 33 |
15 files changed, 244 insertions, 119 deletions
diff --git a/django/conf/locale/he/LC_MESSAGES/django.mo b/django/conf/locale/he/LC_MESSAGES/django.mo Binary files differindex 8d8e328203..9a3e69c5c1 100644 --- a/django/conf/locale/he/LC_MESSAGES/django.mo +++ b/django/conf/locale/he/LC_MESSAGES/django.mo diff --git a/django/conf/locale/he/LC_MESSAGES/django.po b/django/conf/locale/he/LC_MESSAGES/django.po index 53b40132ba..ed048c76f2 100644 --- a/django/conf/locale/he/LC_MESSAGES/django.po +++ b/django/conf/locale/he/LC_MESSAGES/django.po @@ -391,7 +391,7 @@ msgstr "הוספת %s" #: contrib/admin/options.py:1003 #, python-format msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "הפריט %(name)s עם המקש %(key)r אינו קיים." +msgstr "הפריט %(name)s עם המפתח הראשי %(key)r אינו קיים." #: contrib/admin/options.py:860 #, python-format diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index b80aca10a3..798e596978 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -1,11 +1,12 @@ try: - from functools import update_wrapper + from functools import update_wrapper, wraps except ImportError: - from django.utils.functional import update_wrapper # Python 2.3, 2.4 fallback. + from django.utils.functional import update_wrapper, wraps # Python 2.3, 2.4 fallback. from django.contrib.auth import REDIRECT_FIELD_NAME from django.http import HttpResponseRedirect from django.utils.http import urlquote +from django.utils.decorators import auto_adapt_to_methods def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): """ @@ -13,9 +14,19 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE redirecting to the log-in page if necessary. The test should be a callable that takes the user object and returns True if the user passes. """ - def decorate(view_func): - return _CheckLogin(view_func, test_func, login_url, redirect_field_name) - return decorate + if not login_url: + from django.conf import settings + login_url = settings.LOGIN_URL + + def decorator(view_func): + def _wrapped_view(request, *args, **kwargs): + if test_func(request.user): + return view_func(request, *args, **kwargs) + path = urlquote(request.get_full_path()) + tup = login_url, redirect_field_name, path + return HttpResponseRedirect('%s?%s=%s' % tup) + return wraps(view_func)(_wrapped_view) + return auto_adapt_to_methods(decorator) def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME): """ @@ -36,46 +47,3 @@ def permission_required(perm, login_url=None): enabled, redirecting to the log-in page if necessary. """ return user_passes_test(lambda u: u.has_perm(perm), login_url=login_url) - -class _CheckLogin(object): - """ - Class that checks that the user passes the given test, redirecting to - the log-in page if necessary. If the test is passed, the view function - is invoked. The test should be a callable that takes the user object - and returns True if the user passes. - - We use a class here so that we can define __get__. This way, when a - _CheckLogin object is used as a method decorator, the view function - is properly bound to its instance. - """ - def __init__(self, view_func, test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): - if not login_url: - from django.conf import settings - login_url = settings.LOGIN_URL - self.view_func = view_func - self.test_func = test_func - self.login_url = login_url - self.redirect_field_name = redirect_field_name - - # We can't blindly apply update_wrapper because it udpates __dict__ and - # if the view function is already a _CheckLogin object then - # self.test_func and friends will get stomped. However, we also can't - # *not* update the wrapper's dict because then view function attributes - # don't get updated into the wrapper. So we need to split the - # difference: don't let update_wrapper update __dict__, but then update - # the (parts of) __dict__ that we care about ourselves. - update_wrapper(self, view_func, updated=()) - for k in view_func.__dict__: - if k not in self.__dict__: - self.__dict__[k] = view_func.__dict__[k] - - def __get__(self, obj, cls=None): - view_func = self.view_func.__get__(obj, cls) - return _CheckLogin(view_func, self.test_func, self.login_url, self.redirect_field_name) - - def __call__(self, request, *args, **kwargs): - if self.test_func(request.user): - return self.view_func(request, *args, **kwargs) - path = urlquote(request.get_full_path()) - tup = self.login_url, self.redirect_field_name, path - return HttpResponseRedirect('%s?%s=%s' % tup) diff --git a/django/contrib/comments/admin.py b/django/contrib/comments/admin.py index 3b1fb14bcc..c2f8e564f4 100644 --- a/django/contrib/comments/admin.py +++ b/django/contrib/comments/admin.py @@ -20,6 +20,7 @@ class CommentsAdmin(admin.ModelAdmin): list_filter = ('submit_date', 'site', 'is_public', 'is_removed') date_hierarchy = 'submit_date' ordering = ('-submit_date',) + raw_id_fields = ('user',) search_fields = ('comment', 'user__username', 'user_name', 'user_email', 'user_url', 'ip_address') # Only register the default admin if the model is the built-in comment model diff --git a/django/contrib/gis/db/models/sql/aggregates.py b/django/contrib/gis/db/models/sql/aggregates.py index 370182c721..b534288891 100644 --- a/django/contrib/gis/db/models/sql/aggregates.py +++ b/django/contrib/gis/db/models/sql/aggregates.py @@ -16,7 +16,7 @@ def convert_geom(wkt, geo_field): if SpatialBackend.postgis: def convert_extent(box): - # Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)"; + # Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)"; # parsing out and returning as a 4-tuple. ll, ur = box[4:-1].split(',') xmin, ymin = map(float, ll.split()) @@ -32,19 +32,28 @@ elif SpatialBackend.oracle: def convert_extent(clob): if clob: - # Oracle returns a polygon for the extent, we construct - # the 4-tuple from the coordinates in the polygon. - poly = SpatialBackend.Geometry(clob.read()) - shell = poly.shell - ll, ur = shell[0], shell[2] + # Generally, Oracle returns a polygon for the extent -- however, + # it can return a single point if there's only one Point in the + # table. + ext_geom = SpatialBackend.Geometry(clob.read()) + gtype = str(ext_geom.geom_type) + if gtype == 'Polygon': + # Construct the 4-tuple from the coordinates in the polygon. + shell = ext_geom.shell + ll, ur = shell[0][:2], shell[2][:2] + elif gtype == 'Point': + ll = ext_geom.coords[:2] + ur = ll + else: + raise Exception('Unexpected geometry type returned for extent: %s' % gtype) xmin, ymin = ll xmax, ymax = ur return (xmin, ymin, xmax, ymax) else: return None - + def convert_geom(clob, geo_field): - if clob: + if clob: return SpatialBackend.Geometry(clob.read(), geo_field.srid) else: return None @@ -73,7 +82,7 @@ class GeoAggregate(Aggregate): self.extra.setdefault('tolerance', 0.05) # Can't use geographic aggregates on non-geometry fields. - if not isinstance(self.source, GeometryField): + if not isinstance(self.source, GeometryField): raise ValueError('Geospatial aggregates only allowed on geometry fields.') # Making sure the SQL function is available for this spatial backend. @@ -87,7 +96,7 @@ class Collect(GeoAggregate): class Extent(GeoAggregate): is_extent = True sql_function = SpatialBackend.extent - + if SpatialBackend.oracle: # Have to change Extent's attributes here for Oracle. Extent.conversion_class = GeomField diff --git a/django/contrib/gis/shortcuts.py b/django/contrib/gis/shortcuts.py index e62e2b6bc8..a6fb8927d0 100644 --- a/django/contrib/gis/shortcuts.py +++ b/django/contrib/gis/shortcuts.py @@ -1,4 +1,5 @@ import cStringIO, zipfile +from django.conf import settings from django.http import HttpResponse from django.template import loader @@ -6,7 +7,7 @@ def compress_kml(kml): "Returns compressed KMZ from the given KML string." kmz = cStringIO.StringIO() zf = zipfile.ZipFile(kmz, 'a', zipfile.ZIP_DEFLATED) - zf.writestr('doc.kml', kml) + zf.writestr('doc.kml', kml.encode(settings.DEFAULT_CHARSET)) zf.close() kmz.seek(0) return kmz.read() diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py index ef8d44d988..043dace769 100644 --- a/django/contrib/gis/tests/geoapp/test_regress.py +++ b/django/contrib/gis/tests/geoapp/test_regress.py @@ -1,6 +1,7 @@ import os, unittest from django.contrib.gis.db.backend import SpatialBackend -from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_postgis +from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_postgis, no_spatialite +from django.contrib.gis.shortcuts import render_to_kmz from models import City class GeoRegressionTests(unittest.TestCase): @@ -16,3 +17,19 @@ class GeoRegressionTests(unittest.TestCase): self.assertEqual(pnt, City.objects.get(name='Pueblo').point) City.objects.filter(name='Pueblo').update(point=bak) self.assertEqual(bak, City.objects.get(name='Pueblo').point) + + def test02_kmz(self): + "Testing `render_to_kmz` with non-ASCII data, see #11624." + name = '\xc3\x85land Islands'.decode('iso-8859-1') + places = [{'name' : name, + 'description' : name, + 'kml' : '<Point><coordinates>5.0,23.0</coordinates></Point>' + }] + kmz = render_to_kmz('gis/kml/placemarks.kml', {'places' : places}) + + @no_spatialite + def test03_extent(self): + "Testing `extent` on a table with a single point, see #11827." + pnt = City.objects.get(name='Pueblo').point + ref_ext = (pnt.x, pnt.y, pnt.x, pnt.y) + self.assertEqual(ref_ext, City.objects.filter(name='Pueblo').extent()) diff --git a/django/contrib/localflavor/fr/forms.py b/django/contrib/localflavor/fr/forms.py index 7d782b609b..963eadc86a 100644 --- a/django/contrib/localflavor/fr/forms.py +++ b/django/contrib/localflavor/fr/forms.py @@ -28,7 +28,7 @@ class FRPhoneNumberField(Field): '0X XX XX XX XX'. """ default_error_messages = { - 'invalid': u'Phone numbers must be in 0X XX XX XX XX format.', + 'invalid': _('Phone numbers must be in 0X XX XX XX XX format.'), } def clean(self, value): diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py index c6dcf23e9a..b1e3e17227 100644 --- a/django/core/handlers/modpython.py +++ b/django/core/handlers/modpython.py @@ -134,8 +134,8 @@ class ModPythonRequest(http.HttpRequest): if not hasattr(self, '_meta'): self._meta = { 'AUTH_TYPE': self._req.ap_auth_type, - 'CONTENT_LENGTH': self._req.clength, # This may be wrong - 'CONTENT_TYPE': self._req.content_type, # This may be wrong + 'CONTENT_LENGTH': self._req.headers_in.get('content-length', 0), + 'CONTENT_TYPE': self._req.headers_in.get('content-type'), 'GATEWAY_INTERFACE': 'CGI/1.1', 'PATH_INFO': self.path_info, 'PATH_TRANSLATED': None, # Not supported diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 026e426862..60dcf727e4 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -261,6 +261,82 @@ class ManagementUtility(object): sys.exit(1) return klass + def autocomplete(self): + """ + Output completion suggestions for BASH. + + The output of this function is passed to BASH's `COMREPLY` variable and + treated as completion suggestions. `COMREPLY` expects a space + separated string as the result. + + The `COMP_WORDS` and `COMP_CWORD` BASH environment variables are used + to get information about the cli input. Please refer to the BASH + man-page for more information about this variables. + + Subcommand options are saved as pairs. A pair consists of + the long option string (e.g. '--exclude') and a boolean + value indicating if the option requires arguments. When printing to + stdout, a equal sign is appended to options which require arguments. + + Note: If debugging this function, it is recommended to write the debug + output in a separate file. Otherwise the debug output will be treated + and formatted as potential completion suggestions. + """ + # Don't complete if user hasn't sourced bash_completion file. + if not os.environ.has_key('DJANGO_AUTO_COMPLETE'): + return + + cwords = os.environ['COMP_WORDS'].split()[1:] + cword = int(os.environ['COMP_CWORD']) + + try: + curr = cwords[cword-1] + except IndexError: + curr = '' + + subcommands = get_commands().keys() + ['help'] + options = [('--help', None)] + + # subcommand + if cword == 1: + print ' '.join(filter(lambda x: x.startswith(curr), subcommands)) + # subcommand options + # special case: the 'help' subcommand has no options + elif cwords[0] in subcommands and cwords[0] != 'help': + subcommand_cls = self.fetch_command(cwords[0]) + # special case: 'runfcgi' stores additional options as + # 'key=value' pairs + if cwords[0] == 'runfcgi': + from django.core.servers.fastcgi import FASTCGI_OPTIONS + options += [(k, 1) for k in FASTCGI_OPTIONS] + # special case: add the names of installed apps to options + elif cwords[0] in ('dumpdata', 'reset', 'sql', 'sqlall', + 'sqlclear', 'sqlcustom', 'sqlindexes', + 'sqlreset', 'sqlsequencereset', 'test'): + try: + from django.conf import settings + # Get the last part of the dotted path as the app name. + options += [(a.split('.')[-1], 0) for a in settings.INSTALLED_APPS] + except ImportError: + # Fail silently if DJANGO_SETTINGS_MODULE isn't set. The + # user will find out once they execute the command. + pass + options += [(s_opt.get_opt_string(), s_opt.nargs) for s_opt in + subcommand_cls.option_list] + # filter out previously specified options from available options + prev_opts = [x.split('=')[0] for x in cwords[1:cword-1]] + options = filter(lambda (x, v): x not in prev_opts, options) + + # filter options by current input + options = [(k, v) for k, v in options if k.startswith(curr)] + for option in options: + opt_label = option[0] + # append '=' to options which require args + if option[1]: + opt_label += '=' + print opt_label + sys.exit(1) + def execute(self): """ Given the command-line arguments, this figures out which subcommand is @@ -272,6 +348,7 @@ class ManagementUtility(object): parser = LaxOptionParser(usage="%prog subcommand [options] [args]", version=get_version(), option_list=BaseCommand.option_list) + self.autocomplete() try: options, args = parser.parse_args(self.argv) handle_default_options(options) diff --git a/django/core/validators.py b/django/core/validators.py index 225dd5cba2..4515ca7a4f 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -40,7 +40,7 @@ class RegexValidator(object): class URLValidator(RegexValidator): regex = re.compile( r'^https?://' # http:// or https:// - r'(?:(?:[A-Z0-9]+(?:-*[A-Z0-9]+)*\.)+[A-Z]{2,6}|' #domain... + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain... r'localhost|' #localhost... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip r'(?::\d+)?' # optional port @@ -81,7 +81,7 @@ def validate_integer(value): email_re = re.compile( r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string - r')@(?:[A-Z0-9]+(?:-*[A-Z0-9]+)*\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain + r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE) # domain validate_email = RegexValidator(email_re, _(u'Enter a valid e-mail address.'), 'invalid') slug_re = re.compile(r'^[-\w]+$') diff --git a/django/db/models/sql/expressions.py b/django/db/models/sql/expressions.py index 920cbffe73..0914c2b3c1 100644 --- a/django/db/models/sql/expressions.py +++ b/django/db/models/sql/expressions.py @@ -66,7 +66,7 @@ class SQLEvaluator(object): else: sql, params = '%s', (child,) - if hasattr(child, 'children') > 1: + if len(getattr(child, 'children', [])) > 1: format = '(%s)' else: format = '%s' diff --git a/django/forms/fields.py b/django/forms/fields.py index 3197260fb6..b4717e327f 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -32,7 +32,6 @@ from util import ErrorList from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, \ FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, \ DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget -from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile __all__ = ( 'Field', 'CharField', 'IntegerField', diff --git a/django/utils/decorators.py b/django/utils/decorators.py index 8fc4c1d96a..4636a2d040 100644 --- a/django/utils/decorators.py +++ b/django/utils/decorators.py @@ -2,60 +2,86 @@ import types try: - from functools import wraps + from functools import wraps, update_wrapper except ImportError: - from django.utils.functional import wraps # Python 2.3, 2.4 fallback. + from django.utils.functional import wraps, update_wrapper # Python 2.3, 2.4 fallback. -def decorator_from_middleware(middleware_class): +class MethodDecoratorAdaptor(object): """ - Given a middleware class (not an instance), returns a view decorator. This - lets you use middleware functionality on a per-view basis. + Generic way of creating decorators that adapt to being + used on methods """ - def _decorator_from_middleware(*args, **kwargs): - # For historical reasons, these "decorators" are also called as - # dec(func, *args) instead of dec(*args)(func). We handle both forms - # for backwards compatibility. - has_func = True - try: - view_func = kwargs.pop('view_func') - except KeyError: - if len(args): - view_func, args = args[0], args[1:] - else: - has_func = False - if not (has_func and isinstance(view_func, types.FunctionType)): - # We are being called as a decorator. - if has_func: - args = (view_func,) + args - middleware = middleware_class(*args, **kwargs) + def __init__(self, decorator, func): + update_wrapper(self, func) + # NB: update the __dict__ first, *then* set + # our own .func and .decorator, in case 'func' is actually + # another MethodDecoratorAdaptor object, which has its + # 'func' and 'decorator' attributes in its own __dict__ + self.decorator = decorator + self.func = func + def __call__(self, *args, **kwargs): + return self.decorator(self.func)(*args, **kwargs) + def __get__(self, instance, owner): + return self.decorator(self.func.__get__(instance, owner)) - def decorator_func(fn): - return _decorator_from_middleware(fn, *args, **kwargs) - return decorator_func +def auto_adapt_to_methods(decorator): + """ + Takes a decorator function, and returns a decorator-like callable that can + be used on methods as well as functions. + """ + def adapt(func): + return MethodDecoratorAdaptor(decorator, func) + return wraps(decorator)(adapt) - middleware = middleware_class(*args, **kwargs) +def decorator_from_middleware_with_args(middleware_class): + """ + Like decorator_from_middleware, but returns a function + that accepts the arguments to be passed to the middleware_class. + Use like:: - def _wrapped_view(request, *args, **kwargs): - if hasattr(middleware, 'process_request'): - result = middleware.process_request(request) - if result is not None: - return result - if hasattr(middleware, 'process_view'): - result = middleware.process_view(request, view_func, args, kwargs) - if result is not None: - return result - try: - response = view_func(request, *args, **kwargs) - except Exception, e: - if hasattr(middleware, 'process_exception'): - result = middleware.process_exception(request, e) + cache_page = decorator_from_middleware_with_args(CacheMiddleware) + # ... + + @cache_page(3600) + def my_view(request): + # ... + """ + return make_middleware_decorator(middleware_class) + +def decorator_from_middleware(middleware_class): + """ + Given a middleware class (not an instance), returns a view decorator. This + lets you use middleware functionality on a per-view basis. The middleware + is created with no params passed. + """ + return make_middleware_decorator(middleware_class)() + +def make_middleware_decorator(middleware_class): + def _make_decorator(*m_args, **m_kwargs): + middleware = middleware_class(*m_args, **m_kwargs) + def _decorator(view_func): + def _wrapped_view(request, *args, **kwargs): + if hasattr(middleware, 'process_request'): + result = middleware.process_request(request) + if result is not None: + return result + if hasattr(middleware, 'process_view'): + result = middleware.process_view(request, view_func, args, kwargs) + if result is not None: + return result + try: + response = view_func(request, *args, **kwargs) + except Exception, e: + if hasattr(middleware, 'process_exception'): + result = middleware.process_exception(request, e) + if result is not None: + return result + raise + if hasattr(middleware, 'process_response'): + result = middleware.process_response(request, response) if result is not None: return result - raise - if hasattr(middleware, 'process_response'): - result = middleware.process_response(request, response) - if result is not None: - return result - return response - return wraps(view_func)(_wrapped_view) - return _decorator_from_middleware + return response + return wraps(view_func)(_wrapped_view) + return auto_adapt_to_methods(_decorator) + return _make_decorator diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py index 8b620c1345..f051f096a0 100644 --- a/django/views/decorators/cache.py +++ b/django/views/decorators/cache.py @@ -16,11 +16,37 @@ try: except ImportError: from django.utils.functional import wraps # Python 2.3, 2.4 fallback. -from django.utils.decorators import decorator_from_middleware +from django.utils.decorators import decorator_from_middleware_with_args, auto_adapt_to_methods from django.utils.cache import patch_cache_control, add_never_cache_headers from django.middleware.cache import CacheMiddleware -cache_page = decorator_from_middleware(CacheMiddleware) +def cache_page(*args, **kwargs): + # We need backwards compatibility with code which spells it this way: + # def my_view(): pass + # my_view = cache_page(my_view, 123) + # and this way: + # my_view = cache_page(123)(my_view) + # and this: + # my_view = cache_page(my_view, 123, key_prefix="foo") + # and this: + # my_view = cache_page(123, key_prefix="foo")(my_view) + # and possibly this way (?): + # my_view = cache_page(123, my_view) + + # We also add some asserts to give better error messages in case people are + # using other ways to call cache_page that no longer work. + key_prefix = kwargs.pop('key_prefix', None) + assert not kwargs, "The only keyword argument accepted is key_prefix" + if len(args) > 1: + assert len(args) == 2, "cache_page accepts at most 2 arguments" + if callable(args[0]): + return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[1], key_prefix=key_prefix)(args[0]) + elif callable(args[1]): + return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], key_prefix=key_prefix)(args[1]) + else: + assert False, "cache_page must be passed either a single argument (timeout) or a view function and a timeout" + else: + return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], key_prefix=key_prefix) def cache_control(**kwargs): @@ -33,7 +59,7 @@ def cache_control(**kwargs): return wraps(viewfunc)(_cache_controlled) - return _cache_controller + return auto_adapt_to_methods(_cache_controller) def never_cache(view_func): """ @@ -45,3 +71,4 @@ def never_cache(view_func): add_never_cache_headers(response) return response return wraps(view_func)(_wrapped_view_func) +never_cache = auto_adapt_to_methods(never_cache) |
