From 502be865c68635d5c31fa3fa58162b48412153ad Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 25 Oct 2012 10:31:14 +0100 Subject: Add 'page_kwarg' attribute to `MultipleObjectMixin`, removing hardcoded 'page'. --- docs/ref/class-based-views/mixins-multiple-object.txt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/class-based-views/mixins-multiple-object.txt b/docs/ref/class-based-views/mixins-multiple-object.txt index cdb743fcbd..e6abf26e6a 100644 --- a/docs/ref/class-based-views/mixins-multiple-object.txt +++ b/docs/ref/class-based-views/mixins-multiple-object.txt @@ -69,8 +69,15 @@ MultipleObjectMixin An integer specifying how many objects should be displayed per page. If this is given, the view will paginate objects with :attr:`MultipleObjectMixin.paginate_by` objects per page. The view will - expect either a ``page`` query string parameter (via ``GET``) or a - ``page`` variable specified in the URLconf. + expect either a ``page`` query string parameter (via ``request.GET``) + or a ``page`` variable specified in the URLconf. + + .. attribute:: page_kwarg + + A string specifying the name to use for the page parameter. + The view will expect this prameter to be available either as a query + string parameter (via ``request.GET``) or as a kwarg variable specified + in the URLconf. Defaults to ``"page"``. .. attribute:: paginator_class -- cgit v1.3 From 83ba0a9d4b078fd177ac5c06699486d708d62bff Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 27 Oct 2012 11:49:46 +0200 Subject: Fixed #18978 -- Moved cleanup command to sessions. This removes a dependency of 'core' on 'contrib'. --- django/bin/daily_cleanup.py | 8 +++++++- django/contrib/sessions/management/__init__.py | 0 django/contrib/sessions/management/commands/__init__.py | 0 .../sessions/management/commands/clearsessions.py | 11 +++++++++++ django/core/management/commands/cleanup.py | 16 ++++++++-------- docs/internals/deprecation.txt | 5 +++++ docs/ref/django-admin.txt | 15 +++++++++++++++ docs/releases/1.5.txt | 13 ++++++++++++- docs/topics/http/sessions.txt | 2 +- 9 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 django/contrib/sessions/management/__init__.py create mode 100644 django/contrib/sessions/management/commands/__init__.py create mode 100644 django/contrib/sessions/management/commands/clearsessions.py (limited to 'docs/ref') diff --git a/django/bin/daily_cleanup.py b/django/bin/daily_cleanup.py index c9f4cb905c..ac3de00f2c 100755 --- a/django/bin/daily_cleanup.py +++ b/django/bin/daily_cleanup.py @@ -7,7 +7,13 @@ Can be run as a cronjob to clean out old data from the database (only expired sessions at the moment). """ +import warnings + from django.core import management if __name__ == "__main__": - management.call_command('cleanup') + warnings.warn( + "The `daily_cleanup` script has been deprecated " + "in favor of `django-admin.py clearsessions`.", + PendingDeprecationWarning) + management.call_command('clearsessions') diff --git a/django/contrib/sessions/management/__init__.py b/django/contrib/sessions/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/sessions/management/commands/__init__.py b/django/contrib/sessions/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/sessions/management/commands/clearsessions.py b/django/contrib/sessions/management/commands/clearsessions.py new file mode 100644 index 0000000000..fb7666f85a --- /dev/null +++ b/django/contrib/sessions/management/commands/clearsessions.py @@ -0,0 +1,11 @@ +from django.core.management.base import NoArgsCommand +from django.utils import timezone + +class Command(NoArgsCommand): + help = "Can be run as a cronjob or directly to clean out expired sessions (only with the database backend at the moment)." + + def handle_noargs(self, **options): + from django.db import transaction + from django.contrib.sessions.models import Session + Session.objects.filter(expire_date__lt=timezone.now()).delete() + transaction.commit_unless_managed() diff --git a/django/core/management/commands/cleanup.py b/django/core/management/commands/cleanup.py index e19d1649be..f83c64be8f 100644 --- a/django/core/management/commands/cleanup.py +++ b/django/core/management/commands/cleanup.py @@ -1,11 +1,11 @@ -from django.core.management.base import NoArgsCommand -from django.utils import timezone +import warnings -class Command(NoArgsCommand): - help = "Can be run as a cronjob or directly to clean out old data from the database (only expired sessions at the moment)." +from django.contrib.sessions.management.commands import clearsessions + +class Command(clearsessions.Command): def handle_noargs(self, **options): - from django.db import transaction - from django.contrib.sessions.models import Session - Session.objects.filter(expire_date__lt=timezone.now()).delete() - transaction.commit_unless_managed() + warnings.warn( + "The `cleanup` command has been deprecated in favor of `clearsessions`.", + PendingDeprecationWarning) + super(Command, self).handle_noargs(**options) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 10bbfe1a91..77371c8608 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -293,6 +293,11 @@ these changes. * The ``AUTH_PROFILE_MODULE`` setting, and the ``get_profile()`` method on the User model, will be removed. +* The ``cleanup`` management command will be removed. It's replaced by + ``clearsessions``. + +* The ``daily_cleanup.py`` script will be removed. + 2.0 --- diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 7fa7539985..833db0839c 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -96,6 +96,9 @@ cleanup Can be run as a cronjob or directly to clean out old data from the database (only expired sessions at the moment). +.. versionchanged:: 1.5 + :djadmin:`cleanup` is deprecated. Use :djadmin:`clearsessions` instead. + compilemessages --------------- @@ -1187,6 +1190,18 @@ This command is only available if :doc:`GeoDjango ` Please refer to its :djadmin:`description ` in the GeoDjango documentation. +``django.contrib.sessions`` +--------------------------- + +clearsessions +~~~~~~~~~~~~~~~ + +.. django-admin:: clearsessions + +Can be run as a cron job or directly to clean out expired sessions. + +This is only supported by the database backend at the moment. + ``django.contrib.sitemaps`` --------------------------- diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index a0ce3cc7a4..ebf88e83b9 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -613,7 +613,6 @@ Define a ``__str__`` method and apply the The :func:`~django.utils.itercompat.product` function has been deprecated. Use the built-in :func:`itertools.product` instead. - ``django.utils.markup`` ~~~~~~~~~~~~~~~~~~~~~~~ @@ -621,3 +620,15 @@ The markup contrib module has been deprecated and will follow an accelerated deprecation schedule. Direct use of python markup libraries or 3rd party tag libraries is preferred to Django maintaining this functionality in the framework. + +``cleanup`` management command +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :djadmin:`cleanup` management command has been deprecated and replaced by +:djadmin:`clearsessions`. + +``daily_cleanup.py`` script +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The undocumented ``daily_cleanup.py`` script has been deprecated. Use the +:djadmin:`clearsessions` management command instead. diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 15f9f7feba..0082b75db1 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -460,7 +460,7 @@ table. Django updates this row each time the session data changes. If the user logs out manually, Django deletes the row. But if the user does *not* log out, the row never gets deleted. -Django provides a sample clean-up script: ``django-admin.py cleanup``. +Django provides a sample clean-up script: ``django-admin.py clearsessions``. That script deletes any session in the session table whose ``expire_date`` is in the past -- but your application may have different requirements. -- cgit v1.3 From 5fec97b9df6ea075483276de159e522a29437773 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 27 Oct 2012 23:12:08 +0200 Subject: Fixed #18194 -- Expiration of file-based sessions * Prevented stale session files from being loaded * Added removal of stale session files in django-admin.py clearsessions Thanks ej for the report, crodjer and Elvard for their inputs. --- django/contrib/sessions/backends/base.py | 12 +++- django/contrib/sessions/backends/cache.py | 4 ++ django/contrib/sessions/backends/db.py | 5 ++ django/contrib/sessions/backends/file.py | 71 ++++++++++++++++++---- django/contrib/sessions/backends/signed_cookies.py | 4 ++ .../sessions/management/commands/clearsessions.py | 14 +++-- django/contrib/sessions/tests.py | 61 +++++++++++++++++++ docs/ref/django-admin.txt | 2 - docs/topics/http/sessions.txt | 32 +++++++--- 9 files changed, 176 insertions(+), 29 deletions(-) (limited to 'docs/ref') diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index 1f63c3b45a..ff8ab7f677 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import base64 -import time from datetime import datetime, timedelta try: from django.utils.six.moves import cPickle as pickle @@ -309,3 +308,14 @@ class SessionBase(object): Loads the session data and returns a dictionary. """ raise NotImplementedError + + @classmethod + def clear_expired(cls): + """ + Remove expired sessions from the session store. + + If this operation isn't possible on a given backend, it should raise + NotImplementedError. If it isn't necessary, because the backend has + a built-in expiration mechanism, it should be a no-op. + """ + raise NotImplementedError diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py index b66123b915..0c7eb8d2cb 100644 --- a/django/contrib/sessions/backends/cache.py +++ b/django/contrib/sessions/backends/cache.py @@ -65,3 +65,7 @@ class SessionStore(SessionBase): return session_key = self.session_key self._cache.delete(KEY_PREFIX + session_key) + + @classmethod + def clear_expired(cls): + pass diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py index 4dacc96000..47e89b66e5 100644 --- a/django/contrib/sessions/backends/db.py +++ b/django/contrib/sessions/backends/db.py @@ -71,6 +71,11 @@ class SessionStore(SessionBase): except Session.DoesNotExist: pass + @classmethod + def clear_expired(cls): + Session.objects.filter(expire_date__lt=timezone.now()).delete() + transaction.commit_unless_managed() + # At bottom to avoid circular import from django.contrib.sessions.models import Session diff --git a/django/contrib/sessions/backends/file.py b/django/contrib/sessions/backends/file.py index 20ac2c2087..f3a71f8271 100644 --- a/django/contrib/sessions/backends/file.py +++ b/django/contrib/sessions/backends/file.py @@ -1,3 +1,4 @@ +import datetime import errno import os import tempfile @@ -5,27 +6,36 @@ import tempfile from django.conf import settings from django.contrib.sessions.backends.base import SessionBase, CreateError from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured - +from django.utils import timezone class SessionStore(SessionBase): """ Implements a file based session store. """ def __init__(self, session_key=None): - self.storage_path = getattr(settings, "SESSION_FILE_PATH", None) - if not self.storage_path: - self.storage_path = tempfile.gettempdir() - - # Make sure the storage path is valid. - if not os.path.isdir(self.storage_path): - raise ImproperlyConfigured( - "The session storage path %r doesn't exist. Please set your" - " SESSION_FILE_PATH setting to an existing directory in which" - " Django can store session data." % self.storage_path) - + self.storage_path = type(self)._get_storage_path() self.file_prefix = settings.SESSION_COOKIE_NAME super(SessionStore, self).__init__(session_key) + @classmethod + def _get_storage_path(cls): + try: + return cls._storage_path + except AttributeError: + storage_path = getattr(settings, "SESSION_FILE_PATH", None) + if not storage_path: + storage_path = tempfile.gettempdir() + + # Make sure the storage path is valid. + if not os.path.isdir(storage_path): + raise ImproperlyConfigured( + "The session storage path %r doesn't exist. Please set your" + " SESSION_FILE_PATH setting to an existing directory in which" + " Django can store session data." % storage_path) + + cls._storage_path = storage_path + return storage_path + VALID_KEY_CHARS = set("abcdef0123456789") def _key_to_file(self, session_key=None): @@ -44,6 +54,18 @@ class SessionStore(SessionBase): return os.path.join(self.storage_path, self.file_prefix + session_key) + def _last_modification(self): + """ + Return the modification time of the file storing the session's content. + """ + modification = os.stat(self._key_to_file()).st_mtime + if settings.USE_TZ: + modification = datetime.datetime.utcfromtimestamp(modification) + modification = modification.replace(tzinfo=timezone.utc) + else: + modification = datetime.datetime.fromtimestamp(modification) + return modification + def load(self): session_data = {} try: @@ -56,6 +78,15 @@ class SessionStore(SessionBase): session_data = self.decode(file_data) except (EOFError, SuspiciousOperation): self.create() + + # Remove expired sessions. + expiry_age = self.get_expiry_age( + modification=self._last_modification(), + expiry=session_data.get('_session_expiry')) + if expiry_age < 0: + session_data = {} + self.delete() + self.create() except IOError: self.create() return session_data @@ -142,3 +173,19 @@ class SessionStore(SessionBase): def clean(self): pass + + @classmethod + def clear_expired(cls): + storage_path = getattr(settings, "SESSION_FILE_PATH", tempfile.gettempdir()) + file_prefix = settings.SESSION_COOKIE_NAME + + for session_file in os.listdir(storage_path): + if not session_file.startswith(file_prefix): + continue + session_key = session_file[len(file_prefix):] + session = cls(session_key) + # When an expired session is loaded, its file is removed, and a + # new file is immediately created. Prevent this by disabling + # the create() method. + session.create = lambda: None + session.load() diff --git a/django/contrib/sessions/backends/signed_cookies.py b/django/contrib/sessions/backends/signed_cookies.py index 23915cf98c..c2b7a3123f 100644 --- a/django/contrib/sessions/backends/signed_cookies.py +++ b/django/contrib/sessions/backends/signed_cookies.py @@ -92,3 +92,7 @@ class SessionStore(SessionBase): return signing.dumps(session_cache, compress=True, salt='django.contrib.sessions.backends.signed_cookies', serializer=PickleSerializer) + + @classmethod + def clear_expired(cls): + pass diff --git a/django/contrib/sessions/management/commands/clearsessions.py b/django/contrib/sessions/management/commands/clearsessions.py index fb7666f85a..8eb23dfee0 100644 --- a/django/contrib/sessions/management/commands/clearsessions.py +++ b/django/contrib/sessions/management/commands/clearsessions.py @@ -1,11 +1,15 @@ +from django.conf import settings from django.core.management.base import NoArgsCommand -from django.utils import timezone +from django.utils.importlib import import_module + class Command(NoArgsCommand): help = "Can be run as a cronjob or directly to clean out expired sessions (only with the database backend at the moment)." def handle_noargs(self, **options): - from django.db import transaction - from django.contrib.sessions.models import Session - Session.objects.filter(expire_date__lt=timezone.now()).delete() - transaction.commit_unless_managed() + engine = import_module(settings.SESSION_ENGINE) + try: + engine.SessionStore.clear_expired() + except NotImplementedError: + self.stderr.write("Session engine '%s' doesn't support clearing " + "expired sessions.\n" % settings.SESSION_ENGINE) diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 21e8d845b8..129cd21735 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -1,4 +1,5 @@ from datetime import timedelta +import os import shutil import string import tempfile @@ -12,6 +13,7 @@ from django.contrib.sessions.backends.file import SessionStore as FileSession from django.contrib.sessions.backends.signed_cookies import SessionStore as CookieSession from django.contrib.sessions.models import Session from django.contrib.sessions.middleware import SessionMiddleware +from django.core import management from django.core.cache import DEFAULT_CACHE_ALIAS from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.http import HttpResponse @@ -319,6 +321,30 @@ class DatabaseSessionTests(SessionTestsMixin, TestCase): del self.session._session_cache self.assertEqual(self.session['y'], 2) + @override_settings(SESSION_ENGINE="django.contrib.sessions.backends.db") + def test_clearsessions_command(self): + """ + Test clearsessions command for clearing expired sessions. + """ + self.assertEqual(0, Session.objects.count()) + + # One object in the future + self.session['foo'] = 'bar' + self.session.set_expiry(3600) + self.session.save() + + # One object in the past + other_session = self.backend() + other_session['foo'] = 'bar' + other_session.set_expiry(-3600) + other_session.save() + + # Two sessions are in the database before clearsessions... + self.assertEqual(2, Session.objects.count()) + management.call_command('clearsessions') + # ... and one is deleted. + self.assertEqual(1, Session.objects.count()) + @override_settings(USE_TZ=True) class DatabaseSessionWithTimeZoneTests(DatabaseSessionTests): @@ -358,6 +384,9 @@ class FileSessionTests(SessionTestsMixin, unittest.TestCase): # Do file session tests in an isolated directory, and kill it after we're done. self.original_session_file_path = settings.SESSION_FILE_PATH self.temp_session_store = settings.SESSION_FILE_PATH = tempfile.mkdtemp() + # Reset the file session backend's internal caches + if hasattr(self.backend, '_storage_path'): + del self.backend._storage_path super(FileSessionTests, self).setUp() def tearDown(self): @@ -368,6 +397,7 @@ class FileSessionTests(SessionTestsMixin, unittest.TestCase): @override_settings( SESSION_FILE_PATH="/if/this/directory/exists/you/have/a/weird/computer") def test_configuration_check(self): + del self.backend._storage_path # Make sure the file backend checks for a good storage dir self.assertRaises(ImproperlyConfigured, self.backend) @@ -381,6 +411,37 @@ class FileSessionTests(SessionTestsMixin, unittest.TestCase): self.assertRaises(SuspiciousOperation, self.backend("a/b/c").load) + @override_settings(SESSION_ENGINE="django.contrib.sessions.backends.file") + def test_clearsessions_command(self): + """ + Test clearsessions command for clearing expired sessions. + """ + storage_path = self.backend._get_storage_path() + file_prefix = settings.SESSION_COOKIE_NAME + + def count_sessions(): + return len([session_file for session_file in os.listdir(storage_path) + if session_file.startswith(file_prefix)]) + + self.assertEqual(0, count_sessions()) + + # One object in the future + self.session['foo'] = 'bar' + self.session.set_expiry(3600) + self.session.save() + + # One object in the past + other_session = self.backend() + other_session['foo'] = 'bar' + other_session.set_expiry(-3600) + other_session.save() + + # Two sessions are in the filesystem before clearsessions... + self.assertEqual(2, count_sessions()) + management.call_command('clearsessions') + # ... and one is deleted. + self.assertEqual(1, count_sessions()) + class CacheSessionTests(SessionTestsMixin, unittest.TestCase): diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 833db0839c..e0b08450e9 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1200,8 +1200,6 @@ clearsessions Can be run as a cron job or directly to clean out expired sessions. -This is only supported by the database backend at the moment. - ``django.contrib.sitemaps`` --------------------------- diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 1e043405f4..d9c472d092 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -272,6 +272,13 @@ You can edit it multiple times. Returns either ``True`` or ``False``, depending on whether the user's session cookie will expire when the user's Web browser is closed. + .. method:: SessionBase.clear_expired + + .. versionadded:: 1.5 + + Removes expired sessions from the session store. This class method is + called by :djadmin:`clearsessions`. + Session object guidelines ------------------------- @@ -458,22 +465,29 @@ This setting is a global default and can be overwritten at a per-session level by explicitly calling the :meth:`~backends.base.SessionBase.set_expiry` method of ``request.session`` as described above in `using sessions in views`_. -Clearing the session table +Clearing the session store ========================== -If you're using the database backend, note that session data can accumulate in -the ``django_session`` database table and Django does *not* provide automatic -purging. Therefore, it's your job to purge expired sessions on a regular basis. +As users create new sessions on your website, session data can accumulate in +your session store. If you're using the database backend, the +``django_session`` database table will grow. If you're using the file backend, +your temporary directory will contain an increasing number of files. -To understand this problem, consider what happens when a user uses a session. +To understand this problem, consider what happens with the database backend. When a user logs in, Django adds a row to the ``django_session`` database table. Django updates this row each time the session data changes. If the user logs out manually, Django deletes the row. But if the user does *not* log out, -the row never gets deleted. +the row never gets deleted. A similar process happens with the file backend. + +Django does *not* provide automatic purging of expired sessions. Therefore, +it's your job to purge expired sessions on a regular basis. Django provides a +clean-up management command for this purpose: :djadmin:`clearsessions`. It's +recommended to call this command on a regular basis, for example as a daily +cron job. -Django provides a sample clean-up script: ``django-admin.py clearsessions``. -That script deletes any session in the session table whose ``expire_date`` is -in the past -- but your application may have different requirements. +Note that the cache backend isn't vulnerable to this problem, because caches +automatically delete stale data. Neither is the cookie backend, because the +session data is stored by the users' browsers. Settings ======== -- cgit v1.3 From 24b2aad8e399fdeb4668fe6f4b7b997cf94100ca Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Mon, 29 Oct 2012 23:12:20 +0100 Subject: Fixed #19209 -- Documented |date:"I". Thanks mitar for the report. --- docs/ref/templates/builtins.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'docs/ref') diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 3b8d058fb4..4aa1a990cd 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1226,7 +1226,8 @@ G Hour, 24-hour format without leading ``'0'`` to ``'23'`` h Hour, 12-hour format. ``'01'`` to ``'12'`` H Hour, 24-hour format. ``'00'`` to ``'23'`` i Minutes. ``'00'`` to ``'59'`` -I Not implemented. +I Daylight Savings Time, whether it's ``'1'`` or ``'0'`` + in effect or not. j Day of the month without leading ``'1'`` to ``'31'`` zeros. l Day of the week, textual, long. ``'Friday'`` -- cgit v1.3 From 146ed13a111c97c1c04902a6c0eda1e4ee6e604c Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Tue, 30 Oct 2012 21:59:23 +0100 Subject: Fixed #17083 -- Allowed sessions to use non-default cache. --- django/conf/global_settings.py | 1 + django/contrib/sessions/backends/cache.py | 5 +++-- django/contrib/sessions/tests.py | 26 ++++++++++++++++++++++---- docs/ref/settings.txt | 10 ++++++++++ docs/releases/1.5.txt | 3 +++ docs/topics/http/sessions.txt | 9 +++++++++ 6 files changed, 48 insertions(+), 6 deletions(-) (limited to 'docs/ref') diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 84296c7493..c533efc41c 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -445,6 +445,7 @@ MIDDLEWARE_CLASSES = ( # SESSIONS # ############ +SESSION_CACHE_ALIAS = 'default' # Cache to store session data if using the cache session backend. SESSION_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want. SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks). SESSION_COOKIE_DOMAIN = None # A string like ".example.com", or None for standard domain cookie. diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py index 1b4906f923..596042fcb3 100644 --- a/django/contrib/sessions/backends/cache.py +++ b/django/contrib/sessions/backends/cache.py @@ -1,5 +1,6 @@ +from django.conf import settings from django.contrib.sessions.backends.base import SessionBase, CreateError -from django.core.cache import cache +from django.core.cache import get_cache from django.utils.six.moves import xrange KEY_PREFIX = "django.contrib.sessions.cache" @@ -10,7 +11,7 @@ class SessionStore(SessionBase): A cache-based session store. """ def __init__(self, session_key=None): - self._cache = cache + self._cache = get_cache(settings.SESSION_CACHE_ALIAS) super(SessionStore, self).__init__(session_key) @property diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 718967791d..da79ac9de6 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -13,8 +13,8 @@ from django.contrib.sessions.backends.file import SessionStore as FileSession from django.contrib.sessions.backends.signed_cookies import SessionStore as CookieSession from django.contrib.sessions.models import Session from django.contrib.sessions.middleware import SessionMiddleware +from django.core.cache import get_cache from django.core import management -from django.core.cache import DEFAULT_CACHE_ALIAS from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.http import HttpResponse from django.test import TestCase, RequestFactory @@ -136,8 +136,8 @@ class SessionTestsMixin(object): self.assertTrue(self.session.modified) def test_save(self): - if (hasattr(self.session, '_cache') and - 'DummyCache' in settings.CACHES[DEFAULT_CACHE_ALIAS]['BACKEND']): + if (hasattr(self.session, '_cache') and'DummyCache' in + settings.CACHES[settings.SESSION_CACHE_ALIAS]['BACKEND']): raise unittest.SkipTest("Session saving tests require a real cache backend") self.session.save() self.assertTrue(self.session.exists(self.session.session_key)) @@ -355,7 +355,8 @@ class CacheDBSessionTests(SessionTestsMixin, TestCase): backend = CacheDBSession - @unittest.skipIf('DummyCache' in settings.CACHES[DEFAULT_CACHE_ALIAS]['BACKEND'], + @unittest.skipIf('DummyCache' in + settings.CACHES[settings.SESSION_CACHE_ALIAS]['BACKEND'], "Session saving tests require a real cache backend") def test_exists_searches_cache_first(self): self.session.save() @@ -454,6 +455,23 @@ class CacheSessionTests(SessionTestsMixin, unittest.TestCase): self.session._session_key = (string.ascii_letters + string.digits) * 20 self.assertEqual(self.session.load(), {}) + def test_default_cache(self): + self.session.save() + self.assertNotEqual(get_cache('default').get(self.session.cache_key), None) + + @override_settings(CACHES={ + 'default': { + 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', + }, + 'sessions': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + }, + }, SESSION_CACHE_ALIAS='sessions') + def test_non_default_cache(self): + self.session.save() + self.assertEqual(get_cache('default').get(self.session.cache_key), None) + self.assertNotEqual(get_cache('sessions').get(self.session.cache_key), None) + class SessionMiddlewareTests(unittest.TestCase): diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index a909c12665..e8b41afb39 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1693,6 +1693,16 @@ This is useful if you have multiple Django instances running under the same hostname. They can use different cookie paths, and each instance will only see its own session cookie. +.. setting:: SESSION_CACHE_ALIAS + +SESSION_CACHE_ALIAS +------------------- + +Default: ``default`` + +If you're using :ref:`cache-based session storage `, +this selects the cache to use. + .. setting:: SESSION_COOKIE_SECURE SESSION_COOKIE_SECURE diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 3ee1b2d21f..e18a78afc8 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -299,6 +299,9 @@ Django 1.5 also includes several smaller improvements worth noting: * RemoteUserMiddleware now forces logout when the REMOTE_USER header disappears during the same browser session. +* The :ref:`cache-based session backend ` can store + session data in a non-default cache. + Backwards incompatible changes in 1.5 ===================================== diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index d9c472d092..baf8aa5cb5 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -45,6 +45,8 @@ If you want to use a database-backed session, you need to add Once you have configured your installation, run ``manage.py syncdb`` to install the single database table that stores session data. +.. _cached-sessions-backend: + Using cached sessions --------------------- @@ -62,6 +64,13 @@ sure you've configured your cache; see the :doc:`cache documentation sessions directly instead of sending everything through the file or database cache backends. +If you have multiple caches defined in :setting:`CACHES`, Django will use the +default cache. To use another cache, set :setting:`SESSION_CACHE_ALIAS` to the +name of that cache. + +.. versionchanged:: 1.5 + The :setting:`SESSION_CACHE_ALIAS` setting was added. + Once your cache is configured, you've got two choices for how to store data in the cache: -- cgit v1.3 From ede8a0be05f7b55d07ab5f60f3e8e3135a54f743 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Nov 2012 06:58:02 -0400 Subject: Fixed #19179 - Added mention of NamedUrlSessionWizard and NamedUrlCookieWizard; thanks Tom for the report. --- docs/ref/contrib/formtools/form-wizard.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index 0ced1bf155..d1193badbe 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -622,8 +622,11 @@ Usage of ``NamedUrlWizardView`` .. class:: NamedUrlWizardView -There is a :class:`WizardView` subclass which adds named-urls support to the wizard. -By doing this, you can have single urls for every step. +There is a :class:`WizardView` subclass which adds named-urls support to the +wizard. By doing this, you can have single urls for every step. You can also +use the :class:`NamedUrlSessionWizardView` or :class:`NamedUrlCookieWizardView` +classes which preselect the backend used for storing information (server-side +sessions and browser cookies respectively). To use the named urls, you have to change the ``urls.py``. -- cgit v1.3 From af7ea808d8540d1be87d89172f371ac928ff5c19 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Nov 2012 16:11:05 -0400 Subject: Added WizardView.file_storage exception message and docs Thanks Danilo Bargen for the patch. --- django/contrib/formtools/wizard/storage/base.py | 8 ++++++-- django/contrib/formtools/wizard/views.py | 12 +++++++----- docs/ref/contrib/formtools/form-wizard.txt | 25 +++++++++++++++++++++++++ docs/topics/files.txt | 2 ++ 4 files changed, 40 insertions(+), 7 deletions(-) (limited to 'docs/ref') diff --git a/django/contrib/formtools/wizard/storage/base.py b/django/contrib/formtools/wizard/storage/base.py index aafc833484..2e59679d09 100644 --- a/django/contrib/formtools/wizard/storage/base.py +++ b/django/contrib/formtools/wizard/storage/base.py @@ -69,7 +69,9 @@ class BaseStorage(object): wizard_files = self.data[self.step_files_key].get(step, {}) if wizard_files and not self.file_storage: - raise NoFileStorageConfigured + raise NoFileStorageConfigured( + "You need to define 'file_storage' in your " + "wizard view in order to handle file uploads.") files = {} for field, field_dict in six.iteritems(wizard_files): @@ -81,7 +83,9 @@ class BaseStorage(object): def set_step_files(self, step, files): if files and not self.file_storage: - raise NoFileStorageConfigured + raise NoFileStorageConfigured( + "You need to define 'file_storage' in your " + "wizard view in order to handle file uploads.") if step not in self.data[self.step_files_key]: self.data[self.step_files_key][step] = {} diff --git a/django/contrib/formtools/wizard/views.py b/django/contrib/formtools/wizard/views.py index ea41e86852..5b45267f36 100644 --- a/django/contrib/formtools/wizard/views.py +++ b/django/contrib/formtools/wizard/views.py @@ -174,7 +174,9 @@ class WizardView(TemplateView): for field in six.itervalues(form.base_fields): if (isinstance(field, forms.FileField) and not hasattr(cls, 'file_storage')): - raise NoFileStorageConfigured + raise NoFileStorageConfigured( + "You need to define 'file_storage' in your " + "wizard view in order to handle file uploads.") # build the kwargs for the wizardview instances kwargs['form_list'] = init_form_list @@ -436,8 +438,8 @@ class WizardView(TemplateView): def get_all_cleaned_data(self): """ Returns a merged dictionary of all step cleaned_data dictionaries. - If a step contains a `FormSet`, the key will be prefixed with formset - and contain a list of the formset cleaned_data dictionaries. + If a step contains a `FormSet`, the key will be prefixed with + 'formset-' and contain a list of the formset cleaned_data dictionaries. """ cleaned_data = {} for form_key in self.get_form_list(): @@ -458,8 +460,8 @@ class WizardView(TemplateView): def get_cleaned_data_for_step(self, step): """ Returns the cleaned data for a given `step`. Before returning the - cleaned data, the stored values are being revalidated through the - form. If the data doesn't validate, None will be returned. + cleaned data, the stored values are revalidated through the form. + If the data doesn't validate, None will be returned. """ if step in self.form_list: form_obj = self.get_form(step=step, diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index d1193badbe..3edc019d05 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -493,6 +493,21 @@ Advanced ``WizardView`` methods context = self.get_context_data(form=form, **kwargs) return self.render_to_response(context) +.. method:: WizardView.get_cleaned_data_for_step(step) + + This method returns the cleaned data for a given ``step``. Before returning + the cleaned data, the stored values are revalidated through the form. If + the data doesn't validate, ``None`` will be returned. + +.. method:: WizardView.get_all_cleaned_data() + + This method returns a merged dictionary of all form steps' ``cleaned_data`` + dictionaries. If a step contains a ``FormSet``, the key will be prefixed + with ``formset-`` and contain a list of the formset's ``cleaned_data`` + dictionaries. Note that if two or more steps have a field with the same + name, the value for that field from the latest step will overwrite the + value from any earlier steps. + Providing initial data for the forms ==================================== @@ -534,6 +549,16 @@ This storage will temporarily store the uploaded files for the wizard. The :attr:`file_storage` attribute should be a :class:`~django.core.files.storage.Storage` subclass. +Django provides a built-in storage class (see :ref:`the built-in filesystem +storage class `):: + + from django.conf import settings + from django.core.files.storage import FileSystemStorage + + class CustomWizardView(WizardView): + ... + file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'photos')) + .. warning:: Please remember to take care of removing old files as the diff --git a/docs/topics/files.txt b/docs/topics/files.txt index c9b4327941..66e104759a 100644 --- a/docs/topics/files.txt +++ b/docs/topics/files.txt @@ -139,6 +139,8 @@ useful -- you can use the global default storage system:: See :doc:`/ref/files/storage` for the file storage API. +.. _builtin-fs-storage: + The built-in filesystem storage class ------------------------------------- -- cgit v1.3 From 082fad0b83638332f85c5957eb8dcc5e38417608 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 2 Nov 2012 16:15:40 -0400 Subject: Cleaned up contrib.admin install instructions. Thanks Cal Leeming for the patch. --- docs/ref/contrib/admin/index.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 6ed929cb7d..b661806c76 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -26,9 +26,10 @@ There are seven steps in activating the Django admin site: in your :setting:`INSTALLED_APPS` list, add them. 3. Add ``django.contrib.messages.context_processors.messages`` to - :setting:`TEMPLATE_CONTEXT_PROCESSORS` and - :class:`~django.contrib.messages.middleware.MessageMiddleware` to - :setting:`MIDDLEWARE_CLASSES`. (These are both active by default, so + :setting:`TEMPLATE_CONTEXT_PROCESSORS` as well as + :class:`django.contrib.auth.middleware.AuthenticationMiddleware` and + :class:`django.contrib.messages.middleware.MessageMiddleware` to + :setting:`MIDDLEWARE_CLASSES`. (These are all active by default, so you only need to do this if you've manually tweaked the settings.) 4. Determine which of your application's models should be editable in the -- cgit v1.3 From d1de7596b207b3a7dea8203334ef1739db3b1c94 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 2 Nov 2012 16:48:55 -0400 Subject: Fixed #19120 - Added an example of using ModelAdmin methods for read-only fields. Thanks Daniele Procida for the patch. --- docs/ref/contrib/admin/index.txt | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index b661806c76..f6da5b6cb2 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -816,15 +816,34 @@ subclass:: By default the admin shows all fields as editable. Any fields in this option (which should be a ``list`` or ``tuple``) will display its data - as-is and non-editable. This option behaves nearly identical to - :attr:`ModelAdmin.list_display`. Usage is the same, however, when you - specify :attr:`ModelAdmin.fields` or :attr:`ModelAdmin.fieldsets` the - read-only fields must be present to be shown (they are ignored otherwise). + as-is and non-editable. Note that when specifying :attr:`ModelAdmin.fields` + or :attr:`ModelAdmin.fieldsets` the read-only fields must be present to be + shown (they are ignored otherwise). If ``readonly_fields`` is used without defining explicit ordering through :attr:`ModelAdmin.fields` or :attr:`ModelAdmin.fieldsets` they will be added last after all editable fields. + A read-only field can not only display data from a model's field, it can + also display the output of a a model's method or a method of the + ``ModelAdmin`` class itself. This is very similar to the way + :attr:`ModelAdmin.list_display` behaves. This provides an easy way to use + the admin interface to provide feedback on the status of the objects being + edited, for example:: + + class PersonAdmin(ModelAdmin): + readonly_fields = ('address_report',) + + def address_report(self, instance): + return ", ".join(instance.get_full_address()) or \ + "I can't determine this address." + + # short_description functions like a model field's verbose_name + address_report.short_description = "Address" + # in this example, we have used HTML tags in the output + address_report.allow_tags = True + + .. attribute:: ModelAdmin.save_as Set ``save_as`` to enable a "save as" feature on admin change forms. -- cgit v1.3 From 965cc0b1ffa27574e5684a18f9400577e5b4598d Mon Sep 17 00:00:00 2001 From: Preston Holmes Date: Fri, 2 Nov 2012 15:49:29 +0000 Subject: Deprecated depth kwarg on select_related. This is the start of a deprecation path for the depth kwarg on select_related. Removing this will allow us to update select_related so it chains properly and have an API similar to prefetch_related. Thanks to Marc Tamlyn for spearheading and initial patch. refs #16855 --- django/db/models/query.py | 4 ++++ docs/internals/deprecation.txt | 3 +++ docs/ref/models/querysets.txt | 30 ++++++++++++++++-------------- docs/releases/1.5.txt | 7 +++++++ 4 files changed, 30 insertions(+), 14 deletions(-) (limited to 'docs/ref') diff --git a/django/db/models/query.py b/django/db/models/query.py index da4c69f362..c00080abef 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -5,6 +5,7 @@ The main QuerySet implementation. This provides the public API for the ORM. import copy import itertools import sys +import warnings from django.core import exceptions from django.db import connections, router, transaction, IntegrityError @@ -698,6 +699,9 @@ class QuerySet(object): If fields are specified, they must be ForeignKey fields and only those related objects are included in the selection. """ + if 'depth' in kwargs: + warnings.warn('The "depth" keyword argument has been deprecated.\n' + 'Use related field names instead.', PendingDeprecationWarning) depth = kwargs.pop('depth', 0) if kwargs: raise TypeError('Unexpected keyword arguments to select_related: %s' diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 77371c8608..9fd92db2b4 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -298,6 +298,9 @@ these changes. * The ``daily_cleanup.py`` script will be removed. +* The ``depth`` keyword argument will be removed from + :meth:`~django.db.models.query.QuerySet.select_related`. + 2.0 --- diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 7138cd0e74..295c996af4 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -676,21 +676,12 @@ Note that, by default, ``select_related()`` does not follow foreign keys that have ``null=True``. Usually, using ``select_related()`` can vastly improve performance because your -app can avoid many database calls. However, in situations with deeply nested -sets of relationships ``select_related()`` can sometimes end up following "too -many" relations, and can generate queries so large that they end up being slow. +app can avoid many database calls. However, there are times you are only +interested in specific related models, or have deeply nested sets of +relationships, and in these cases ``select_related()`` can can be optimized by +explicitly passing the related field names you are interested in. Only +the specified relations will be followed. -In these situations, you can use the ``depth`` argument to ``select_related()`` -to control how many "levels" of relations ``select_related()`` will actually -follow:: - - b = Book.objects.select_related(depth=1).get(id=4) - p = b.author # Doesn't hit the database. - c = p.hometown # Requires a database call. - -Sometimes you only want to access specific models that are related to your root -model, not all of the related models. In these cases, you can pass the related -field names to ``select_related()`` and it will only follow those relations. You can even do this for models that are more than one relation away by separating the field names with double underscores, just as for filters. For example, if you have this model:: @@ -730,6 +721,17 @@ You can also refer to the reverse direction of a is defined. Instead of specifying the field name, use the :attr:`related_name ` for the field on the related object. +.. deprecated:: 1.5 + The ``depth`` parameter to ``select_related()`` has been deprecated. You + should replace it with the use of the ``(*fields)`` listing specific + related fields instead as documented above. + +A depth limit of relationships to follow can also be specified:: + + b = Book.objects.select_related(depth=1).get(id=4) + p = b.author # Doesn't hit the database. + c = p.hometown # Requires a database call. + A :class:`~django.db.models.OneToOneField` is not traversed in the reverse direction if you are performing a depth-based ``select_related()`` call. diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index e18a78afc8..154f711560 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -638,3 +638,10 @@ The :djadmin:`cleanup` management command has been deprecated and replaced by The undocumented ``daily_cleanup.py`` script has been deprecated. Use the :djadmin:`clearsessions` management command instead. + +``depth`` keyword argument in ``select_related`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``depth`` keyword argument in +:meth:`~django.db.models.query.QuerySet.select_related` has been deprecated. +You should use field names instead. -- cgit v1.3 From 39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 3 Nov 2012 05:22:34 -0400 Subject: Fixed #16841 - Documented a couple ModelAdmin methods * ModelAdmin.get_changelist_form and get_changelist_formset * InlineModelAdmin.get_formset Thanks Jordan Reiter for the report. --- docs/ref/contrib/admin/index.txt | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index f6da5b6cb2..ee1342b43d 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1233,10 +1233,39 @@ templates used by the :class:`ModelAdmin` views: .. method:: ModelAdmin.get_changelist(self, request, **kwargs) - Returns the Changelist class to be used for listing. By default, + Returns the ``Changelist`` class to be used for listing. By default, ``django.contrib.admin.views.main.ChangeList`` is used. By inheriting this class you can change the behavior of the listing. +.. method:: ModelAdmin.get_changelist_form(self, request, **kwargs) + + Returns a :class:`~django.forms.ModelForm` class for use in the ``Formset`` + on the changelist page. To use a custom form, for example:: + + class MyForm(forms.ModelForm): + class Meta: + model = MyModel + + class MyModelAdmin(admin.ModelAdmin): + def get_changelist_form(self, request, **kwargs): + return MyForm + +.. method:: ModelAdmin.get_changelist_formset(self, request, **kwargs) + + Returns a :ref:`ModelFormSet ` class for use on the + changelist page if :attr:`~ModelAdmin.list_editable` is used. To use a + custom formset, for example:: + + from django.forms.models import BaseModelFormSet + + class MyAdminFormSet(BaseModelFormSet): + pass + + class MyModelAdmin(admin.ModelAdmin): + def get_changelist_formset(self, request, **kwargs): + kwargs['formset'] = MyAdminFormSet + return super(MyModelAdmin, self).get_changelist_formset(request, **kwargs) + .. method:: ModelAdmin.has_add_permission(self, request) Should return ``True`` if adding an object is permitted, ``False`` @@ -1552,6 +1581,10 @@ The ``InlineModelAdmin`` class adds: Specifies whether or not inline objects can be deleted in the inline. Defaults to ``True``. +.. method:: InlineModelAdmin.get_formset(self, request, obj=None, **kwargs) + + Returns a ``BaseInlineFormSet`` class for use in admin add/change views. + See the example for :class:`ModelAdmin.get_formsets`. Working with a model with two or more foreign keys to the same parent model --------------------------------------------------------------------------- -- cgit v1.3 From ac2052ebc84c45709ab5f0f25e685bf656ce79bc Mon Sep 17 00:00:00 2001 From: Ulrich Petri Date: Sat, 7 Jul 2012 14:24:50 +0200 Subject: Fixed #17549 -- Added a clickable link for URLFields in admin change list. --- django/contrib/admin/static/admin/css/widgets.css | 15 +++++++++++ django/contrib/admin/widgets.py | 15 ++++++++++- docs/ref/models/fields.txt | 5 ++++ tests/regressiontests/admin_widgets/tests.py | 31 +++++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) (limited to 'docs/ref') diff --git a/django/contrib/admin/static/admin/css/widgets.css b/django/contrib/admin/static/admin/css/widgets.css index 0a7012c7b2..3b19353e6f 100644 --- a/django/contrib/admin/static/admin/css/widgets.css +++ b/django/contrib/admin/static/admin/css/widgets.css @@ -225,6 +225,21 @@ table p.datetime { padding-left: 0; } +/* URL */ + +p.url { + line-height: 20px; + margin: 0; + padding: 0; + color: #666; + font-size: 11px; + font-weight: bold; +} + +.url a { + font-weight: normal; +} + /* FILE UPLOADS */ p.file-upload { diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 1e0bc2d366..1e6277fb87 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -10,7 +10,7 @@ from django.contrib.admin.templatetags.admin_static import static from django.core.urlresolvers import reverse from django.forms.widgets import RadioFieldRenderer from django.forms.util import flatatt -from django.utils.html import escape, format_html, format_html_join +from django.utils.html import escape, format_html, format_html_join, smart_urlquote from django.utils.text import Truncator from django.utils.translation import ugettext as _ from django.utils.safestring import mark_safe @@ -306,6 +306,19 @@ class AdminURLFieldWidget(forms.TextInput): final_attrs.update(attrs) super(AdminURLFieldWidget, self).__init__(attrs=final_attrs) + def render(self, name, value, attrs=None): + html = super(AdminURLFieldWidget, self).render(name, value, attrs) + if value: + value = force_text(self._format_value(value)) + final_attrs = {'href': mark_safe(smart_urlquote(value))} + html = format_html( + '

{0} {2}
{3} {4}

', + _('Currently:'), flatatt(final_attrs), value, + _('Change:'), html + ) + return html + + class AdminIntegerFieldWidget(forms.TextInput): class_name = 'vIntegerField' diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 809d56eaf5..d8ea6bb31d 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -922,6 +922,11 @@ Like all :class:`CharField` subclasses, :class:`URLField` takes the optional :attr:`~CharField.max_length`argument. If you don't specify :attr:`~CharField.max_length`, a default of 200 is used. +.. versionadded:: 1.5 + +The current value of the field will be displayed as a clickable link above the +input widget. + Relationship fields =================== diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py index 4b115431c1..0b016d885b 100644 --- a/tests/regressiontests/admin_widgets/tests.py +++ b/tests/regressiontests/admin_widgets/tests.py @@ -266,6 +266,37 @@ class AdminSplitDateTimeWidgetTest(DjangoTestCase): ) +class AdminURLWidgetTest(DjangoTestCase): + def test_render(self): + w = widgets.AdminURLFieldWidget() + self.assertHTMLEqual( + conditional_escape(w.render('test', '')), + '' + ) + self.assertHTMLEqual( + conditional_escape(w.render('test', 'http://example.com')), + '

Currently:http://example.com
Change:

' + ) + + def test_render_idn(self): + w = widgets.AdminURLFieldWidget() + self.assertHTMLEqual( + conditional_escape(w.render('test', 'http://example-äüö.com')), + '

Currently:http://example-äüö.com
Change:

' + ) + + def test_render_quoting(self): + w = widgets.AdminURLFieldWidget() + self.assertHTMLEqual( + conditional_escape(w.render('test', 'http://example.com/some text')), + '

Currently:http://example.com/<sometag>some text</sometag>
Change:

' + ) + self.assertHTMLEqual( + conditional_escape(w.render('test', 'http://example-äüö.com/some text')), + '

Currently:http://example-äüö.com/<sometag>some text</sometag>
Change:

' + ) + + class AdminFileWidgetTest(DjangoTestCase): def test_render(self): band = models.Band.objects.create(name='Linkin Park') -- cgit v1.3 From 249c3d730e632b3c5b8c2bf5e6e871d61df15c6c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 4 Nov 2012 05:32:53 -0500 Subject: Fixed #19090 - Added PostgreSQL connection note. Thanks Melevir for the patch. --- docs/ref/databases.txt | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'docs/ref') diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 3a52f838e7..946d0f4f3b 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -33,6 +33,15 @@ aggregate with a database backend that falls within the affected release range. .. _known to be faulty: http://archives.postgresql.org/pgsql-bugs/2007-07/msg00046.php .. _Release 8.2.5: http://www.postgresql.org/docs/devel/static/release-8-2-5.html +PostgreSQL connection settings +------------------------------ + +By default (empty :setting:`HOST`), the connection to the database is done +through UNIX domain sockets ('local' lines in pg_hba.conf). If you want to +connect through TCP sockets, set :setting:`HOST` to 'localhost' or '127.0.0.1' +('host' lines in pg_hba.conf). On Windows, you should always define +:setting:`HOST`, as UNIX domain sockets are not available. + Optimizing PostgreSQL's configuration ------------------------------------- -- cgit v1.3 From 4285571c5a9bf6ca3cb7c4d774942b9ae5b537e4 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 4 Nov 2012 10:16:06 -0800 Subject: Fixed #5805 -- it is now possible to specify multi-column indexes. Thanks to jgelens for the original patch. --- django/core/management/validation.py | 35 ++++++++++----- django/db/backends/creation.py | 51 ++++++++++++++-------- django/db/models/options.py | 4 +- docs/ref/models/options.txt | 15 +++++++ .../invalid_models/invalid_models/models.py | 8 ++++ tests/regressiontests/indexes/__init__.py | 0 tests/regressiontests/indexes/models.py | 11 +++++ tests/regressiontests/indexes/tests.py | 12 +++++ tests/regressiontests/initial_sql_regress/tests.py | 7 ++- tests/regressiontests/introspection/models.py | 4 ++ tests/regressiontests/introspection/tests.py | 12 ++--- tests/runtests.py | 2 +- 12 files changed, 121 insertions(+), 40 deletions(-) create mode 100644 tests/regressiontests/indexes/__init__.py create mode 100644 tests/regressiontests/indexes/models.py create mode 100644 tests/regressiontests/indexes/tests.py (limited to 'docs/ref') diff --git a/django/core/management/validation.py b/django/core/management/validation.py index 957a712b72..32e7181dab 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -1,3 +1,4 @@ +import collections import sys from django.conf import settings @@ -327,15 +328,29 @@ def get_validation_errors(outfile, app=None): # Check unique_together. for ut in opts.unique_together: - for field_name in ut: - try: - f = opts.get_field(field_name, many_to_many=True) - except models.FieldDoesNotExist: - e.add(opts, '"unique_together" refers to %s, a field that doesn\'t exist. Check your syntax.' % field_name) - else: - if isinstance(f.rel, models.ManyToManyRel): - e.add(opts, '"unique_together" refers to %s. ManyToManyFields are not supported in unique_together.' % f.name) - if f not in opts.local_fields: - e.add(opts, '"unique_together" refers to %s. This is not in the same model as the unique_together statement.' % f.name) + validate_local_fields(e, opts, "unique_together", ut) + if not isinstance(opts.index_together, collections.Sequence): + e.add(opts, '"index_together" must a sequence') + else: + for it in opts.index_together: + validate_local_fields(e, opts, "index_together", it) return len(e.errors) + + +def validate_local_fields(e, opts, field_name, fields): + from django.db import models + + if not isinstance(fields, collections.Sequence): + e.add(opts, 'all %s elements must be sequences' % field_name) + else: + for field in fields: + try: + f = opts.get_field(field, many_to_many=True) + except models.FieldDoesNotExist: + e.add(opts, '"%s" refers to %s, a field that doesn\'t exist.' % (field_name, field)) + else: + if isinstance(f.rel, models.ManyToManyRel): + e.add(opts, '"%s" refers to %s. ManyToManyFields are not supported in %s.' % (field_name, f.name, field_name)) + if f not in opts.local_fields: + e.add(opts, '"%s" refers to %s. This is not in the same model as the %s statement.' % (field_name, f.name, field_name)) diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index 3262a8922f..4c4cf4d044 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -177,34 +177,47 @@ class BaseDatabaseCreation(object): output = [] for f in model._meta.local_fields: output.extend(self.sql_indexes_for_field(model, f, style)) + for fs in model._meta.index_together: + fields = [model._meta.get_field_by_name(f)[0] for f in fs] + output.extend(self.sql_indexes_for_fields(model, fields, style)) return output def sql_indexes_for_field(self, model, f, style): """ Return the CREATE INDEX SQL statements for a single model field. """ + if f.db_index and not f.unique: + return self.sql_indexes_for_fields(model, [f], style) + else: + return [] + + def sql_indexes_for_fields(self, model, fields, style): from django.db.backends.util import truncate_name - if f.db_index and not f.unique: - qn = self.connection.ops.quote_name - tablespace = f.db_tablespace or model._meta.db_tablespace - if tablespace: - tablespace_sql = self.connection.ops.tablespace_sql(tablespace) - if tablespace_sql: - tablespace_sql = ' ' + tablespace_sql - else: - tablespace_sql = '' - i_name = '%s_%s' % (model._meta.db_table, self._digest(f.column)) - output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' + - style.SQL_TABLE(qn(truncate_name( - i_name, self.connection.ops.max_name_length()))) + ' ' + - style.SQL_KEYWORD('ON') + ' ' + - style.SQL_TABLE(qn(model._meta.db_table)) + ' ' + - "(%s)" % style.SQL_FIELD(qn(f.column)) + - "%s;" % tablespace_sql] + if len(fields) == 1 and fields[0].db_tablespace: + tablespace_sql = self.connection.ops.tablespace_sql(fields[0].db_tablespace) + elif model._meta.db_tablespace: + tablespace_sql = self.connection.ops.tablespace_sql(model._meta.db_tablespace) else: - output = [] - return output + tablespace_sql = "" + if tablespace_sql: + tablespace_sql = " " + tablespace_sql + + field_names = [] + qn = self.connection.ops.quote_name + for f in fields: + field_names.append(style.SQL_FIELD(qn(f.column))) + + index_name = "%s_%s" % (model._meta.db_table, self._digest([f.name for f in fields])) + + return [ + style.SQL_KEYWORD("CREATE INDEX") + " " + + style.SQL_TABLE(qn(truncate_name(index_name, self.connection.ops.max_name_length()))) + " " + + style.SQL_KEYWORD("ON") + " " + + style.SQL_TABLE(qn(model._meta.db_table)) + " " + + "(%s)" % style.SQL_FIELD(", ".join(field_names)) + + "%s;" % tablespace_sql, + ] def sql_destroy_model(self, model, references_to_delete, style): """ diff --git a/django/db/models/options.py b/django/db/models/options.py index f430caceef..b04f3d4c2d 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -21,7 +21,8 @@ get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]| DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering', 'unique_together', 'permissions', 'get_latest_by', 'order_with_respect_to', 'app_label', 'db_tablespace', - 'abstract', 'managed', 'proxy', 'swappable', 'auto_created') + 'abstract', 'managed', 'proxy', 'swappable', 'auto_created', + 'index_together') @python_2_unicode_compatible @@ -34,6 +35,7 @@ class Options(object): self.db_table = '' self.ordering = [] self.unique_together = [] + self.index_together = [] self.permissions = [] self.object_name, self.app_label = None, app_label self.get_latest_by = None diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index c5ae8398ea..ab944d7dda 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -261,6 +261,21 @@ Django quotes column and table names behind the scenes. :class:`~django.db.models.ManyToManyField`, try using a signal or an explicit :attr:`through ` model. +``index_together`` + +.. versionadded:: 1.5 + +.. attribute:: Options.index_together + + Sets of field names that, taken together, are indexed:: + + index_together = [ + ["pub_date", "deadline"], + ] + + This list of fields will be indexed together (i.e. the appropriate + ``CREATE INDEX`` statement will be issued.) + ``verbose_name`` ---------------- diff --git a/tests/modeltests/invalid_models/invalid_models/models.py b/tests/modeltests/invalid_models/invalid_models/models.py index ccb6396352..3c21e1ddb8 100644 --- a/tests/modeltests/invalid_models/invalid_models/models.py +++ b/tests/modeltests/invalid_models/invalid_models/models.py @@ -356,6 +356,13 @@ class HardReferenceModel(models.Model): m2m_4 = models.ManyToManyField('invalid_models.SwappedModel', related_name='m2m_hardref4') +class BadIndexTogether1(models.Model): + class Meta: + index_together = [ + ["field_that_does_not_exist"], + ] + + model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute that is a positive integer. invalid_models.fielderrors: "charfield2": CharFields require a "max_length" attribute that is a positive integer. invalid_models.fielderrors: "charfield3": CharFields require a "max_length" attribute that is a positive integer. @@ -470,6 +477,7 @@ invalid_models.hardreferencemodel: 'm2m_3' defines a relation with the model 'in invalid_models.hardreferencemodel: 'm2m_4' defines a relation with the model 'invalid_models.SwappedModel', which has been swapped out. Update the relation to point at settings.TEST_SWAPPED_MODEL. invalid_models.badswappablevalue: TEST_SWAPPED_MODEL_BAD_VALUE is not of the form 'app_label.app_name'. invalid_models.badswappablemodel: Model has been swapped out for 'not_an_app.Target' which has not been installed or is abstract. +invalid_models.badindextogether1: "index_together" refers to field_that_does_not_exist, a field that doesn't exist. """ if not connection.features.interprets_empty_strings_as_nulls: diff --git a/tests/regressiontests/indexes/__init__.py b/tests/regressiontests/indexes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/indexes/models.py b/tests/regressiontests/indexes/models.py new file mode 100644 index 0000000000..9758377f99 --- /dev/null +++ b/tests/regressiontests/indexes/models.py @@ -0,0 +1,11 @@ +from django.db import models + + +class Article(models.Model): + headline = models.CharField(max_length=100) + pub_date = models.DateTimeField() + + class Meta: + index_together = [ + ["headline", "pub_date"], + ] diff --git a/tests/regressiontests/indexes/tests.py b/tests/regressiontests/indexes/tests.py new file mode 100644 index 0000000000..0dac881fa9 --- /dev/null +++ b/tests/regressiontests/indexes/tests.py @@ -0,0 +1,12 @@ +from django.core.management.color import no_style +from django.db import connections, DEFAULT_DB_ALIAS +from django.test import TestCase + +from .models import Article + + +class IndexesTests(TestCase): + def test_index_together(self): + connection = connections[DEFAULT_DB_ALIAS] + index_sql = connection.creation.sql_indexes_for_model(Article, no_style()) + self.assertEqual(len(index_sql), 1) diff --git a/tests/regressiontests/initial_sql_regress/tests.py b/tests/regressiontests/initial_sql_regress/tests.py index 03a91cb807..39d8921061 100644 --- a/tests/regressiontests/initial_sql_regress/tests.py +++ b/tests/regressiontests/initial_sql_regress/tests.py @@ -1,3 +1,6 @@ +from django.core.management.color import no_style +from django.core.management.sql import custom_sql_for_model +from django.db import connections, DEFAULT_DB_ALIAS from django.test import TestCase from .models import Simple @@ -15,10 +18,6 @@ class InitialSQLTests(TestCase): self.assertEqual(Simple.objects.count(), 0) def test_custom_sql(self): - from django.core.management.sql import custom_sql_for_model - from django.core.management.color import no_style - from django.db import connections, DEFAULT_DB_ALIAS - # Simulate the custom SQL loading by syncdb connection = connections[DEFAULT_DB_ALIAS] custom_sql = custom_sql_for_model(Simple, no_style(), connection) diff --git a/tests/regressiontests/introspection/models.py b/tests/regressiontests/introspection/models.py index 6e5beba61d..4de82e47e7 100644 --- a/tests/regressiontests/introspection/models.py +++ b/tests/regressiontests/introspection/models.py @@ -17,6 +17,7 @@ class Reporter(models.Model): def __str__(self): return "%s %s" % (self.first_name, self.last_name) + @python_2_unicode_compatible class Article(models.Model): headline = models.CharField(max_length=100) @@ -28,3 +29,6 @@ class Article(models.Model): class Meta: ordering = ('headline',) + index_together = [ + ["headline", "pub_date"], + ] diff --git a/tests/regressiontests/introspection/tests.py b/tests/regressiontests/introspection/tests.py index 4b8a3277e2..2df946d874 100644 --- a/tests/regressiontests/introspection/tests.py +++ b/tests/regressiontests/introspection/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import,unicode_literals +from __future__ import absolute_import, unicode_literals from functools import update_wrapper @@ -13,7 +13,7 @@ if connection.vendor == 'oracle': else: expectedFailureOnOracle = lambda f: f -# + # The introspection module is optional, so methods tested here might raise # NotImplementedError. This is perfectly acceptable behavior for the backend # in question, but the tests need to handle this without failing. Ideally we'd @@ -23,7 +23,7 @@ else: # wrapper that ignores the exception. # # The metaclass is just for fun. -# + def ignore_not_implemented(func): def _inner(*args, **kwargs): @@ -34,15 +34,16 @@ def ignore_not_implemented(func): update_wrapper(_inner, func) return _inner + class IgnoreNotimplementedError(type): def __new__(cls, name, bases, attrs): - for k,v in attrs.items(): + for k, v in attrs.items(): if k.startswith('test'): attrs[k] = ignore_not_implemented(v) return type.__new__(cls, name, bases, attrs) -class IntrospectionTests(six.with_metaclass(IgnoreNotimplementedError, TestCase)): +class IntrospectionTests(six.with_metaclass(IgnoreNotimplementedError, TestCase)): def test_table_names(self): tl = connection.introspection.table_names() self.assertEqual(tl, sorted(tl)) @@ -163,6 +164,7 @@ class IntrospectionTests(six.with_metaclass(IgnoreNotimplementedError, TestCase) self.assertNotIn('first_name', indexes) self.assertIn('id', indexes) + def datatype(dbtype, description): """Helper to convert a data type into a string.""" dt = connection.introspection.get_field_type(dbtype, description) diff --git a/tests/runtests.py b/tests/runtests.py index a81fee6858..90e2dc2d65 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -277,7 +277,7 @@ if __name__ == "__main__": usage = "%prog [options] [module module module ...]" parser = OptionParser(usage=usage) parser.add_option( - '-v','--verbosity', action='store', dest='verbosity', default='1', + '-v', '--verbosity', action='store', dest='verbosity', default='1', type='choice', choices=['0', '1', '2', '3'], help='Verbosity level; 0=minimal output, 1=normal output, 2=all ' 'output') -- cgit v1.3 From d94dc2d1fa190d202d19a351f00a6b42e670fecb Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sun, 4 Nov 2012 09:52:37 -0800 Subject: Fixed formatting of get_FOO_display example --- docs/ref/models/instances.txt | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 1ba41148b0..b4872e3e5c 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -616,25 +616,25 @@ the field. This method returns the "human-readable" value of the field. For example:: - from django.db import models - - class Person(models.Model): - SHIRT_SIZES = ( - (u'S', u'Small'), - (u'M', u'Medium'), - (u'L', u'Large'), - ) - name = models.CharField(max_length=60) - shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES) - - :: - - >>> p = Person(name="Fred Flintstone", shirt_size="L") - >>> p.save() - >>> p.shirt_size - u'L' - >>> p.get_shirt_size_display() - u'Large' + from django.db import models + + class Person(models.Model): + SHIRT_SIZES = ( + (u'S', u'Small'), + (u'M', u'Medium'), + (u'L', u'Large'), + ) + name = models.CharField(max_length=60) + shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES) + +:: + + >>> p = Person(name="Fred Flintstone", shirt_size="L") + >>> p.save() + >>> p.shirt_size + u'L' + >>> p.get_shirt_size_display() + u'Large' .. method:: Model.get_next_by_FOO(\**kwargs) .. method:: Model.get_previous_by_FOO(\**kwargs) -- cgit v1.3 From 957787ace0a14fa2ee2539d47a64b266bc93b6bd Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 4 Nov 2012 15:41:33 -0800 Subject: Added multi-column indexes to the 1.5 release notes. --- docs/ref/models/options.txt | 4 ++-- docs/releases/1.5.txt | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index ab944d7dda..a577135271 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -263,10 +263,10 @@ Django quotes column and table names behind the scenes. ``index_together`` -.. versionadded:: 1.5 - .. attribute:: Options.index_together + .. versionadded:: 1.5 + Sets of field names that, taken together, are indexed:: index_together = [ diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 154f711560..a8024424bd 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -302,6 +302,10 @@ Django 1.5 also includes several smaller improvements worth noting: * The :ref:`cache-based session backend ` can store session data in a non-default cache. +* Multi-column indexes can now be created on models. Read the + :attr:`~django.db.models.Options.index_together` documentation for more + infomration. + Backwards incompatible changes in 1.5 ===================================== -- cgit v1.3 From d5c3c45f2fdfee09d81ad8dc7b0db8338d6d0aae Mon Sep 17 00:00:00 2001 From: Daniel Greenfeld Date: Sun, 4 Nov 2012 16:35:40 -0800 Subject: Demonstrate how to round to integers using floatformat templatetag --- docs/ref/templates/builtins.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'docs/ref') diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 4aa1a990cd..175e9dfd5d 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1517,6 +1517,17 @@ displayed. For example: ``34.26000`` ``{{ value|floatformat:"-3" }}`` ``34.260`` ============ ================================ ========== +If the argument passed to ``floatformat`` is 0 (zero), it will round the number +to the nearest integer. + +============ ================================ ========== +``value`` Template Output +============ ================================ ========== +``34.23234`` ``{{ value|floatformat:"0" }}`` ``34`` +``34.00000`` ``{{ value|floatformat:"0" }}`` ``34`` +``39.56000`` ``{{ value|floatformat:"0" }}`` ``40`` +============ ================================ ========== + Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with an argument of ``-1``. -- cgit v1.3 From a70492e6b532905c921678f35b5c60b22387f1c6 Mon Sep 17 00:00:00 2001 From: Daniel Greenfeld Date: Sun, 4 Nov 2012 16:35:40 -0800 Subject: Fixed #19241 -- Improved floatformat docs Demonstrate how to round to integers using floatformat templatetag --- docs/ref/templates/builtins.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'docs/ref') diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 4aa1a990cd..c7fab8c53d 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1505,6 +1505,17 @@ that many decimal places. For example: ``34.26000`` ``{{ value|floatformat:3 }}`` ``34.260`` ============ ============================= ========== +Particularly useful is passing 0 (zero) as the argument which will round the +float to the nearest integer. + +============ ================================ ========== +``value`` Template Output +============ ================================ ========== +``34.23234`` ``{{ value|floatformat:"0" }}`` ``34`` +``34.00000`` ``{{ value|floatformat:"0" }}`` ``34`` +``39.56000`` ``{{ value|floatformat:"0" }}`` ``40`` +============ ================================ ========== + If the argument passed to ``floatformat`` is negative, it will round a number to that many decimal places -- but only if there's a decimal part to be displayed. For example: -- cgit v1.3 From 2cb48fffd4eecdaf77231d8acce6e6fa484698a2 Mon Sep 17 00:00:00 2001 From: Preston Holmes Date: Sun, 4 Nov 2012 19:04:07 -0800 Subject: Removed redundant docs addition across two commits d5c3c45f2fdfee09d81ad8dc7b0db8338d6d0aae a70492e6b532905c921678f35b5c60b22387f1c6 --- docs/ref/templates/builtins.txt | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 3220b5f611..c7fab8c53d 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1528,17 +1528,6 @@ displayed. For example: ``34.26000`` ``{{ value|floatformat:"-3" }}`` ``34.260`` ============ ================================ ========== -If the argument passed to ``floatformat`` is 0 (zero), it will round the number -to the nearest integer. - -============ ================================ ========== -``value`` Template Output -============ ================================ ========== -``34.23234`` ``{{ value|floatformat:"0" }}`` ``34`` -``34.00000`` ``{{ value|floatformat:"0" }}`` ``34`` -``39.56000`` ``{{ value|floatformat:"0" }}`` ``40`` -============ ================================ ========== - Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with an argument of ``-1``. -- cgit v1.3 From d3fd8a151231726adacb99bdbcd573f95ce32262 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 5 Nov 2012 07:14:40 -0500 Subject: Fixed #15591 - Clarified interaction between ModelForm and model validation. --- docs/ref/models/instances.txt | 17 ++++++++++------- docs/topics/forms/modelforms.txt | 12 +++++++++--- 2 files changed, 19 insertions(+), 10 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index b4872e3e5c..1d0ac08061 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -67,9 +67,9 @@ Validating objects There are three steps involved in validating a model: -1. Validate the model fields -2. Validate the model as a whole -3. Validate the field uniqueness +1. Validate the model fields - :meth:`Model.clean_fields()` +2. Validate the model as a whole - :meth:`Model.clean()` +3. Validate the field uniqueness - :meth:`Model.validate_unique()` All three steps are performed when you call a model's :meth:`~Model.full_clean()` method. @@ -97,17 +97,20 @@ not be corrected by the user. Note that ``full_clean()`` will *not* be called automatically when you call your model's :meth:`~Model.save()` method, nor as a result of -:class:`~django.forms.ModelForm` validation. You'll need to call it manually -when you want to run one-step model validation for your own manually created -models. +:class:`~django.forms.ModelForm` validation. In the case of +:class:`~django.forms.ModelForm` validation, :meth:`Model.clean_fields()`, +:meth:`Model.clean()`, and :meth:`Model.validate_unique()` are all called +individually. -Example:: +You'll need to call ``full_clean`` manually when you want to run one-step model +validation for your own manually created models. For example:: try: article.full_clean() except ValidationError as e: # Do something based on the errors contained in e.message_dict. # Display them to a user, or handle them programatically. + pass The first step ``full_clean()`` performs is to clean each individual field. diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 692be7cd7c..fcc9bd6a7f 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -192,6 +192,8 @@ we'll discuss in a moment.):: name = forms.CharField(max_length=100) authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all()) +.. _modelform-is-valid-and-errors: + The ``is_valid()`` method and ``errors`` ---------------------------------------- @@ -213,7 +215,9 @@ method. This method creates and saves a database object from the data bound to the form. A subclass of ``ModelForm`` can accept an existing model instance as the keyword argument ``instance``; if this is supplied, ``save()`` will update that instance. If it's not supplied, -``save()`` will create a new instance of the specified model:: +``save()`` will create a new instance of the specified model: + +.. code-block:: python # Create a form instance from POST data. >>> f = ArticleForm(request.POST) @@ -232,8 +236,10 @@ supplied, ``save()`` will update that instance. If it's not supplied, >>> f = ArticleForm(request.POST, instance=a) >>> f.save() -Note that ``save()`` will raise a ``ValueError`` if the data in the form -doesn't validate -- i.e., if form.errors evaluates to True. +Note that if the form :ref:`hasn't been validated +`, calling ``save()`` will do so by checking +``form.errors``. A ``ValueError`` will be raised if the data in the form +doesn't validate -- i.e., if ``form.errors`` evaluates to ``True``. This ``save()`` method accepts an optional ``commit`` keyword argument, which accepts either ``True`` or ``False``. If you call ``save()`` with -- cgit v1.3 From 2cc1884383a0b5371854be6806851521b623f45b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 Nov 2012 05:16:01 -0500 Subject: Fixed #19246 - Updated SECURE_PROXY_SSL_HEADER example to use 'X-Forwarded-Proto' Thanks Fred Palmer for the report. --- docs/ref/settings.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index e8b41afb39..5544c99dd1 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1560,9 +1560,9 @@ for. You'll need to set a tuple with two elements -- the name of the header to look for and the required value. For example:: - SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') + SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') -Here, we're telling Django that we trust the ``X-Forwarded-Protocol`` header +Here, we're telling Django that we trust the ``X-Forwarded-Proto`` header that comes from our proxy, and any time its value is ``'https'``, then the request is guaranteed to be secure (i.e., it originally came in via HTTPS). Obviously, you should *only* set this setting if you control your proxy or @@ -1575,16 +1575,18 @@ available in ``request.META``.) .. warning:: - **You will probably open security holes in your site if you set this without knowing what you're doing. And if you fail to set it when you should. Seriously.** + **You will probably open security holes in your site if you set this + without knowing what you're doing. And if you fail to set it when you + should. Seriously.** Make sure ALL of the following are true before setting this (assuming the values from the example above): * Your Django app is behind a proxy. - * Your proxy strips the 'X-Forwarded-Protocol' header from all incoming + * Your proxy strips the ``X-Forwarded-Proto`` header from all incoming requests. In other words, if end users include that header in their requests, the proxy will discard it. - * Your proxy sets the 'X-Forwarded-Protocol' header and sends it to Django, + * Your proxy sets the ``X-Forwarded-Proto`` header and sends it to Django, but only for requests that originally come in via HTTPS. If any of those are not true, you should keep this setting set to ``None`` -- cgit v1.3 From a386675a6a636b8e6b96277da8e9191e9dc710dc Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 Nov 2012 06:58:25 -0500 Subject: Fixed #15968 - Noted that readonly_fields are excluded from the ModelForm --- docs/ref/contrib/admin/index.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index ee1342b43d..29ee66bccc 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -816,9 +816,11 @@ subclass:: By default the admin shows all fields as editable. Any fields in this option (which should be a ``list`` or ``tuple``) will display its data - as-is and non-editable. Note that when specifying :attr:`ModelAdmin.fields` - or :attr:`ModelAdmin.fieldsets` the read-only fields must be present to be - shown (they are ignored otherwise). + as-is and non-editable; they are also excluded from the + :class:`~django.forms.ModelForm` used for creating and editing. Note that + when specifying :attr:`ModelAdmin.fields` or :attr:`ModelAdmin.fieldsets` + the read-only fields must be present to be shown (they are ignored + otherwise). If ``readonly_fields`` is used without defining explicit ordering through :attr:`ModelAdmin.fields` or :attr:`ModelAdmin.fieldsets` they will be -- cgit v1.3 From b0c72d0a308bcb41c676d36c24e6b2f85e00cd95 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Wed, 7 Nov 2012 17:13:06 +0100 Subject: Fixed invalid ipv4 mapped ipv6 addresses in docs --- docs/ref/forms/fields.txt | 2 +- docs/ref/models/fields.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 7c8d509031..75d05c6829 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -683,7 +683,7 @@ For each field, we describe the default widget used if you don't specify .. attribute:: unpack_ipv4 - Unpacks IPv4 mapped addresses like ``::ffff::192.0.2.1``. + Unpacks IPv4 mapped addresses like ``::ffff:192.0.2.1``. If this option is enabled that address would be unpacked to ``192.0.2.1``. Default is disabled. Can only be used when ``protocol`` is set to ``'both'``. diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index d8ea6bb31d..cd1185585c 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -825,7 +825,7 @@ are converted to lowercase. .. attribute:: GenericIPAddressField.unpack_ipv4 - Unpacks IPv4 mapped addresses like ``::ffff::192.0.2.1``. + Unpacks IPv4 mapped addresses like ``::ffff:192.0.2.1``. If this option is enabled that address would be unpacked to ``192.0.2.1``. Default is disabled. Can only be used when ``protocol`` is set to ``'both'``. -- cgit v1.3 From b1ac329ba9bbcba90d8ced7e16909ed169b1d16e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 7 Nov 2012 18:31:14 +0100 Subject: Fixed #19115 -- Documented stdout/stderr options for call_command Thanks d1ffuz0r for helping with the patch. --- docs/ref/django-admin.txt | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'docs/ref') diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index e0b08450e9..a5ed37f25f 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1469,3 +1469,12 @@ Examples:: from django.core import management management.call_command('flush', verbosity=0, interactive=False) management.call_command('loaddata', 'test_data', verbosity=0) + +Output redirection +================== + +Note that you can redirect standard output and error streams as all commands +support the ``stdout`` and ``stderr`` options. For example, you could write:: + + with open('/tmp/command_output') as f: + management.call_command('dumpdata', stdout=f) -- cgit v1.3 From 1db5d8827351acd2d039c218723d5100dc7e7f95 Mon Sep 17 00:00:00 2001 From: Daniel Greenfeld Date: Thu, 8 Nov 2012 16:32:16 -0800 Subject: Added examples for comment, templatetag, escape, force_escape, timesince, and timeuntil --- docs/ref/templates/builtins.txt | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index c7fab8c53d..ef9aa83955 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -53,6 +53,13 @@ comment Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. +Sample usage:: + +

Rendered text with {{ pub_date|date:"c" }}

+ {% comment %} +

Commented out text with {{ create_date|date:"c" }}

+ {% endcomment %} + .. templatetag:: csrf_token csrf_token @@ -947,6 +954,10 @@ Argument Outputs ``closecomment`` ``#}`` ================== ======= +Sample usage:: + + {% templatetag openblock %} url 'entry_list' {% templatetag closeblock %} + .. templatetag:: url url @@ -1409,6 +1420,12 @@ applied to the result will only result in one round of escaping being done. So it is safe to use this function even in auto-escaping environments. If you want multiple escaping passes to be applied, use the :tfilter:`force_escape` filter. +For example, you can apply ``escape`` to fields when :ttag:`autoescape` is off:: + + {% autoescape off %} + {{ title|escape }} + {% endautoescape %} + .. templatefilter:: escapejs escapejs @@ -1542,6 +1559,13 @@ string. This is useful in the rare cases where you need multiple escaping or want to apply other filters to the escaped results. Normally, you want to use the :tfilter:`escape` filter. +For example, if you want to catch the ```` HTML elements created by +the :tfilter:`linebreaks` filter:: + + {% autoescape off %} + {{ body|linebreaks|force_escape }} + {% endautoescape %} + .. templatefilter:: get_digit get_digit @@ -1979,7 +2003,9 @@ Takes an optional argument that is a variable containing the date to use as the comparison point (without the argument, the comparison point is *now*). For example, if ``blog_date`` is a date instance representing midnight on 1 June 2006, and ``comment_date`` is a date instance for 08:00 on 1 June 2006, -then ``{{ blog_date|timesince:comment_date }}`` would return "8 hours". +then the following would return "8 hours":: + + {{ blog_date|timesince:comment_date }} Comparing offset-naive and offset-aware datetimes will return an empty string. @@ -1998,7 +2024,9 @@ given date or datetime. For example, if today is 1 June 2006 and Takes an optional argument that is a variable containing the date to use as the comparison point (instead of *now*). If ``from_date`` contains 22 June -2006, then ``{{ conference_date|timeuntil:from_date }}`` will return "1 week". +2006, then the following will return "1 week":: + + {{ conference_date|timeuntil:from_date }} Comparing offset-naive and offset-aware datetimes will return an empty string. -- cgit v1.3 From 5bc6929f9af4d71065bce42578e49354096a7bf4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 9 Nov 2012 16:15:19 +0000 Subject: Include `versionadded 1.5` directive --- docs/ref/class-based-views/mixins-multiple-object.txt | 2 ++ 1 file changed, 2 insertions(+) (limited to 'docs/ref') diff --git a/docs/ref/class-based-views/mixins-multiple-object.txt b/docs/ref/class-based-views/mixins-multiple-object.txt index e6abf26e6a..bdcb43b163 100644 --- a/docs/ref/class-based-views/mixins-multiple-object.txt +++ b/docs/ref/class-based-views/mixins-multiple-object.txt @@ -74,6 +74,8 @@ MultipleObjectMixin .. attribute:: page_kwarg + .. versionadded:: 1.5 + A string specifying the name to use for the page parameter. The view will expect this prameter to be available either as a query string parameter (via ``request.GET``) or as a kwarg variable specified -- cgit v1.3 From 3f2fc2f41abf226913517eb1e655f823f2c5e53a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 9 Nov 2012 16:16:58 +0000 Subject: Formatting tweaks. --- docs/ref/class-based-views/mixins-multiple-object.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/ref') diff --git a/docs/ref/class-based-views/mixins-multiple-object.txt b/docs/ref/class-based-views/mixins-multiple-object.txt index bdcb43b163..fb5a1715e6 100644 --- a/docs/ref/class-based-views/mixins-multiple-object.txt +++ b/docs/ref/class-based-views/mixins-multiple-object.txt @@ -79,7 +79,7 @@ MultipleObjectMixin A string specifying the name to use for the page parameter. The view will expect this prameter to be available either as a query string parameter (via ``request.GET``) or as a kwarg variable specified - in the URLconf. Defaults to ``"page"``. + in the URLconf. Defaults to ``page``. .. attribute:: paginator_class -- cgit v1.3 From 17b14d481984f3a466a02993b35940430e5dbe91 Mon Sep 17 00:00:00 2001 From: Nicolas Ippolito Date: Mon, 12 Nov 2012 22:15:41 +0100 Subject: Typo in comments doc --- docs/ref/contrib/comments/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt index 1c6ff7c7ed..adda1eaf6c 100644 --- a/docs/ref/contrib/comments/index.txt +++ b/docs/ref/contrib/comments/index.txt @@ -209,7 +209,7 @@ default version of which is included with Django. Rendering a custom comment form ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you want more control over the look and feel of the comment form, you use use +If you want more control over the look and feel of the comment form, you may use :ttag:`get_comment_form` to get a :doc:`form object ` that you can use in the template:: -- cgit v1.3 From 3f65f751a0082b83ff24849a1445aa0838b27d93 Mon Sep 17 00:00:00 2001 From: Daniel Greenfeld Date: Mon, 12 Nov 2012 16:12:27 -0800 Subject: Converted to

per #aaugustin's request --- docs/ref/templates/builtins.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/ref') diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index ef9aa83955..5b76952c49 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1559,7 +1559,7 @@ string. This is useful in the rare cases where you need multiple escaping or want to apply other filters to the escaped results. Normally, you want to use the :tfilter:`escape` filter. -For example, if you want to catch the ```` HTML elements created by +For example, if you want to catch the ``

`` HTML elements created by the :tfilter:`linebreaks` filter:: {% autoescape off %} -- cgit v1.3 From 00ff69a827b38054afe557fc7d0a589b270ed871 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Tue, 13 Nov 2012 20:46:29 +0100 Subject: Fixed #19283 -- Fixed typo in imports in CBV docs. --- docs/ref/class-based-views/generic-editing.txt | 2 +- docs/topics/class-based-views/generic-editing.txt | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt index 2fac06ee02..01f9e32c53 100644 --- a/docs/ref/class-based-views/generic-editing.txt +++ b/docs/ref/class-based-views/generic-editing.txt @@ -16,8 +16,8 @@ editing content: has been defined. For these cases we assume the following has been defined in `myapp/models.py`:: - from django import models from django.core.urlresolvers import reverse + from django.db import models class Author(models.Model): name = models.CharField(max_length=200) diff --git a/docs/topics/class-based-views/generic-editing.txt b/docs/topics/class-based-views/generic-editing.txt index 7bae3c692d..7d12184705 100644 --- a/docs/topics/class-based-views/generic-editing.txt +++ b/docs/topics/class-based-views/generic-editing.txt @@ -90,8 +90,8 @@ class: .. code-block:: python # models.py - from django import models from django.core.urlresolvers import reverse + from django.db import models class Author(models.Model): name = models.CharField(max_length=200) @@ -102,7 +102,7 @@ class: Then we can use :class:`CreateView` and friends to do the actual work. Notice how we're just configuring the generic class-based views here; we don't have to write any logic ourselves:: - + # views.py from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.core.urlresolvers import reverse_lazy @@ -134,7 +134,7 @@ Finally, we hook these new views into the URLconf:: url(r'author/(?P\d+)/$', AuthorUpdate.as_view(), name='author_update'), url(r'author/(?P\d+)/delete/$', AuthorDelete.as_view(), name='author_delete'), ) - + .. note:: These views inherit :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin` @@ -160,8 +160,8 @@ you can use a custom :class:`ModelForm` to do this. First, add the foreign key relation to the model:: # models.py - from django import models from django.contrib.auth import User + from django.db import models class Author(models.Model): name = models.CharField(max_length=200) @@ -177,7 +177,7 @@ Create a custom :class:`ModelForm` in order to exclude the # forms.py from django import forms from myapp.models import Author - + class AuthorForm(forms.ModelForm): class Meta: model = Author @@ -190,7 +190,7 @@ In the view, use the custom :attr:`form_class` and override from django.views.generic.edit import CreateView from myapp.models import Author from myapp.forms import AuthorForm - + class AuthorCreate(CreateView): form_class = AuthorForm model = Author -- cgit v1.3 From d8ee46afff913975404887cdd2eec635a03013f8 Mon Sep 17 00:00:00 2001 From: Brandon Adams Date: Mon, 12 Nov 2012 17:17:05 -0500 Subject: comment_will_be_sent can cause a 400, not a 403 Doc cleanup for django.contrib.comments.signals.comment_will_be_sent If a receiver returns False, an HttpResponse with status code 400 is returned. A test case already exists confirming this behavior. Updated docs to reflect reality. --- django/contrib/comments/signals.py | 2 +- docs/ref/contrib/comments/signals.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'docs/ref') diff --git a/django/contrib/comments/signals.py b/django/contrib/comments/signals.py index fe1083bd14..079afaf03a 100644 --- a/django/contrib/comments/signals.py +++ b/django/contrib/comments/signals.py @@ -6,7 +6,7 @@ from django.dispatch import Signal # Sent just before a comment will be posted (after it's been approved and # moderated; this can be used to modify the comment (in place) with posting # details or other such actions. If any receiver returns False the comment will be -# discarded and a 403 (not allowed) response. This signal is sent at more or less +# discarded and a 400 response. This signal is sent at more or less # the same time (just before, actually) as the Comment object's pre-save signal, # except that the HTTP request is sent along with this signal. comment_will_be_posted = Signal(providing_args=["comment", "request"]) diff --git a/docs/ref/contrib/comments/signals.txt b/docs/ref/contrib/comments/signals.txt index 9d7c435927..8274539ed7 100644 --- a/docs/ref/contrib/comments/signals.txt +++ b/docs/ref/contrib/comments/signals.txt @@ -20,8 +20,8 @@ Sent just before a comment will be saved, after it's been sanity checked and submitted. This can be used to modify the comment (in place) with posting details or other such actions. -If any receiver returns ``False`` the comment will be discarded and a 403 (not -allowed) response will be returned. +If any receiver returns ``False`` the comment will be discarded and a 400 +response will be returned. This signal is sent at more or less the same time (just before, actually) as the ``Comment`` object's :data:`~django.db.models.signals.pre_save` signal. -- cgit v1.3 From ac4aa8a76c5fe49d3a028d74c86b6589ead8d608 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 17 Nov 2012 06:49:28 -0500 Subject: Documented that contrib.sites creates a default site. Thanks Lorin Hochstein for the patch. --- docs/ref/contrib/sites.txt | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index 790e003453..7e5448b3d3 100644 --- a/docs/ref/contrib/sites.txt +++ b/docs/ref/contrib/sites.txt @@ -127,8 +127,10 @@ For example:: def my_view(request): if settings.SITE_ID == 3: # Do something. + pass else: # Do something else. + pass Of course, it's ugly to hard-code the site IDs like that. This sort of hard-coding is best for hackish fixes that you need done quickly. The @@ -141,11 +143,13 @@ domain:: current_site = get_current_site(request) if current_site.domain == 'foo.com': # Do something + pass else: # Do something else. + pass -This has also the advantage of checking if the sites framework is installed, and -return a :class:`RequestSite` instance if it is not. +This has also the advantage of checking if the sites framework is installed, +and return a :class:`RequestSite` instance if it is not. If you don't have access to the request object, you can use the ``get_current()`` method of the :class:`~django.contrib.sites.models.Site` @@ -158,8 +162,10 @@ the :setting:`SITE_ID` setting. This example is equivalent to the previous one:: current_site = Site.objects.get_current() if current_site.domain == 'foo.com': # Do something + pass else: # Do something else. + pass Getting the current domain for display -------------------------------------- @@ -200,8 +206,8 @@ subscribing to LJWorld.com alerts." Same goes for the email's message body. Note that an even more flexible (but more heavyweight) way of doing this would be to use Django's template system. Assuming Lawrence.com and LJWorld.com have -different template directories (:setting:`TEMPLATE_DIRS`), you could simply farm out -to the template system like so:: +different template directories (:setting:`TEMPLATE_DIRS`), you could simply +farm out to the template system like so:: from django.core.mail import send_mail from django.template import loader, Context @@ -216,9 +222,9 @@ to the template system like so:: # ... -In this case, you'd have to create :file:`subject.txt` and :file:`message.txt` template -files for both the LJWorld.com and Lawrence.com template directories. That -gives you more flexibility, but it's also more complex. +In this case, you'd have to create :file:`subject.txt` and :file:`message.txt` +template files for both the LJWorld.com and Lawrence.com template directories. +That gives you more flexibility, but it's also more complex. It's a good idea to exploit the :class:`~django.contrib.sites.models.Site` objects as much as possible, to remove unneeded complexity and redundancy. @@ -240,6 +246,15 @@ To do this, you can use the sites framework. A simple example:: >>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url()) 'http://example.com/mymodel/objects/3/' + +Default site and ``syncdb`` +=========================== + +``django.contrib.sites`` registers a +:data:`~django.db.models.signals.post_syncdb` signal handler which creates a +default site named ``example.com`` with the domain ``example.com``. For +example, this site will be created after Django creates the test database. + Caching the current ``Site`` object =================================== -- cgit v1.3 From 9b755a298a55849bf8e831a46639999609aedfff Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 17 Nov 2012 22:38:19 +0100 Subject: Fixed #19291 -- Completed deprecation of ADMIN_MEDIA_PREFIX. --- django/conf/__init__.py | 3 --- django/contrib/admin/templatetags/adminmedia.py | 15 --------------- docs/internals/deprecation.txt | 9 +++++---- docs/ref/settings.txt | 10 ---------- docs/releases/1.5.txt | 5 +++++ 5 files changed, 10 insertions(+), 32 deletions(-) delete mode 100644 django/contrib/admin/templatetags/adminmedia.py (limited to 'docs/ref') diff --git a/django/conf/__init__.py b/django/conf/__init__.py index dec4cf9418..b00c8d5046 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -110,9 +110,6 @@ class BaseSettings(object): def __setattr__(self, name, value): if name in ("MEDIA_URL", "STATIC_URL") and value and not value.endswith('/'): raise ImproperlyConfigured("If set, %s must end with a slash" % name) - elif name == "ADMIN_MEDIA_PREFIX": - warnings.warn("The ADMIN_MEDIA_PREFIX setting has been removed; " - "use STATIC_URL instead.", DeprecationWarning) elif name == "ALLOWED_INCLUDE_ROOTS" and isinstance(value, six.string_types): raise ValueError("The ALLOWED_INCLUDE_ROOTS setting must be set " "to a tuple, not a string.") diff --git a/django/contrib/admin/templatetags/adminmedia.py b/django/contrib/admin/templatetags/adminmedia.py deleted file mode 100644 index b08d13c18f..0000000000 --- a/django/contrib/admin/templatetags/adminmedia.py +++ /dev/null @@ -1,15 +0,0 @@ -import warnings -from django.template import Library -from django.templatetags.static import PrefixNode - -register = Library() - -@register.simple_tag -def admin_media_prefix(): - """ - Returns the string contained in the setting ADMIN_MEDIA_PREFIX. - """ - warnings.warn( - "The admin_media_prefix template tag is deprecated. " - "Use the static template tag instead.", DeprecationWarning) - return PrefixNode.handle_simple("ADMIN_MEDIA_PREFIX") diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 9fd92db2b4..414da30ff8 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -140,6 +140,11 @@ these changes. removed. In its place use :class:`~django.contrib.staticfiles.handlers.StaticFilesHandler`. +* The template tags library ``adminmedia`` and the template tag ``{% + admin_media_prefix %}`` will be removed in favor of the generic static files + handling. (This is faster than the usual deprecation path; see the + :doc:`Django 1.4 release notes`.) + * The :ttag:`url` and :ttag:`ssi` template tags will be modified so that the first argument to each tag is a template variable, not an implied string. In 1.4, this behavior is provided by a version of the tag @@ -232,10 +237,6 @@ these changes. :setting:`LOGGING` setting should include this filter explicitly if it is desired. -* The template tag - :func:`django.contrib.admin.templatetags.adminmedia.admin_media_prefix` - will be removed in favor of the generic static files handling. - * The builtin truncation functions :func:`django.utils.text.truncate_words` and :func:`django.utils.text.truncate_html_words` will be removed in favor of the ``django.utils.text.Truncator`` class. diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 5544c99dd1..9b222bf586 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -2210,16 +2210,6 @@ The default value for the X-Frame-Options header used by Deprecated settings =================== -.. setting:: ADMIN_MEDIA_PREFIX - -ADMIN_MEDIA_PREFIX ------------------- - -.. deprecated:: 1.4 - This setting has been obsoleted by the ``django.contrib.staticfiles`` app - integration. See the :doc:`Django 1.4 release notes` for - more information. - .. setting:: AUTH_PROFILE_MODULE AUTH_PROFILE_MODULE diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index c53518feaa..cffc0f23af 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -574,6 +574,11 @@ Miscellaneous HTML validation against pre-HTML5 Strict DTDs, you should add a div around it in your pages. +* The template tags library ``adminmedia``, which only contained the + deprecated template tag ``{% admin_media_prefix %}``, was removed. + Attempting to load it with ``{% load adminmedia %}`` will fail. If your + templates still contain that line you must remove it. + Features deprecated in 1.5 ========================== -- cgit v1.3 From ccb2b574e8ea961f11b1c36bcbdf396cd9acb550 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 17 Nov 2012 23:25:52 +0100 Subject: Fixed #19315 -- Improved markup in admin FAQ. Thanks ClaesBas. --- docs/faq/admin.txt | 23 +++++++++++++---------- docs/ref/contrib/admin/index.txt | 2 ++ 2 files changed, 15 insertions(+), 10 deletions(-) (limited to 'docs/ref') diff --git a/docs/faq/admin.txt b/docs/faq/admin.txt index 872ad254c9..30d452cbe2 100644 --- a/docs/faq/admin.txt +++ b/docs/faq/admin.txt @@ -10,8 +10,8 @@ things: * Set the :setting:`SESSION_COOKIE_DOMAIN` setting in your admin config file to match your domain. For example, if you're going to - "http://www.example.com/admin/" in your browser, in - "myproject.settings" you should set ``SESSION_COOKIE_DOMAIN = 'www.example.com'``. + "http://www.example.com/admin/" in your browser, in "myproject.settings" you + should set :setting:`SESSION_COOKIE_DOMAIN` = 'www.example.com'. * Some browsers (Firefox?) don't like to accept cookies from domains that don't have dots in them. If you're running the admin site on "localhost" @@ -23,8 +23,9 @@ I can't log in. When I enter a valid username and password, it brings up the log ----------------------------------------------------------------------------------------------------------------------------------------------------------- If you're sure your username and password are correct, make sure your user -account has ``is_active`` and ``is_staff`` set to True. The admin site only -allows access to users with those two fields both set to True. +account has :attr:`~django.contrib.auth.models.User.is_active` and +:attr:`~django.contrib.auth.models.User.is_staff` set to True. The admin site +only allows access to users with those two fields both set to True. How can I prevent the cache middleware from caching the admin site? ------------------------------------------------------------------- @@ -64,9 +65,10 @@ My "list_filter" contains a ManyToManyField, but the filter doesn't display. Django won't bother displaying the filter for a ``ManyToManyField`` if there are fewer than two related objects. -For example, if your ``list_filter`` includes ``sites``, and there's only one -site in your database, it won't display a "Site" filter. In that case, -filtering by site would be meaningless. +For example, if your :attr:`~django.contrib.admin.ModelAdmin.list_filter` +includes :doc:`sites `, and there's only one site in your +database, it won't display a "Site" filter. In that case, filtering by site +would be meaningless. Some objects aren't appearing in the admin. ------------------------------------------- @@ -85,9 +87,10 @@ How can I customize the functionality of the admin interface? You've got several options. If you want to piggyback on top of an add/change form that Django automatically generates, you can attach arbitrary JavaScript -modules to the page via the model's ``class Admin`` ``js`` parameter. That -parameter is a list of URLs, as strings, pointing to JavaScript modules that -will be included within the admin form via a ``