summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorHonza Král <honza.kral@gmail.com>2009-10-12 10:16:17 +0000
committerHonza Král <honza.kral@gmail.com>2009-10-12 10:16:17 +0000
commitdfe495fbe8e360ee3b3cd8b29e55ee19d86fc9d2 (patch)
tree16bccad252c6fd2b00e734f275594ae159596e70 /django
parent83a3588ff712d5fe44e9692f5cb6a1d020f3ab2f (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.mobin72623 -> 72636 bytes
-rw-r--r--django/conf/locale/he/LC_MESSAGES/django.po2
-rw-r--r--django/contrib/auth/decorators.py64
-rw-r--r--django/contrib/comments/admin.py1
-rw-r--r--django/contrib/gis/db/models/sql/aggregates.py29
-rw-r--r--django/contrib/gis/shortcuts.py3
-rw-r--r--django/contrib/gis/tests/geoapp/test_regress.py19
-rw-r--r--django/contrib/localflavor/fr/forms.py2
-rw-r--r--django/core/handlers/modpython.py4
-rw-r--r--django/core/management/__init__.py77
-rw-r--r--django/core/validators.py4
-rw-r--r--django/db/models/sql/expressions.py2
-rw-r--r--django/forms/fields.py1
-rw-r--r--django/utils/decorators.py122
-rw-r--r--django/views/decorators/cache.py33
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
index 8d8e328203..9a3e69c5c1 100644
--- a/django/conf/locale/he/LC_MESSAGES/django.mo
+++ b/django/conf/locale/he/LC_MESSAGES/django.mo
Binary files differ
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)