diff --git a/django/core/db/__init__.py b/django/core/db/__init__.py index c4f06e4c6e..6636d4f176 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 @@ -41,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 4399b6a6a0..e7ede84a12 100644 --- a/django/core/db/backends/mysql.py +++ b/django/core/db/backends/mysql.py @@ -122,18 +122,23 @@ 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', - '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/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 = { diff --git a/django/core/template/defaulttags.py b/django/core/template/defaulttags.py index cc59dad388..ce49bcc505 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): 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/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/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/cache.txt b/docs/cache.txt index c1b1352bca..f8986b9115 100644 --- a/docs/cache.txt +++ b/docs/cache.txt @@ -272,39 +272,64 @@ 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, ``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 +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 =================== 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: 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 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. 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).