From 43ad69e24e556c26cdefd96f84a8d06e086ff854 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Sun, 23 Oct 2005 22:42:44 +0000 Subject: [PATCH 01/17] Fixed #684 -- Fixed login_required and user_passes_test decorators. Thanks, rjwittams git-svn-id: http://code.djangoproject.com/svn/django/trunk@1004 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/decorators/auth.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/django/views/decorators/auth.py b/django/views/decorators/auth.py index f543a6aa48..a5a28bb0b2 100644 --- a/django/views/decorators/auth.py +++ b/django/views/decorators/auth.py @@ -1,19 +1,22 @@ -def user_passes_test(view_func, test_func): +def user_passes_test(test_func): """ Decorator for views that checks that the user passes the given test, 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. """ - from django.views.auth.login import redirect_to_login - def _checklogin(request, *args, **kwargs): - if test_func(request.user): - return view_func(request, *args, **kwargs) - return redirect_to_login(request.path) - return _checklogin + def _dec(view_func): + def _checklogin(request, *args, **kwargs): + from django.views.auth.login import redirect_to_login + if test_func(request.user): + return view_func(request, *args, **kwargs) + return redirect_to_login(request.path) + return _checklogin + return _dec -def login_required(view_func): +login_required = user_passes_test(lambda u: not u.is_anonymous()) +login_required.__doc__ = ( """ Decorator for views that checks that the user is logged in, redirecting to the log-in page if necessary. """ - return user_passes_test(lambda u: not u.is_anonymous()) + ) From c604de5a5db2cff33ad78ad8565fcb9b05e405f6 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Sun, 23 Oct 2005 22:43:24 +0000 Subject: [PATCH 02/17] Added 'django-admin.py installperms' command git-svn-id: http://code.djangoproject.com/svn/django/trunk@1005 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/bin/django-admin.py | 3 ++- django/core/management.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/django/bin/django-admin.py b/django/bin/django-admin.py index 0d021ba172..8cf042a5c1 100755 --- a/django/bin/django-admin.py +++ b/django/bin/django-admin.py @@ -11,6 +11,7 @@ ACTION_MAPPING = { 'init': management.init, 'inspectdb': management.inspectdb, 'install': management.install, + 'installperms': management.installperms, 'runserver': management.runserver, 'sql': management.get_sql_create, 'sqlall': management.get_sql_all, @@ -24,7 +25,7 @@ ACTION_MAPPING = { 'validate': management.validate, } -NO_SQL_TRANSACTION = ('adminindex', 'createcachetable', 'dbcheck', 'install', 'sqlindexes') +NO_SQL_TRANSACTION = ('adminindex', 'createcachetable', 'dbcheck', 'install', 'installperms', 'sqlindexes') def get_usage(): """ diff --git a/django/core/management.py b/django/core/management.py index 3b973c28da..ca94683cff 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -345,6 +345,27 @@ The full error: %s\n""" % \ install.help_doc = "Executes ``sqlall`` for the given model module name(s) in the current database." install.args = APP_ARGS +def installperms(mod): + "Installs any permissions for the given model, if needed." + from django.models.auth import permissions + from django.models.core import packages + num_added = 0 + package = packages.get_object(pk=mod._MODELS[0]._meta.app_label) + for klass in mod._MODELS: + opts = klass._meta + for codename, name in _get_all_permissions(opts): + try: + permissions.get_object(name__exact=name, codename__exact=codename, package__label__exact=package.label) + except permissions.PermissionDoesNotExist: + p = permissions.Permission(name=name, package=package, codename=codename) + p.save() + print "Added permission '%r'." % p + num_added += 1 + if not num_added: + print "No permissions were added, because all necessary permissions were already installed." +installperms.help_doc = "Installs any permissions for the given model module name(s), if needed." +installperms.args = APP_ARGS + def _start_helper(app_or_project, name, directory, other_name=''): other = {'project': 'app', 'app': 'project'}[app_or_project] if not _is_valid_dir_name(name): From d4abd6fb5265d711056a4350745a870ef39c5439 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Sun, 23 Oct 2005 22:43:54 +0000 Subject: [PATCH 03/17] Small massaging of docs/model-api.txt git-svn-id: http://code.djangoproject.com/svn/django/trunk@1006 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/model-api.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/model-api.txt b/docs/model-api.txt index fa6d8f10e6..3522585e7a 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -750,11 +750,12 @@ Here's a list of all possible ``META`` options. No options are required. Adding ``permissions`` Extra permissions to enter into the permissions table when creating this object. Add, delete and change permissions are automatically created for - each object. This option specifies extra permissions:: + each object that has ``admin`` set. This example specifies an extra + permission, ``can_deliver_pizzas``:: permissions = (("can_deliver_pizzas", "Can deliver pizzas"),) - This is a list of 2-tuples of + This is a list or tuple of 2-tuples in the format ``(permission_code, human_readable_permission_name)``. ``unique_together`` From e0d2793b7b6363196599d67316c1dac8c6c725e7 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 25 Oct 2005 01:44:14 +0000 Subject: [PATCH 04/17] Fixed #687 -- Fixed bug in floatformat template filter and added unit tests. Thanks, Sune git-svn-id: http://code.djangoproject.com/svn/django/trunk@1007 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/template/defaultfilters.py | 14 +++++++------- tests/othertests/defaultfilters.py | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 tests/othertests/defaultfilters.py diff --git a/django/core/template/defaultfilters.py b/django/core/template/defaultfilters.py index 746fa9b873..37e79ece3f 100644 --- a/django/core/template/defaultfilters.py +++ b/django/core/template/defaultfilters.py @@ -24,15 +24,15 @@ def fix_ampersands(value, _): def floatformat(text, _): """ - Displays a floating point number as 34.2 (with one decimal place) - but + Displays a floating point number as 34.2 (with one decimal place) -- but only if there's a point to be displayed """ - from math import modf - if not text: - return '' - if modf(float(text))[0] < 0.1: - return text - return "%.1f" % float(text) + f = float(text) + m = f - int(f) + if m: + return '%.1f' % f + else: + return '%d' % int(f) def linenumbers(value, _): "Displays text with line numbers" diff --git a/tests/othertests/defaultfilters.py b/tests/othertests/defaultfilters.py new file mode 100644 index 0000000000..d440e25dd5 --- /dev/null +++ b/tests/othertests/defaultfilters.py @@ -0,0 +1,20 @@ +""" +>>> floatformat(7.7, None) +'7.7' +>>> floatformat(7.0, None) +'7' +>>> floatformat(0.7, None) +'0.7' +>>> floatformat(0.07, None) +'0.1' +>>> floatformat(0.007, None) +'0.0' +>>> floatformat(0.0, None) +'0' +""" + +from django.core.template.defaultfilters import * + +if __name__ == '__main__': + import doctest + doctest.testmod() From 1ed99862c6215ddcd22b3e7797415afd8d49b8e0 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 25 Oct 2005 01:47:34 +0000 Subject: [PATCH 05/17] Fixed #679 -- 'collapse' admin row class is now processed correctly when more than one tuple is in the admin.fields list. Thanks, malcolm git-svn-id: http://code.djangoproject.com/svn/django/trunk@1008 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/views/main.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 8ecf0a2c97..e3dd5afdcf 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -556,14 +556,14 @@ def _get_template(opts, app_label, add=False, change=False, show_delete=False, f if not seen_collapse and 'collapse' in options.get('classes', ''): seen_collapse = True javascript_imports.append('%sjs/admin/CollapsedFieldsets.js' % ADMIN_MEDIA_PREFIX) - try: - for field_list in options['fields']: + for field_list in options['fields']: + try: for f in field_list: if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface: javascript_imports.extend(['%sjs/SelectBox.js' % ADMIN_MEDIA_PREFIX, '%sjs/SelectFilter2.js' % ADMIN_MEDIA_PREFIX]) raise StopIteration - except StopIteration: - break + except StopIteration: + break for j in javascript_imports: t.append('' % j) From 5c3d1ec163ecbb6209965d1ea32fe4d380dcc9c6 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 25 Oct 2005 01:51:57 +0000 Subject: [PATCH 06/17] Fixed #677 -- db.queries is now reset per request. Thanks, seancazzell git-svn-id: http://code.djangoproject.com/svn/django/trunk@1009 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/handlers/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 9b541b36db..190a8b02c2 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -48,6 +48,9 @@ class BaseHandler: from django.core.mail import mail_admins from django.conf.settings import DEBUG, INTERNAL_IPS, ROOT_URLCONF + # Reset query list per request. + db.db.queries = [] + # Apply request middleware for middleware_method in self._request_middleware: response = middleware_method(request) From 3ad82eff77ed2d30d74413a7b7cff006bbd8beb2 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 25 Oct 2005 02:01:10 +0000 Subject: [PATCH 07/17] Improved autoreloader to ignore files that might be in eggs. Refs #596 git-svn-id: http://code.djangoproject.com/svn/django/trunk@1010 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/autoreload.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index 039ba68cfb..04e319c1c7 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -37,6 +37,8 @@ def reloader_thread(): mtimes = {} while RUN_RELOADER: for filename in filter(lambda v: v, map(lambda m: getattr(m, "__file__", None), sys.modules.values())) + reloadFiles: + if not os.path.exists(filename): + continue # File might be in an egg, so it can't be reloaded. if filename.endswith(".pyc"): filename = filename[:-1] mtime = os.stat(filename).st_mtime From d310b108f451d3203b69956953e4c0f2eb55c291 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 25 Oct 2005 02:02:58 +0000 Subject: [PATCH 08/17] Changed global_settings.TEMPLATE_LOADERS to be same as default project_template.settings.TEMPLATE_LOADERS git-svn-id: http://code.djangoproject.com/svn/django/trunk@1011 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/global_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 52acb51de4..40e230b04c 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -65,8 +65,8 @@ TEMPLATE_FILE_EXTENSION = '.html' # See the comments in django/core/template/loader.py for interface # documentation. TEMPLATE_LOADERS = ( -# 'django.core.template.loaders.app_directories.load_template_source', 'django.core.template.loaders.filesystem.load_template_source', + 'django.core.template.loaders.app_directories.load_template_source', # 'django.core.template.loaders.eggs.load_template_source', ) From c3377c1eae8c330e86999ce2b58dc8e7339f40cb Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Wed, 26 Oct 2005 14:01:53 +0000 Subject: [PATCH 09/17] Fixed shortcut redirect handler git-svn-id: http://code.djangoproject.com/svn/django/trunk@1012 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/defaults.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/django/views/defaults.py b/django/views/defaults.py index decc220cf7..45e5636c5a 100644 --- a/django/views/defaults.py +++ b/django/views/defaults.py @@ -1,10 +1,11 @@ from django.core.exceptions import Http404, ObjectDoesNotExist from django.core.template import Context, loader -from django.models.core import sites +from django.models.core import sites, contenttypes from django.utils import httpwrappers def shortcut(request, content_type_id, object_id): - from django.models.core import contenttypes + """Redirect to an object's page based on a content-type ID and an object ID""" + # Look up the object, making sure it's got a get_absolute_url() function. try: content_type = contenttypes.get_object(pk=content_type_id) obj = content_type.get_object_for_this_type(pk=object_id) @@ -14,25 +15,37 @@ def shortcut(request, content_type_id, object_id): absurl = obj.get_absolute_url() except AttributeError: raise Http404, "%s objects don't have get_absolute_url() methods" % content_type.name + + # Try to figure out the object's domain so we can do a cross-site redirect + # if necessary + + # If the object actually defines a domain, we're done. if absurl.startswith('http://'): return httpwrappers.HttpResponseRedirect(absurl) + object_domain = None + + # Next, look for an many-to-many relationship to sites if hasattr(obj, 'get_site_list'): site_list = obj.get_site_list() if site_list: object_domain = site_list[0].domain + + # Next, look for a many-to-one relationship to sites elif hasattr(obj, 'get_site'): try: object_domain = obj.get_site().domain except sites.SiteDoesNotExist: pass - try: - object_domain = sites.get_current().domain - except sites.SiteDoesNotExist: - pass - if not object_domain: - return httpwrappers.HttpResponseRedirect(absurl) - return httpwrappers.HttpResponseRedirect('http://%s%s' % (object_domain, absurl)) + + # Then, fall back to the current site (if possible) + else: + try: + object_domain = sites.get_current().domain + except sites.SiteDoesNotExist: + # Finally, give up and use a URL without the domain name + return httpwrappers.HttpResponseRedirect(obj.get_absolute_url()) + return httpwrappers.HttpResponseRedirect('http://%s%s' % (object_domain, obj.get_absolute_url())) def page_not_found(request): """ From e7c870c36ea6f567e7834ce0e74ea0257c8631b7 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Fri, 28 Oct 2005 01:30:30 +0000 Subject: [PATCH 10/17] Fixed #703: added decorators to require that view be called with a given HTTP REQUEST_METHOD git-svn-id: http://code.djangoproject.com/svn/django/trunk@1016 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/decorators/http.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/django/views/decorators/http.py b/django/views/decorators/http.py index 13062b630f..b9b6bac757 100644 --- a/django/views/decorators/http.py +++ b/django/views/decorators/http.py @@ -1,9 +1,35 @@ """ -Decorator for views that supports conditional get on ETag and Last-Modified -headers. +Decorators for views based on HTTP headers. """ from django.utils.decorators import decorator_from_middleware from django.middleware.http import ConditionalGetMiddleware +from django.utils.httpwrappers import HttpResponseForbidden conditional_page = decorator_from_middleware(ConditionalGetMiddleware) + +def require_http_methods(request_method_list): + """ + Decorator to make a view only accept particular request methods. Usage:: + + @require_http_methods(["GET", "POST"]) + def my_view(request): + # I can assume now that only GET or POST requests make it this far + # ... + + Note that request methods ARE case sensitive. + """ + def decorator(func): + def inner(request, *args, **kwargs): + method = request.META.get("REQUEST_METHOD", None) + if method not in request_method_list: + raise HttpResponseForbidden("REQUEST_METHOD '%s' not allowed" % method) + return func(request, *args, **kwargs) + return inner + return decorator + +require_GET = require_http_methods(["GET"]) +require_GET.__doc__ = "Decorator to require that a view only accept the GET method." + +require_POST = require_http_methods(["POST"]) +require_POST.__doc__ = "Decorator to require that a view only accept the POST method." \ No newline at end of file From c6f375fd1124562cde131c9785e7d7f6771ef7f2 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Fri, 28 Oct 2005 03:41:39 +0000 Subject: [PATCH 11/17] Fixed #700 -- urlify.js now uses hyphens instead of underscores git-svn-id: http://code.djangoproject.com/svn/django/trunk@1017 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/media/js/urlify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/admin/media/js/urlify.js b/django/contrib/admin/media/js/urlify.js index 1e0f04b9ab..5cdb6890c4 100644 --- a/django/contrib/admin/media/js/urlify.js +++ b/django/contrib/admin/media/js/urlify.js @@ -9,7 +9,7 @@ function URLify(s, num_chars) { s = s.replace(r, ''); s = s.replace(/[^\w\s-]/g, ''); // remove unneeded chars s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces - s = s.replace(/\s+/g, '_'); // convert spaces to underscores + s = s.replace(/\s+/g, '-'); // convert spaces to hyphens s = s.toLowerCase(); // convert to lowercase return s.substring(0, num_chars);// trim to first num_chars chars } \ No newline at end of file From b5a6ff5bdd70b5d35db473b3519866537fec0088 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Fri, 28 Oct 2005 03:45:51 +0000 Subject: [PATCH 12/17] Fixed #488 -- removetags template filter now removes tags without a space before the final slash git-svn-id: http://code.djangoproject.com/svn/django/trunk@1018 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/template/defaultfilters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/core/template/defaultfilters.py b/django/core/template/defaultfilters.py index 37e79ece3f..2604caf7f9 100644 --- a/django/core/template/defaultfilters.py +++ b/django/core/template/defaultfilters.py @@ -175,7 +175,7 @@ def removetags(value, tags): "Removes a space separated list of [X]HTML tags from the output" tags = [re.escape(tag) for tag in tags.split()] tags_re = '(%s)' % '|'.join(tags) - starttag_re = re.compile('<%s(>|(\s+[^>]*>))' % tags_re) + starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re) endtag_re = re.compile('' % tags_re) value = starttag_re.sub('', value) value = endtag_re.sub('', value) From 67d490a61dc5ee42b972f6c64bf589fbfc8db83f Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Fri, 28 Oct 2005 03:48:33 +0000 Subject: [PATCH 13/17] Fixed #701 -- contrib.admin.views.doc now uses get_internal_type() git-svn-id: http://code.djangoproject.com/svn/django/trunk@1019 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/views/doc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/views/doc.py b/django/contrib/admin/views/doc.py index 166ecdff2d..e4d0f1c648 100644 --- a/django/contrib/admin/views/doc.py +++ b/django/contrib/admin/views/doc.py @@ -288,9 +288,9 @@ DATA_TYPE_MAPPING = { def get_readable_field_data_type(field): # ForeignKey is a special case. Use the field type of the relation. - if field.__class__.__name__ == 'ForeignKey': + if field.get_internal_type() == 'ForeignKey': field = field.rel.get_related_field() - return DATA_TYPE_MAPPING[field.__class__.__name__] % field.__dict__ + return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__ def extract_views_from_urlpatterns(urlpatterns, base=''): """ From f12e3243326a2b6b0d09206b373b34e028eab25c Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Sat, 29 Oct 2005 17:00:20 +0000 Subject: [PATCH 14/17] Fixed #612 - added cache control headers (thanks, hugo) git-svn-id: http://code.djangoproject.com/svn/django/trunk@1020 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/cache.py | 42 ++++++++++++++++++++++++++++++-- django/views/decorators/cache.py | 16 ++++++++++++ docs/cache.txt | 34 ++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/django/utils/cache.py b/django/utils/cache.py index fcd0825a22..631ea8f08d 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -21,6 +21,45 @@ import datetime, md5, re from django.conf import settings from django.core.cache import cache +cc_delim_re = re.compile(r'\s*,\s*') +def patch_cache_control(response, **kwargs): + """ + This function patches the Cache-Control header by adding all + keyword arguments to it. The transformation is as follows: + + - all keyword parameter names are turned to lowercase and + all _ will be translated to - + - if the value of a parameter is True (exatly True, not just a + true value), only the parameter name is added to the header + - all other parameters are added with their value, after applying + str to it. + """ + + def dictitem(s): + t = s.split('=',1) + if len(t) > 1: + return (t[0].lower().replace('-', '_'), t[1]) + else: + return (t[0].lower().replace('-', '_'), True) + + def dictvalue(t): + if t[1] == True: + return t[0] + else: + return t[0] + '=' + str(t[1]) + + if response.has_header('Cache-Control'): + print response['Cache-Control'] + cc = cc_delim_re.split(response['Cache-Control']) + print cc + cc = dict([dictitem(el) for el in cc]) + else: + cc = {} + for (k,v) in kwargs.items(): + cc[k.replace('_', '-')] = v + cc = ', '.join([dictvalue(el) for el in cc.items()]) + response['Cache-Control'] = cc + vary_delim_re = re.compile(r',\s*') def patch_response_headers(response, cache_timeout=None): @@ -43,8 +82,7 @@ def patch_response_headers(response, cache_timeout=None): response['Last-Modified'] = now.strftime('%a, %d %b %Y %H:%M:%S GMT') if not response.has_header('Expires'): response['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT') - if not response.has_header('Cache-Control'): - response['Cache-Control'] = 'max-age=%d' % cache_timeout + patch_cache_control(response, max_age=cache_timeout) def patch_vary_headers(response, newheaders): """ diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py index 09f9a0139f..f86372cf4e 100644 --- a/django/views/decorators/cache.py +++ b/django/views/decorators/cache.py @@ -10,8 +10,24 @@ example, as that is unique across a Django project. Additionally, all headers from the response's Vary header will be taken into account on caching -- just like the middleware does. """ +import re from django.utils.decorators import decorator_from_middleware +from django.utils.cache import patch_cache_control from django.middleware.cache import CacheMiddleware cache_page = decorator_from_middleware(CacheMiddleware) + +def cache_control(**kwargs): + + def _cache_controller(viewfunc): + + def _cache_controlled(request, *args, **kw): + response = viewfunc(request, *args, **kw) + patch_cache_control(response, **kwargs) + return response + + return _cache_controlled + + return _cache_controller + diff --git a/docs/cache.txt b/docs/cache.txt index f15da2660b..7db42db249 100644 --- a/docs/cache.txt +++ b/docs/cache.txt @@ -270,6 +270,40 @@ and a list/tuple of header names as its second argument. .. _`HTTP Vary headers`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44 +Controlling cache: Using Vary headers +===================================== + +Another problem with caching is the privacy of data, and the question where data can +be stored in a cascade of caches. A user usually faces two kinds of caches: his own +browser cache (a private cache) and his providers cache (a public cache). A public cache +is used by multiple users and controlled by someone else. This poses problems with private +(in the sense of sensitive) data - you don't want your social security number or your +banking account numbers stored in some public cache. So web applications need a way +to tell the caches what data is private and what is public. + +Other aspects are the definition how long a page should be cached at max, or wether the +cache should allways check for newer versions and only deliver the cache content when +there were no changes (some caches might deliver cached content even if the server page +changed - just because the cache copy isn't yet expired). + +So there are a multitude of options you can control for your pages. This is where the +Cache-Control header (more infos in `HTTP Cache-Control headers`_) comes in. The usage +is quite simple:: + + @cache_control(private=True, must_revalidate=True, max_age=3600) + def my_view(request): + ... + +This would define the view as private, to be revalidated on every access and cache +copies will only be stored for 3600 seconds at max. + +The caching middleware already set's this header up with a max-age of the CACHE_MIDDLEWARE_SETTINGS +setting. And the cache_page decorator does the same. The cache_control decorator correctly merges +different values into one big header, though. But you should take into account that middlewares +might overwrite some of your headers or set their own defaults if you don't give that header yourself. + +.. _`HTTP Cache-Control headers`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + Other optimizations =================== From 2822f71d08f3a94a3e26bb80fa1dbcef7bf2f1db Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Sat, 29 Oct 2005 17:05:19 +0000 Subject: [PATCH 15/17] Fixed #140: memcached backends may now use multiple servers git-svn-id: http://code.djangoproject.com/svn/django/trunk@1021 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/cache.py | 6 ++++-- docs/cache.txt | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/django/core/cache.py b/django/core/cache.py index 6391304158..caeb315f05 100644 --- a/django/core/cache.py +++ b/django/core/cache.py @@ -13,7 +13,9 @@ settings.CACHE_BACKEND and use that to create and load a cache object. The CACHE_BACKEND setting is a quasi-URI; examples are: memcached://127.0.0.1:11211/ A memcached backend; the server is running - on localhost port 11211. + on localhost port 11211. You can use + multiple memcached servers by separating + them with semicolons. db://tablename/ A database backend in a table named "tablename". This table should be created @@ -134,7 +136,7 @@ else: "Memcached cache backend." def __init__(self, server, params): _Cache.__init__(self, params) - self._cache = memcache.Client([server]) + self._cache = memcache.Client(server.split(';')) def get(self, key, default=None): val = self._cache.get(key) diff --git a/docs/cache.txt b/docs/cache.txt index 7db42db249..c1b1352bca 100644 --- a/docs/cache.txt +++ b/docs/cache.txt @@ -29,7 +29,9 @@ Examples: CACHE_BACKEND Explanation ============================== =========================================== memcached://127.0.0.1:11211/ A memcached backend; the server is running - on localhost port 11211. + on localhost port 11211. You can use + multiple memcached servers by separating + them with semicolons. db://tablename/ A database backend in a table named "tablename". This table should be created From 7e0719efa6b579270601692f1125f88dd1e0002c Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Sat, 29 Oct 2005 17:10:51 +0000 Subject: [PATCH 16/17] Fixed #675: PasswordFields now respect length and maxlength params git-svn-id: http://code.djangoproject.com/svn/django/trunk@1022 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/formfields.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/django/core/formfields.py b/django/core/formfields.py index 9b9f0af1d1..48238cf050 100644 --- a/django/core/formfields.py +++ b/django/core/formfields.py @@ -215,6 +215,7 @@ class FormField: #################### class TextField(FormField): + input_type = "text" def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[]): self.field_name = field_name self.length, self.maxlength = length, maxlength @@ -237,8 +238,8 @@ class TextField(FormField): maxlength = 'maxlength="%s" ' % self.maxlength if isinstance(data, unicode): data = data.encode(DEFAULT_CHARSET) - return '' % \ - (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '', + return '' % \ + (self.input_type, FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '', self.field_name, self.length, escape(data), maxlength) def html2python(data): @@ -246,11 +247,7 @@ class TextField(FormField): html2python = staticmethod(html2python) class PasswordField(TextField): - def render(self, data): - # value is always blank because we never want to redisplay it - return '' % \ - (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '', - self.field_name) + input_type = "password" class LargeTextField(TextField): def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=[], maxlength=None): From fb55717a09fb7f92c76d1af23b2717b7a0610d03 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Sun, 30 Oct 2005 14:35:44 +0000 Subject: [PATCH 17/17] Documented {{{singular}}} argument for m2m relationships (fixes #664). git-svn-id: http://code.djangoproject.com/svn/django/trunk@1025 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/model-api.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/model-api.txt b/docs/model-api.txt index 3522585e7a..edf94dd17c 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -621,6 +621,14 @@ the relationship should work. All are optional: vertically). ``limit_choices_to`` See the description under ``ForeignKey`` above. + + ``singular`` The singular name of the field. Use to name the ``get_*`` + methods: in the example above, Django gives the ``Pizza`` + objects a ``get_topping_list()`` method, where ``topping`` + is the default ``singular`` value derived from the lowercase + version of the class being linked to. Use the singular + parameter to change this, which is if you want one model to + have multiple ``ManyToMany`` relationships to another model. ======================= ============================================================ One-to-one relationships