From cbe24263692641af8908da21d1a2d333100a468a Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Mon, 31 Oct 2005 03:14:57 +0000 Subject: [PATCH 1/9] Made several grammar fixes to cache documentation from [1020] git-svn-id: http://code.djangoproject.com/svn/django/trunk@1030 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/cache.py | 16 ++++------ docs/cache.txt | 74 +++++++++++++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/django/utils/cache.py b/django/utils/cache.py index 631ea8f08d..b36753b558 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -22,19 +22,19 @@ 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. + * All keyword parameter names are turned to lowercase, and underscores + are converted to hyphens. + * If the value of a parameter is True (exactly 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: @@ -49,9 +49,7 @@ def patch_cache_control(response, **kwargs): 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 = {} diff --git a/docs/cache.txt b/docs/cache.txt index c1b1352bca..6c1a3140d5 100644 --- a/docs/cache.txt +++ b/docs/cache.txt @@ -272,39 +272,65 @@ 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 -===================================== +Controlling cache: Using other 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. +Another problem with caching is the privacy of data and the question of where +data should be stored in a cascade of caches. -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). +A user usually faces two kinds of caches: his own browser cache (a private +cache) and his provider's cache (a public cache). A public cache is used by +multiple users and controlled by someone else. This poses problems with +sensitive data: You don't want, say, your banking-account number stored in a +public cache. So Web applications need a way to tell caches which data is +private and which is public. -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:: +The solution is to indicate a page's cache should be "private." To do this in +Django, use the ``cache_control`` view decorator. Example:: - @cache_control(private=True, must_revalidate=True, max_age=3600) + from django.views.decorators.cache import cache_control + @cache_control(private=True) 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. +This decorator takes care of sending out the appropriate HTTP header behind the +scenes. -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. +There are a few other ways to control cache parameters. For example, HTTP +allows applications to do the following: -.. _`HTTP Cache-Control headers`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + * Define the maximum time a page should be cached. + * Specify whether a cache should always check for newer versions, only + delivering the cached content when there are no changes. (Some caches + might deliver cached content even if the server page changed -- simply + because the cache copy isn't yet expired.) + +In Django, use the ``cache_control`` view decorator to specify these cache +parameters. In this example, the ``cache_control`` decorator tells caches to +revalidate the cache on every access and to store cached versions for, at most, +3600 seconds:: + + from django.views.decorators.cache import cache_control + @cache_control(must_revalidate=True, max_age=3600) + def my_view(request): + ... + +Any valid Cache-Control directive is valid in ``cache_control()``. For a full +list, see the `Cache-Control spec`_. Just pass the directives as keyword +arguments to ``cache_control()``, substituting underscores for hyphens. For +directives that don't take an argument, set the argument to ``True``. + +Examples: + + ``@cache_control(max_age=3600)`` turns into ``max-age=3600``. + ``@cache_control(public=True)`` turns into ``public``. + +(Note that the caching middleware already sets the cache header's max-age with +the value of the ``CACHE_MIDDLEWARE_SETTINGS`` setting. If you use a custom +``max_age`` in a ``cache_control`` decorator, the decorator will take +precedence, and the header values will be merged correctly.) + +.. _`Cache-Control spec`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 Other optimizations =================== From 546e368d0b0a0d74b13a756fa67e162709af22e8 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Mon, 31 Oct 2005 03:17:39 +0000 Subject: [PATCH 2/9] More tightening of docs/cache.txt git-svn-id: http://code.djangoproject.com/svn/django/trunk@1031 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/cache.txt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/cache.txt b/docs/cache.txt index 6c1a3140d5..f8986b9115 100644 --- a/docs/cache.txt +++ b/docs/cache.txt @@ -306,24 +306,23 @@ allows applications to do the following: because the cache copy isn't yet expired.) In Django, use the ``cache_control`` view decorator to specify these cache -parameters. In this example, the ``cache_control`` decorator tells caches to -revalidate the cache on every access and to store cached versions for, at most, -3600 seconds:: +parameters. In this example, ``cache_control`` tells caches to revalidate the +cache on every access and to store cached versions for, at most, 3600 seconds:: from django.views.decorators.cache import cache_control @cache_control(must_revalidate=True, max_age=3600) def my_view(request): ... -Any valid Cache-Control directive is valid in ``cache_control()``. For a full -list, see the `Cache-Control spec`_. Just pass the directives as keyword +Any valid ``Cache-Control`` directive is valid in ``cache_control()``. For a +full list, see the `Cache-Control spec`_. Just pass the directives as keyword arguments to ``cache_control()``, substituting underscores for hyphens. For directives that don't take an argument, set the argument to ``True``. Examples: - ``@cache_control(max_age=3600)`` turns into ``max-age=3600``. - ``@cache_control(public=True)`` turns into ``public``. + * ``@cache_control(max_age=3600)`` turns into ``max-age=3600``. + * ``@cache_control(public=True)`` turns into ``public``. (Note that the caching middleware already sets the cache header's max-age with the value of the ``CACHE_MIDDLEWARE_SETTINGS`` setting. If you use a custom From 3226127aa29c498921f7a7e4209de229d253d21f Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 1 Nov 2005 00:40:32 +0000 Subject: [PATCH 3/9] Added model unit tests for year, month and day lookup. Refs #659 git-svn-id: http://code.djangoproject.com/svn/django/trunk@1033 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/testapp/models/basic.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/testapp/models/basic.py b/tests/testapp/models/basic.py index ebd784137a..ad2a8cb862 100644 --- a/tests/testapp/models/basic.py +++ b/tests/testapp/models/basic.py @@ -51,6 +51,17 @@ datetime.datetime(2005, 7, 28, 0, 0)
>>> articles.get_object(pub_date__year=2005)
+>>> articles.get_object(pub_date__year=2005, pub_date__month=7) +
+>>> articles.get_object(pub_date__year=2005, pub_date__month=7, pub_date__day=28) +
+ +>>> articles.get_list(pub_date__year=2005) +[
] +>>> articles.get_list(pub_date__year=2004) +[] +>>> articles.get_list(pub_date__year=2005, pub_date__month=7) +[
] # Django raises an ArticleDoesNotExist exception for get_object() >>> articles.get_object(id__exact=2) @@ -58,6 +69,11 @@ Traceback (most recent call last): ... ArticleDoesNotExist: Article does not exist for {'id__exact': 2} +>>> articles.get_object(pub_date__year=2005, pub_date__month=8) +Traceback (most recent call last): + ... +ArticleDoesNotExist: Article does not exist for ... + # Lookup by a primary key is the most common case, so Django provides a # shortcut for primary-key exact lookups. # The following is identical to articles.get_object(id__exact=1). From 41d5cff745c26eab9a0b6ff29d2addfef7c5303a Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 1 Nov 2005 00:49:52 +0000 Subject: [PATCH 4/9] Fixed #548 -- Added missing step at the end of docs/tutorial04.txt git-svn-id: http://code.djangoproject.com/svn/django/trunk@1034 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/tutorial04.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/tutorial04.txt b/docs/tutorial04.txt index 737d8deb1f..4f9ef3baff 100644 --- a/docs/tutorial04.txt +++ b/docs/tutorial04.txt @@ -213,6 +213,9 @@ The generic views pass ``object`` and ``object_list`` to their templates, so change your templates so that ``latest_poll_list`` becomes ``object_list`` and ``poll`` becomes ``object``. +In the ``vote()`` view, change the template call from ``polls/detail`` to +``polls/polls_detail``, and pass ``object`` in the context instead of ``poll``. + Finally, you can delete the ``index()``, ``detail()`` and ``results()`` views from ``polls/views/polls.py``. We don't need them anymore. From 390666ac2bf8223bede4f78a97836051bc9f9526 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 1 Nov 2005 01:02:07 +0000 Subject: [PATCH 5/9] Fixed #508 -- Added support for 'expires' in cookies and changed session middleware to set 'expires' in addition to 'max_age'. Thanks, mark@junklight.com git-svn-id: http://code.djangoproject.com/svn/django/trunk@1035 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/middleware/sessions.py | 3 ++- django/utils/httpwrappers.py | 4 ++-- docs/request_response.txt | 4 +++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/django/middleware/sessions.py b/django/middleware/sessions.py index 42b2118410..8b9f21f78d 100644 --- a/django/middleware/sessions.py +++ b/django/middleware/sessions.py @@ -71,6 +71,7 @@ class SessionMiddleware: session_key = request.session.session_key or sessions.get_new_session_key() new_session = sessions.save(session_key, request.session._session, datetime.datetime.now() + datetime.timedelta(seconds=SESSION_COOKIE_AGE)) + expires = datetime.datetime.strftime(datetime.datetime.utcnow() + datetime.timedelta(seconds=SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT") response.set_cookie(SESSION_COOKIE_NAME, session_key, - max_age=SESSION_COOKIE_AGE, domain=SESSION_COOKIE_DOMAIN) + max_age=SESSION_COOKIE_AGE, expires=expires, domain=SESSION_COOKIE_DOMAIN) return response diff --git a/django/utils/httpwrappers.py b/django/utils/httpwrappers.py index 5f9362bd24..c1aa9d6ee1 100644 --- a/django/utils/httpwrappers.py +++ b/django/utils/httpwrappers.py @@ -172,9 +172,9 @@ class HttpResponse: return True return False - def set_cookie(self, key, value='', max_age=None, path='/', domain=None, secure=None): + def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None): self.cookies[key] = value - for var in ('max_age', 'path', 'domain', 'secure'): + for var in ('max_age', 'path', 'domain', 'secure', 'expires'): val = locals()[var] if val is not None: self.cookies[key][var.replace('_', '-')] = val diff --git a/docs/request_response.txt b/docs/request_response.txt index 150a5bc92c..85a5e091db 100644 --- a/docs/request_response.txt +++ b/docs/request_response.txt @@ -284,12 +284,14 @@ Methods Returns ``True`` or ``False`` based on a case-insensitive check for a header with the given name. -``set_cookie(key, value='', max_age=None, path='/', domain=None, secure=None)`` +``set_cookie(key, value='', max_age=None, expires=None, path='/', domain=None, secure=None)`` Sets a cookie. The parameters are the same as in the `cookie Morsel`_ object in the Python standard library. * ``max_age`` should be a number of seconds, or ``None`` (default) if the cookie should last only as long as the client's browser session. + * ``expires`` should be a string in the format + ``"Wdy, DD-Mon-YY HH:MM:SS GMT"``. * Use ``domain`` if you want to set a cross-domain cookie. For example, ``domain=".lawrence.com"`` will set a cookie that is readable by the domains www.lawrence.com, blogs.lawrence.com and From 7136eb8f3a052afef29e343679e84e93d7e9e0a3 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 1 Nov 2005 01:08:13 +0000 Subject: [PATCH 6/9] Fixed #507 -- Changed MySQL backend so that it uses 'LIKE BINARY' for case-sensitive comparisons -- contains, startswith and endswith. Thanks, Simon git-svn-id: http://code.djangoproject.com/svn/django/trunk@1036 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/db/backends/mysql.py | 6 +++--- docs/db-api.txt | 15 +++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/django/core/db/backends/mysql.py b/django/core/db/backends/mysql.py index 4399b6a6a0..23815a80cb 100644 --- a/django/core/db/backends/mysql.py +++ b/django/core/db/backends/mysql.py @@ -125,15 +125,15 @@ def get_relations(cursor, table_name): OPERATOR_MAPPING = { 'exact': '=', 'iexact': 'LIKE', - 'contains': 'LIKE', + 'contains': 'LIKE BINARY', 'icontains': 'LIKE', 'ne': '!=', 'gt': '>', 'gte': '>=', 'lt': '<', 'lte': '<=', - 'startswith': 'LIKE', - 'endswith': 'LIKE', + 'startswith': 'LIKE BINARY', + 'endswith': 'LIKE BINARY', 'istartswith': 'LIKE', 'iendswith': 'LIKE', } diff --git a/docs/db-api.txt b/docs/db-api.txt index b80d4e8647..1a4f488d50 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -161,9 +161,9 @@ The DB API supports the following lookup types: ``foo``, ``FOO``, ``fOo``, etc. contains Case-sensitive containment test: ``polls.get_list(question__contains="spam")`` returns all polls - that contain "spam" in the question. (PostgreSQL only. MySQL - doesn't support case-sensitive LIKE statements; ``contains`` - will act like ``icontains`` for MySQL.) + that contain "spam" in the question. (PostgreSQL and MySQL + only. SQLite doesn't support case-sensitive LIKE statements; + ``contains`` will act like ``icontains`` for SQLite.) icontains Case-insensitive containment test. gt Greater than: ``polls.get_list(id__gt=4)``. gte Greater than or equal to. @@ -174,11 +174,10 @@ The DB API supports the following lookup types: a list of polls whose IDs are either 1, 3 or 4. startswith Case-sensitive starts-with: ``polls.get_list(question_startswith="Would")``. (PostgreSQL - only. MySQL doesn't support case-sensitive LIKE statements; - ``startswith`` will act like ``istartswith`` for MySQL.) - endswith Case-sensitive ends-with. (PostgreSQL only. MySQL doesn't - support case-sensitive LIKE statements; ``endswith`` will act - like ``iendswith`` for MySQL.) + and MySQL only. SQLite doesn't support case-sensitive LIKE + statements; ``startswith`` will act like ``istartswith`` for + SQLite.) + endswith Case-sensitive ends-with. (PostgreSQL and MySQL only.) istartswith Case-insensitive starts-with. iendswith Case-insensitive ends-with. range Range test: From cee6faf43ed92c5b276ca4d2e1754a72fbc00ce1 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 1 Nov 2005 01:11:36 +0000 Subject: [PATCH 7/9] Fixed #505 -- ssi template tag now displays a message instead of failing silently if DEBUG=True. Thanks, Manuzhai git-svn-id: http://code.djangoproject.com/svn/django/trunk@1037 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/template/defaulttags.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/django/core/template/defaulttags.py b/django/core/template/defaulttags.py index ea21ebafce..535d0b81e1 100644 --- a/django/core/template/defaulttags.py +++ b/django/core/template/defaulttags.py @@ -211,8 +211,12 @@ class SsiNode(Node): self.filepath, self.parsed = filepath, parsed def render(self, context): + from django.conf.settings import DEBUG if not include_is_allowed(self.filepath): - return '' # Fail silently for invalid includes. + if DEBUG: + return "[Didn't have permission to include file]" + else: + return '' # Fail silently for invalid includes. try: fp = open(self.filepath, 'r') output = fp.read() @@ -223,8 +227,11 @@ class SsiNode(Node): try: t = Template(output) return t.render(context) - except TemplateSyntaxError: - return '' # Fail silently for invalid included templates. + except (TemplateSyntaxError, e): + if DEBUG: + return "[Included template had syntax error: %s]" % e + else: + return '' # Fail silently for invalid included templates. return output class LoadNode(Node): From c65332d409cf477d4b55f1ff3bacbdea4a0afaff Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 1 Nov 2005 01:28:42 +0000 Subject: [PATCH 8/9] Updated out-of-date docstring in django/core/db/__init__.py git-svn-id: http://code.djangoproject.com/svn/django/trunk@1038 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/db/__init__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/django/core/db/__init__.py b/django/core/db/__init__.py index c4f06e4c6e..f0ffeebc2e 100644 --- a/django/core/db/__init__.py +++ b/django/core/db/__init__.py @@ -1,16 +1,11 @@ """ This is the core database connection. -All CMS code assumes database SELECT statements cast the resulting values as such: +All Django code assumes database SELECT statements cast the resulting values as such: * booleans are mapped to Python booleans * dates are mapped to Python datetime.date objects * times are mapped to Python datetime.time objects * timestamps are mapped to Python datetime.datetime objects - -Right now, we're handling this by using psycopg's custom typecast definitions. -If we move to a different database module, we should ensure that it either -performs the appropriate typecasting out of the box, or that it has hooks that -let us do that. """ from django.conf.settings import DATABASE_ENGINE From 23bb8c4a4b5e785cd4992689c529f2d9a86898b2 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 1 Nov 2005 01:32:54 +0000 Subject: [PATCH 9/9] Added quote_name hook for each database backend. Refs #121. Thanks, Robin Munn -- we miss you. git-svn-id: http://code.djangoproject.com/svn/django/trunk@1039 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/db/__init__.py | 1 + django/core/db/backends/ado_mssql.py | 4 ++++ django/core/db/backends/mysql.py | 5 +++++ django/core/db/backends/postgresql.py | 5 +++++ django/core/db/backends/sqlite3.py | 5 +++++ 5 files changed, 20 insertions(+) diff --git a/django/core/db/__init__.py b/django/core/db/__init__.py index f0ffeebc2e..6636d4f176 100644 --- a/django/core/db/__init__.py +++ b/django/core/db/__init__.py @@ -36,6 +36,7 @@ get_limit_offset_sql = dbmod.get_limit_offset_sql get_random_function_sql = dbmod.get_random_function_sql get_table_list = dbmod.get_table_list get_relations = dbmod.get_relations +quote_name = dbmod.quote_name OPERATOR_MAPPING = dbmod.OPERATOR_MAPPING DATA_TYPES = dbmod.DATA_TYPES DATA_TYPES_REVERSE = dbmod.DATA_TYPES_REVERSE diff --git a/django/core/db/backends/ado_mssql.py b/django/core/db/backends/ado_mssql.py index 9ea0d5456d..a673c4812e 100644 --- a/django/core/db/backends/ado_mssql.py +++ b/django/core/db/backends/ado_mssql.py @@ -110,6 +110,10 @@ def get_table_list(cursor): def get_relations(cursor, table_name): raise NotImplementedError +def quote_name(name): + # TODO: Figure out how MS-SQL quotes database identifiers. + return name + OPERATOR_MAPPING = { 'exact': '=', 'iexact': 'LIKE', diff --git a/django/core/db/backends/mysql.py b/django/core/db/backends/mysql.py index 23815a80cb..e7ede84a12 100644 --- a/django/core/db/backends/mysql.py +++ b/django/core/db/backends/mysql.py @@ -122,6 +122,11 @@ def get_table_list(cursor): def get_relations(cursor, table_name): raise NotImplementedError +def quote_name(name): + if name.startswith("`") and name.endswith("`"): + return name # Quoting once is enough. + return "`%s`" % name + OPERATOR_MAPPING = { 'exact': '=', 'iexact': 'LIKE', diff --git a/django/core/db/backends/postgresql.py b/django/core/db/backends/postgresql.py index c922fd42f6..a1de11e3df 100644 --- a/django/core/db/backends/postgresql.py +++ b/django/core/db/backends/postgresql.py @@ -116,6 +116,11 @@ def get_relations(cursor, table_name): continue return relations +def quote_name(name): + if name.startswith('"') and name.endswith('"'): + return name # Quoting once is enough. + return '"%s"' % name + # Register these custom typecasts, because Django expects dates/times to be # in Python's native (standard-library) datetime/time format, whereas psycopg # use mx.DateTime by default. diff --git a/django/core/db/backends/sqlite3.py b/django/core/db/backends/sqlite3.py index ea05302a61..3fde8c77e1 100644 --- a/django/core/db/backends/sqlite3.py +++ b/django/core/db/backends/sqlite3.py @@ -124,6 +124,11 @@ def get_table_list(cursor): def get_relations(cursor, table_name): raise NotImplementedError +def quote_name(name): + if name.startswith('"') and name.endswith('"'): + return name # Quoting once is enough. + return '"%s"' % name + # Operators and fields ######################################################## OPERATOR_MAPPING = {