From a2e927b7ed25bd319f866c55b18cd214a149d859 Mon Sep 17 00:00:00 2001 From: Mike Grouchy Date: Wed, 18 Jul 2012 08:00:31 -0400 Subject: BaseCache now has a no-op close method as per ticket #18582 Also removed the hasattr check when firing request_finished signal for caches with a 'close' method. Should be safe to call `cache.close` everywhere now --- docs/topics/cache.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'docs') diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 03afa86647..85f4c64aa8 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -779,6 +779,16 @@ nonexistent cache key.:: However, if the backend doesn't natively provide an increment/decrement operation, it will be implemented using a two-step retrieve/update. + +You can close the connection to your cache with ``close()`` if implemented by +the cache backend. + + >>> cache.close() + +.. note:: + + For caches that don't implement ``close`` methods it is a no-op. + .. _cache_key_prefixing: Cache key prefixing -- cgit v1.3 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'. --- django/views/generic/list.py | 4 +++- docs/ref/class-based-views/mixins-multiple-object.txt | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/django/views/generic/list.py b/django/views/generic/list.py index ec30c58f29..0c383d609a 100644 --- a/django/views/generic/list.py +++ b/django/views/generic/list.py @@ -17,6 +17,7 @@ class MultipleObjectMixin(ContextMixin): paginate_by = None context_object_name = None paginator_class = Paginator + page_kwarg = 'page' def get_queryset(self): """ @@ -39,7 +40,8 @@ class MultipleObjectMixin(ContextMixin): Paginate the queryset, if needed. """ paginator = self.get_paginator(queryset, page_size, allow_empty_first_page=self.get_allow_empty()) - page = self.kwargs.get('page') or self.request.GET.get('page') or 1 + page_kwarg = self.page_kwarg + page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1 try: page_number = int(page) except ValueError: 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 90c76564669fa03caefcf4318ffdf9ba8fa4d40b Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 26 Oct 2012 10:23:33 +0200 Subject: Fixed #19191 -- Corrected a typo in CustomUser docs Thanks spleeyah for the report. --- docs/topics/auth.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 41159984f6..d261a3c90b 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -1889,7 +1889,7 @@ password resets. You must then provide some key implementation details: as the identifying field:: class MyUser(AbstractBaseUser): - identfier = models.CharField(max_length=40, unique=True, db_index=True) + identifier = models.CharField(max_length=40, unique=True, db_index=True) ... USERNAME_FIELD = 'identifier' -- cgit v1.3 From 373df56d36891b9ab1f88519bf9e8f3c0b3bb108 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Fri, 26 Oct 2012 22:01:34 -0300 Subject: Advanced version identifiers for 1.6 cycle. --- django/__init__.py | 2 +- docs/conf.py | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/django/__init__.py b/django/__init__.py index 32e1374765..873c328add 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 5, 0, 'alpha', 0) +VERSION = (1, 6, 0, 'alpha', 0) def get_version(*args, **kwargs): # Don't litter django/__init__.py with all the get_version stuff. diff --git a/docs/conf.py b/docs/conf.py index 433fd679a1..6dd84cffba 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,11 +52,23 @@ copyright = 'Django Software Foundation and contributors' # built documents. # # The short X.Y version. -version = '1.5' +version = '1.6' # The full version, including alpha/beta/rc tags. -release = '1.5' +try: + from django import VERSION, get_version +except ImportError: + release = version +else: + def django_release(): + pep386ver = get_version() + if VERSION[3:5] == ('alpha', 0) and 'dev' not in pep386ver: + return pep386ver + '.dev' + return pep386ver + + release = django_release() + # The next version to be released -django_next_version = '1.6' +django_next_version = '1.7' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -- 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') 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 b7d81715dc85fd5ec10a207b8b6ebca8399497e0 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Sat, 27 Oct 2012 21:18:48 +0200 Subject: Removed a redundant colon in the query docs. Thanks to Berker Peksag for the patch. --- docs/topics/db/queries.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index d826a39562..321c8a42eb 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -417,7 +417,7 @@ translates (roughly) into the following SQL:: There's one exception though, in case of a :class:`~django.db.models.ForeignKey` you can specify the field name suffixed with ``_id``. In this case, the value parameter is expected - to contain the raw value of the foreign model's primary key. For example:: + to contain the raw value of the foreign model's primary key. For example: >>> Entry.objects.filter(blog_id__exact=4) -- cgit v1.3 From fc2681b22b120a468607c6aeb06163d8e5dbf897 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 27 Oct 2012 21:55:50 +0200 Subject: Fixed #17787 -- Documented reset caches by setting_changed signal --- docs/topics/testing.txt | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) (limited to 'docs') diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index d0b2e7cdf9..7c25a8b3ff 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -1586,15 +1586,23 @@ The decorator can also be applied to test case classes:: the original ``LoginTestCase`` is still equally affected by the decorator. -.. note:: - - When overriding settings, make sure to handle the cases in which your app's - code uses a cache or similar feature that retains state even if the - setting is changed. Django provides the - :data:`django.test.signals.setting_changed` signal that lets you register - callbacks to clean up and otherwise reset state when settings are changed. - Note that this signal isn't currently used by Django itself, so changing - built-in settings may not yield the results you expect. +When overriding settings, make sure to handle the cases in which your app's +code uses a cache or similar feature that retains state even if the +setting is changed. Django provides the +:data:`django.test.signals.setting_changed` signal that lets you register +callbacks to clean up and otherwise reset state when settings are changed. + +Django itself uses this signal to reset various data: + +=========================== ======================== +Overriden settings Data reset +=========================== ======================== +USE_TZ, TIME_ZONE Databases timezone +TEMPLATE_CONTEXT_PROCESSORS Context processors cache +TEMPLATE_LOADERS Template loaders cache +SERIALIZATION_MODULES Serializers cache +LOCALE_PATHS, LANGUAGE_CODE Default translation and loaded translations +=========================== ======================== Emptying the test outbox ~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.3 From cd17a24083f3ef17cf4c40a41c9d03c250d817c6 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 27 Oct 2012 21:32:50 +0200 Subject: Added optional kwargs to get_expiry_age/date. This change allows for cleaner tests: we can test the exact output. Refs #18194: this change makes it possible to compute session expiry dates at times other than when the session is saved. Fixed #18458: the existence of the `modification` kwarg implies that you must pass it to get_expiry_age/date if you call these functions outside of a short request - response cycle (the intended use case). --- django/contrib/sessions/backends/base.py | 40 ++++++++++++++++++----- django/contrib/sessions/backends/cached_db.py | 2 +- django/contrib/sessions/tests.py | 46 +++++++++++++++++---------- docs/topics/http/sessions.txt | 11 +++++++ 4 files changed, 73 insertions(+), 26 deletions(-) (limited to 'docs') diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index 65bbe059eb..1f63c3b45a 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -170,28 +170,52 @@ class SessionBase(object): _session = property(_get_session) - def get_expiry_age(self, expiry=None): + def get_expiry_age(self, **kwargs): """Get the number of seconds until the session expires. - expiry is an optional parameter specifying the datetime of expiry. + Optionally, this function accepts `modification` and `expiry` keyword + arguments specifying the modification and expiry of the session. """ - if expiry is None: + try: + modification = kwargs['modification'] + except KeyError: + modification = timezone.now() + # Make the difference between "expiry=None passed in kwargs" and + # "expiry not passed in kwargs", in order to guarantee not to trigger + # self.load() when expiry is provided. + try: + expiry = kwargs['expiry'] + except KeyError: expiry = self.get('_session_expiry') + if not expiry: # Checks both None and 0 cases return settings.SESSION_COOKIE_AGE if not isinstance(expiry, datetime): return expiry - delta = expiry - timezone.now() + delta = expiry - modification return delta.days * 86400 + delta.seconds - def get_expiry_date(self): - """Get session the expiry date (as a datetime object).""" - expiry = self.get('_session_expiry') + def get_expiry_date(self, **kwargs): + """Get session the expiry date (as a datetime object). + + Optionally, this function accepts `modification` and `expiry` keyword + arguments specifying the modification and expiry of the session. + """ + try: + modification = kwargs['modification'] + except KeyError: + modification = timezone.now() + # Same comment as in get_expiry_age + try: + expiry = kwargs['expiry'] + except KeyError: + expiry = self.get('_session_expiry') + if isinstance(expiry, datetime): return expiry if not expiry: # Checks both None and 0 cases expiry = settings.SESSION_COOKIE_AGE - return timezone.now() + timedelta(seconds=expiry) + return modification + timedelta(seconds=expiry) def set_expiry(self, value): """ diff --git a/django/contrib/sessions/backends/cached_db.py b/django/contrib/sessions/backends/cached_db.py index 8e4cf8b7e7..31c6fbfce3 100644 --- a/django/contrib/sessions/backends/cached_db.py +++ b/django/contrib/sessions/backends/cached_db.py @@ -40,7 +40,7 @@ class SessionStore(DBStore): ) data = self.decode(s.session_data) cache.set(self.cache_key, data, - self.get_expiry_age(s.expire_date)) + self.get_expiry_age(expiry=s.expire_date)) except (Session.DoesNotExist, SuspiciousOperation): self.create() data = {} diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 527e8eb331..44c3b485e9 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -197,31 +197,43 @@ class SessionTestsMixin(object): self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE) def test_custom_expiry_seconds(self): - # Using seconds + modification = timezone.now() + self.session.set_expiry(10) - delta = self.session.get_expiry_date() - timezone.now() - self.assertIn(delta.seconds, (9, 10)) - age = self.session.get_expiry_age() - self.assertIn(age, (9, 10)) + date = self.session.get_expiry_date(modification=modification) + self.assertEqual(date, modification + timedelta(seconds=10)) + + age = self.session.get_expiry_age(modification=modification) + self.assertEqual(age, 10) def test_custom_expiry_timedelta(self): - # Using timedelta - self.session.set_expiry(timedelta(seconds=10)) - delta = self.session.get_expiry_date() - timezone.now() - self.assertIn(delta.seconds, (9, 10)) + modification = timezone.now() + + # Mock timezone.now, because set_expiry calls it on this code path. + original_now = timezone.now + try: + timezone.now = lambda: modification + self.session.set_expiry(timedelta(seconds=10)) + finally: + timezone.now = original_now + + date = self.session.get_expiry_date(modification=modification) + self.assertEqual(date, modification + timedelta(seconds=10)) - age = self.session.get_expiry_age() - self.assertIn(age, (9, 10)) + age = self.session.get_expiry_age(modification=modification) + self.assertEqual(age, 10) def test_custom_expiry_datetime(self): - # Using fixed datetime - self.session.set_expiry(timezone.now() + timedelta(seconds=10)) - delta = self.session.get_expiry_date() - timezone.now() - self.assertIn(delta.seconds, (9, 10)) + modification = timezone.now() + + self.session.set_expiry(modification + timedelta(seconds=10)) + + date = self.session.get_expiry_date(modification=modification) + self.assertEqual(date, modification + timedelta(seconds=10)) - age = self.session.get_expiry_age() - self.assertIn(age, (9, 10)) + age = self.session.get_expiry_age(modification=modification) + self.assertEqual(age, 10) def test_custom_expiry_reset(self): self.session.set_expiry(None) diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 0082b75db1..1e043405f4 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -250,12 +250,23 @@ You can edit it multiple times. with no custom expiration (or those set to expire at browser close), this will equal :setting:`SESSION_COOKIE_AGE`. + This function accepts two optional keyword arguments: + + - ``modification``: last modification of the session, as a + :class:`~datetime.datetime` object. Defaults to the current time. + - ``expiry``: expiry information for the session, as a + :class:`~datetime.datetime` object, an :class:`int` (in seconds), or + ``None``. Defaults to the value stored in the session by + :meth:`set_expiry`, if there is one, or ``None``. + .. method:: get_expiry_date Returns the date this session will expire. For sessions with no custom expiration (or those set to expire at browser close), this will equal the date :setting:`SESSION_COOKIE_AGE` seconds from now. + This function accepts the same keyword argumets as :meth:`get_expiry_age`. + .. method:: get_expire_at_browser_close Returns either ``True`` or ``False``, depending on whether the user's -- 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') 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 0b98ef632147a26f2430a3ede48d9e58983cc3ae Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sun, 28 Oct 2012 18:18:09 -0300 Subject: Ensure that version detection in docs from 373df56d uses the right Django copy. --- docs/conf.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/conf.py b/docs/conf.py index 6dd84cffba..ced3fef5f7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,12 +14,15 @@ from __future__ import unicode_literals import sys -import os +from os.path import abspath, dirname, join + +# Make sure we use this copy of Django +sys.path.insert(1, abspath(dirname(dirname(__file__)))) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "_ext"))) +sys.path.append(abspath(join(dirname(__file__), "_ext"))) # -- General configuration ----------------------------------------------------- -- cgit v1.3 From 4ea8105120121c7ef0d3dd6eb23f2bf5f55b496a Mon Sep 17 00:00:00 2001 From: Preston Holmes Date: Thu, 4 Oct 2012 10:14:50 -0700 Subject: Fixed #19061 -- added is_active attribute to AbstractBaseUser --- django/contrib/auth/forms.py | 6 ++++-- django/contrib/auth/models.py | 2 ++ django/contrib/auth/tests/custom_user.py | 17 +++++++++++++++++ django/contrib/auth/tests/models.py | 32 ++++++++++++++++++++++++++++++++ docs/topics/auth.txt | 9 +++++++++ 5 files changed, 64 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 423e3429e6..9279c52675 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -209,10 +209,12 @@ class PasswordResetForm(forms.Form): """ UserModel = get_user_model() email = self.cleaned_data["email"] - self.users_cache = UserModel.objects.filter(email__iexact=email, - is_active=True) + self.users_cache = UserModel.objects.filter(email__iexact=email) if not len(self.users_cache): raise forms.ValidationError(self.error_messages['unknown']) + if not any(user.is_active for user in self.users_cache): + # none of the filtered users are active + raise forms.ValidationError(self.error_messages['unknown']) if any((user.password == UNUSABLE_PASSWORD) for user in self.users_cache): raise forms.ValidationError(self.error_messages['unusable']) diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index bd7bf4a162..de4de7cdfd 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -232,6 +232,8 @@ class AbstractBaseUser(models.Model): password = models.CharField(_('password'), max_length=128) last_login = models.DateTimeField(_('last login'), default=timezone.now) + is_active = True + REQUIRED_FIELDS = [] class Meta: diff --git a/django/contrib/auth/tests/custom_user.py b/django/contrib/auth/tests/custom_user.py index a29ed6a104..710ac8bdd7 100644 --- a/django/contrib/auth/tests/custom_user.py +++ b/django/contrib/auth/tests/custom_user.py @@ -88,3 +88,20 @@ class ExtensionUser(AbstractUser): class Meta: app_label = 'auth' + + +class IsActiveTestUser1(AbstractBaseUser): + """ + This test user class and derivatives test the default is_active behavior + """ + username = models.CharField(max_length=30, unique=True) + + objects = BaseUserManager() + + USERNAME_FIELD = 'username' + + class Meta: + app_label = 'auth' + + # the is_active attr is provided by AbstractBaseUser + diff --git a/django/contrib/auth/tests/models.py b/django/contrib/auth/tests/models.py index 252a0887c8..cb7d8888fe 100644 --- a/django/contrib/auth/tests/models.py +++ b/django/contrib/auth/tests/models.py @@ -1,4 +1,5 @@ from django.conf import settings +from django.contrib.auth import get_user_model from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable, UserManager) from django.contrib.auth.tests.utils import skipIfCustomUser @@ -98,3 +99,34 @@ class UserManagerTestCase(TestCase): self.assertRaisesMessage(ValueError, 'The given username must be set', User.objects.create_user, username='') + +class IsActiveTestCase(TestCase): + """ + Tests the behavior of the guaranteed is_active attribute + """ + + def test_builtin_user_isactive(self): + user = User.objects.create(username='foo', email='foo@bar.com') + # is_active is true by default + self.assertEqual(user.is_active, True) + user.is_active = False + user.save() + user_fetched = User.objects.get(pk=user.pk) + # the is_active flag is saved + self.assertFalse(user_fetched.is_active) + + @override_settings(AUTH_USER_MODEL='auth.IsActiveTestUser1') + def test_is_active_field_default(self): + """ + tests that the default value for is_active is provided + """ + UserModel = get_user_model() + user = UserModel(username='foo') + self.assertEqual(user.is_active, True) + # you can set the attribute - but it will not save + user.is_active = False + # there should be no problem saving - but the attribute is not saved + user.save() + user_fetched = UserModel.objects.get(pk=user.pk) + # the attribute is always true for newly retrieved instance + self.assertEqual(user_fetched.is_active, True) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index d261a3c90b..f7a9084008 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -1911,6 +1911,15 @@ password resets. You must then provide some key implementation details: ``REQUIRED_FIELDS`` must contain all required fields on your User model, but should *not* contain the ``USERNAME_FIELD``. + .. attribute:: User.is_active + + A boolean attribute that indicates whether the user is considered + "active". This attribute is provided as an attribute on + ``AbstractBaseUser`` defaulting to ``True``. How you choose to + implement it will depend on the details of your chosen auth backends. + See the documentation of the :attr:`attribute on the builtin user model + ` for details. + .. method:: User.get_full_name(): A longer formal identifier for the user. A common interpretation -- cgit v1.3 From bc00075d51cd3e3b5f9a4d7d0f138e0a819adcb9 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Mon, 29 Oct 2012 21:39:12 +0100 Subject: Fixed #19208 -- Docs for mod_wsgi daemon mode Thanks Graham Dumpleton for the patch. --- docs/howto/deployment/wsgi/modwsgi.txt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/howto/deployment/wsgi/modwsgi.txt b/docs/howto/deployment/wsgi/modwsgi.txt index 7f68485dff..ead04a4643 100644 --- a/docs/howto/deployment/wsgi/modwsgi.txt +++ b/docs/howto/deployment/wsgi/modwsgi.txt @@ -88,12 +88,20 @@ Using mod_wsgi daemon mode ========================== "Daemon mode" is the recommended mode for running mod_wsgi (on non-Windows -platforms). See the `official mod_wsgi documentation`_ for details on setting -up daemon mode. The only change required to the above configuration if you use -daemon mode is that you can't use ``WSGIPythonPath``; instead you should use -the ``python-path`` option to ``WSGIDaemonProcess``, for example:: +platforms). To create the required daemon process group and delegate the +Django instance to run in it, you will need to add appropriate +``WSGIDaemonProcess`` and ``WSGIProcessGroup`` directives. A further change +required to the above configuration if you use daemon mode is that you can't +use ``WSGIPythonPath``; instead you should use the ``python-path`` option to +``WSGIDaemonProcess``, for example:: WSGIDaemonProcess example.com python-path=/path/to/mysite.com:/path/to/venv/lib/python2.7/site-packages + WSGIProcessGroup example.com + +See the official mod_wsgi documentation for `details on setting up daemon +mode`_. + +.. _details on setting up daemon mode: http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide#Delegation_To_Daemon_Process .. _serving-files: -- 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') 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 9741912a9aaad083eaa8b9780cde37e1843cc4ae Mon Sep 17 00:00:00 2001 From: Preston Holmes Date: Sun, 9 Sep 2012 16:25:06 -0400 Subject: Fixed #17869 - force logout when REMOTE_USER header disappears If the current sessions user was logged in via a remote user backend log out the user if REMOTE_USER header not available - otherwise leave it to other auth middleware to install the AnonymousUser. Thanks to Sylvain Bouchard for the initial patch and ticket maintenance. --- django/contrib/auth/middleware.py | 17 ++++++++++++++--- django/contrib/auth/tests/remote_user.py | 25 +++++++++++++++++++++++-- docs/releases/1.5.txt | 3 +++ 3 files changed, 40 insertions(+), 5 deletions(-) (limited to 'docs') diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py index 0398cfaf1e..f38efdd1d2 100644 --- a/django/contrib/auth/middleware.py +++ b/django/contrib/auth/middleware.py @@ -1,4 +1,6 @@ from django.contrib import auth +from django.contrib.auth import load_backend +from django.contrib.auth.backends import RemoteUserBackend from django.core.exceptions import ImproperlyConfigured from django.utils.functional import SimpleLazyObject @@ -47,9 +49,18 @@ class RemoteUserMiddleware(object): try: username = request.META[self.header] except KeyError: - # If specified header doesn't exist then return (leaving - # request.user set to AnonymousUser by the - # AuthenticationMiddleware). + # If specified header doesn't exist then remove any existing + # authenticated remote-user, or return (leaving request.user set to + # AnonymousUser by the AuthenticationMiddleware). + if request.user.is_authenticated(): + try: + stored_backend = load_backend(request.session.get( + auth.BACKEND_SESSION_KEY, '')) + if isinstance(stored_backend, RemoteUserBackend): + auth.logout(request) + except ImproperlyConfigured as e: + # backend failed to load + auth.logout(request) return # If the user is already authenticated and that user is the user we are # getting passed in the headers, then the correct user is already diff --git a/django/contrib/auth/tests/remote_user.py b/django/contrib/auth/tests/remote_user.py index 9b0f6f8be3..0e59b291a8 100644 --- a/django/contrib/auth/tests/remote_user.py +++ b/django/contrib/auth/tests/remote_user.py @@ -1,8 +1,9 @@ from datetime import datetime from django.conf import settings +from django.contrib.auth import authenticate from django.contrib.auth.backends import RemoteUserBackend -from django.contrib.auth.models import User +from django.contrib.auth.models import User, AnonymousUser from django.contrib.auth.tests.utils import skipIfCustomUser from django.test import TestCase from django.utils import timezone @@ -23,7 +24,7 @@ class RemoteUserTest(TestCase): self.curr_middleware = settings.MIDDLEWARE_CLASSES self.curr_auth = settings.AUTHENTICATION_BACKENDS settings.MIDDLEWARE_CLASSES += (self.middleware,) - settings.AUTHENTICATION_BACKENDS = (self.backend,) + settings.AUTHENTICATION_BACKENDS += (self.backend,) def test_no_remote_user(self): """ @@ -97,6 +98,26 @@ class RemoteUserTest(TestCase): response = self.client.get('/remote_user/', REMOTE_USER=self.known_user) self.assertEqual(default_login, response.context['user'].last_login) + def test_header_disappears(self): + """ + Tests that a logged in user is logged out automatically when + the REMOTE_USER header disappears during the same browser session. + """ + User.objects.create(username='knownuser') + # Known user authenticates + response = self.client.get('/remote_user/', REMOTE_USER=self.known_user) + self.assertEqual(response.context['user'].username, 'knownuser') + # During the session, the REMOTE_USER header disappears. Should trigger logout. + response = self.client.get('/remote_user/') + self.assertEqual(response.context['user'].is_anonymous(), True) + # verify the remoteuser middleware will not remove a user + # authenticated via another backend + User.objects.create_user(username='modeluser', password='foo') + self.client.login(username='modeluser', password='foo') + authenticate(username='modeluser', password='foo') + response = self.client.get('/remote_user/') + self.assertEqual(response.context['user'].username, 'modeluser') + def tearDown(self): """Restores settings to avoid breaking other tests.""" settings.MIDDLEWARE_CLASSES = self.curr_middleware diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index ebf88e83b9..3ee1b2d21f 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -296,6 +296,9 @@ Django 1.5 also includes several smaller improvements worth noting: you to test equality for XML content at a semantic level, without caring for syntax differences (spaces, attribute order, etc.). +* RemoteUserMiddleware now forces logout when the REMOTE_USER header + disappears during the same browser session. + Backwards incompatible changes in 1.5 ===================================== -- cgit v1.3 From 43d7cee86e05dc267d66c3a308746fc1ac58271e Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Tue, 30 Oct 2012 14:02:54 +0100 Subject: Added release notes for 1.6. Since 1.5 is feature-frozen, we need them to document new features. --- docs/releases/1.6.txt | 32 ++++++++++++++++++++++++++++++++ docs/releases/index.txt | 7 +++++++ 2 files changed, 39 insertions(+) create mode 100644 docs/releases/1.6.txt (limited to 'docs') diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt new file mode 100644 index 0000000000..ef162a8de3 --- /dev/null +++ b/docs/releases/1.6.txt @@ -0,0 +1,32 @@ +============================================ +Django 1.6 release notes - UNDER DEVELOPMENT +============================================ + +Welcome to Django 1.6! + +These release notes cover the `new features`_, as well as some `backwards +incompatible changes`_ you'll want to be aware of when upgrading from Django +1.5 or older versions. We've also dropped some features, which are detailed in +:doc:`our deprecation plan `, and we've `begun the +deprecation process for some features`_. + +.. _`new features`: `What's new in Django 1.6`_ +.. _`backwards incompatible changes`: `Backwards incompatible changes in 1.6`_ +.. _`begun the deprecation process for some features`: `Features deprecated in 1.6`_ + +What's new in Django 1.6 +======================== + +Backwards incompatible changes in 1.6 +===================================== + +.. warning:: + + In addition to the changes outlined in this section, be sure to review the + :doc:`deprecation plan ` for any features that + have been removed. If you haven't updated your code within the + deprecation timeline for a given feature, its removal may appear as a + backwards incompatible change. + +Features deprecated in 1.6 +========================== diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 6df9821f56..e71376447d 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -16,6 +16,13 @@ Final releases .. _development_release_notes: +1.6 release +----------- +.. toctree:: + :maxdepth: 1 + + 1.6 + 1.5 release ----------- .. toctree:: -- cgit v1.3 From 9a02851340c5c30b8e2c174783cd86d5cab7ab81 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 27 Oct 2012 22:38:15 +0200 Subject: Fixed #17744 -- Reset default file storage with setting_changed signal --- django/test/signals.py | 7 +++++++ docs/topics/testing.txt | 19 ++++++++++--------- tests/regressiontests/staticfiles_tests/tests.py | 9 ++------- 3 files changed, 19 insertions(+), 16 deletions(-) (limited to 'docs') diff --git a/django/test/signals.py b/django/test/signals.py index d140304f1d..a96bdff3b3 100644 --- a/django/test/signals.py +++ b/django/test/signals.py @@ -5,6 +5,7 @@ from django.conf import settings from django.db import connections from django.dispatch import receiver, Signal from django.utils import timezone +from django.utils.functional import empty template_rendered = Signal(providing_args=["template", "context"]) @@ -72,3 +73,9 @@ def language_changed(**kwargs): trans_real._default = None if kwargs['setting'] == 'LOCALE_PATHS': trans_real._translations = {} + +@receiver(setting_changed) +def file_storage_changed(**kwargs): + if kwargs['setting'] in ('MEDIA_ROOT', 'DEFAULT_FILE_STORAGE'): + from django.core.files.storage import default_storage + default_storage._wrapped = empty diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 7c25a8b3ff..f5fd4fe3e6 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -1594,15 +1594,16 @@ callbacks to clean up and otherwise reset state when settings are changed. Django itself uses this signal to reset various data: -=========================== ======================== -Overriden settings Data reset -=========================== ======================== -USE_TZ, TIME_ZONE Databases timezone -TEMPLATE_CONTEXT_PROCESSORS Context processors cache -TEMPLATE_LOADERS Template loaders cache -SERIALIZATION_MODULES Serializers cache -LOCALE_PATHS, LANGUAGE_CODE Default translation and loaded translations -=========================== ======================== +================================ ======================== +Overriden settings Data reset +================================ ======================== +USE_TZ, TIME_ZONE Databases timezone +TEMPLATE_CONTEXT_PROCESSORS Context processors cache +TEMPLATE_LOADERS Template loaders cache +SERIALIZATION_MODULES Serializers cache +LOCALE_PATHS, LANGUAGE_CODE Default translation and loaded translations +MEDIA_ROOT, DEFAULT_FILE_STORAGE Default file storage +================================ ======================== Emptying the test outbox ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 7ecbccc448..69e30613a8 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -12,7 +12,6 @@ from django.template import loader, Context from django.conf import settings from django.core.cache.backends.base import BaseCache from django.core.exceptions import ImproperlyConfigured -from django.core.files.storage import default_storage from django.core.management import call_command from django.test import TestCase from django.test.utils import override_settings @@ -48,10 +47,9 @@ class BaseStaticFilesTestCase(object): Test case with a couple utility assertions. """ def setUp(self): - # Clear the cached default_storage out, this is because when it first - # gets accessed (by some other test), it evaluates settings.MEDIA_ROOT, + # Clear the cached staticfiles_storage out, this is because when it first + # gets accessed (by some other test), it evaluates settings.STATIC_ROOT, # since we're planning on changing that we need to clear out the cache. - default_storage._wrapped = empty storage.staticfiles_storage._wrapped = empty # Clear the cached staticfile finders, so they are reinitialized every # run and pick up changes in settings.STATICFILES_DIRS. @@ -709,9 +707,6 @@ class TestMiscFinder(TestCase): """ A few misc finder tests. """ - def setUp(self): - default_storage._wrapped = empty - def test_get_finder(self): self.assertIsInstance(finders.get_finder( 'django.contrib.staticfiles.finders.FileSystemFinder'), -- cgit v1.3 From 08cf54990ae112083b159aa4e263c1f64f396f39 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 30 Oct 2012 15:53:56 -0400 Subject: Fixed #16671 - Added a tutorial on reuseable apps Thank-you Katie Miller and Ben Sturmfels for the initial draft, as well as Russ and Carl for the reviews. --- AUTHORS | 2 + docs/index.txt | 3 + docs/intro/index.txt | 13 +- docs/intro/reusable-apps.txt | 363 +++++++++++++++++++++++++++++++++++++++++++ docs/intro/tutorial03.txt | 6 + docs/intro/tutorial04.txt | 9 +- 6 files changed, 388 insertions(+), 8 deletions(-) create mode 100644 docs/intro/reusable-apps.txt (limited to 'docs') diff --git a/AUTHORS b/AUTHORS index 5799b941ff..6f9b410cca 100644 --- a/AUTHORS +++ b/AUTHORS @@ -380,6 +380,7 @@ answer newbie questions, and generally made Django that much better: Christian Metts michal@plovarna.cz Slawek Mikula + Katie Miller Shawn Milochik mitakummaa@gmail.com Taylor Mitchell @@ -510,6 +511,7 @@ answer newbie questions, and generally made Django that much better: Johan C. Stöver Nowell Strite Thomas Stromberg + Ben Sturmfels Travis Swicegood Pascal Varet SuperJared diff --git a/docs/index.txt b/docs/index.txt index 5055edf7e7..a6d9ed2b13 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -46,6 +46,9 @@ Are you new to Django or to programming? This is the place to start! :doc:`Part 3 ` | :doc:`Part 4 ` +* **Advanced Tutorials:** + :doc:`How to write reusable apps ` + The model layer =============== diff --git a/docs/intro/index.txt b/docs/intro/index.txt index 19290a53c6..afb1825b87 100644 --- a/docs/intro/index.txt +++ b/docs/intro/index.txt @@ -6,31 +6,32 @@ place: read this material to quickly get up and running. .. toctree:: :maxdepth: 1 - + overview install tutorial01 tutorial02 tutorial03 tutorial04 + reusable-apps whatsnext - + .. seealso:: If you're new to Python_, you might want to start by getting an idea of what the language is like. Django is 100% Python, so if you've got minimal comfort with Python you'll probably get a lot more out of Django. - + If you're new to programming entirely, you might want to start with this `list of Python resources for non-programmers`_ - + If you already know a few other languages and want to get up to speed with Python quickly, we recommend `Dive Into Python`_ (also available in a `dead-tree version`_). If that's not quite your style, there are quite a few other `books about Python`_. - + .. _python: http://python.org/ .. _list of Python resources for non-programmers: http://wiki.python.org/moin/BeginnersGuide/NonProgrammers .. _dive into python: http://diveintopython.net/ .. _dead-tree version: http://www.amazon.com/exec/obidos/ASIN/1590593561/ref=nosim/jacobian20 - .. _books about Python: http://wiki.python.org/moin/PythonBooks \ No newline at end of file + .. _books about Python: http://wiki.python.org/moin/PythonBooks diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt new file mode 100644 index 0000000000..200051593c --- /dev/null +++ b/docs/intro/reusable-apps.txt @@ -0,0 +1,363 @@ +============================================= +Advanced tutorial: How to write reusable apps +============================================= + +This advanced tutorial begins where :doc:`Tutorial 4 ` left +off. We'll be turning our Web-poll into a standalone Python package you can +reuse in new projects and share with other people. + +If you haven't recently completed Tutorials 1–4, we encourage you to review +these so that your example project matches the one described below. + +Reusability matters +=================== + +It's a lot of work to design, build, test and maintain a web application. Many +Python and Django projects share common problems. Wouldn't it be great if we +could save some of this repeated work? + +Reusability is the way of life in Python. `The Python Package Index (PyPI) +`_ has a vast +range of packages you can use in your own Python programs. Check out `Django +Packages `_ for existing reusable apps you could +incorporate in your project. Django itself is also just a Python package. This +means that you can take existing Python packages or Django apps and compose +them into your own web project. You only need to write the parts that make +your project unique. + +Let's say you were starting a new project that needed a polls app like the one +we've been working on. How do you make this app reusable? Luckily, you're well +on the way already. In :doc:`Tutorial 3 `, we saw how we +could decouple polls from the project-level URLconf using an ``include``. +In this tutorial, we'll take further steps to make the app easy to use in new +projects and ready to publish for others to install and use. + +.. admonition:: Package? App? + + A Python `package `_ + provides a way of grouping related Python code for easy reuse. A package + contains one or more files of Python code (also known as "modules"). + + A package can be imported with ``import foo.bar`` or ``from foo import + bar``. For a directory (like ``polls``) to form a package, it must contain + a special file ``__init__.py``, even if this file is empty. + + A Django *app* is just a Python package that is specifically intended for + use in a Django project. An app may also use common Django conventions, + such as having a ``models.py`` file. + + Later on we use the term *packaging* to describe the process of making a + Python package easy for others to install. It can be a little confusing, we + know. + +Completing your reusable app +============================ + +After the previous tutorials, our project should look like this:: + + mysite/ + manage.py + mysite/ + __init__.py + settings.py + urls.py + wsgi.py + polls/ + admin.py + __init__.py + models.py + tests.py + urls.py + views.py + +You also have a directory somewhere called ``mytemplates`` which you created in +:doc:`Tutorial 2 `. You specified its location in the +TEMPLATE_DIRS setting. This directory should look like this:: + + mytemplates/ + admin/ + base_site.html + polls/ + detail.html + index.html + results.html + +The polls app is already a Python package, thanks to the ``polls/__init__.py`` +file. That's a great start, but we can't just pick up this package and drop it +into a new project. The polls templates are currently stored in the +project-wide ``mytemplates`` directory. To make the app self-contained, it +should also contain the necessary templates. + +Inside the ``polls`` app, create a new ``templates`` directory. Now move the +``polls`` template directory from ``mytemplates`` into the new +``templates``. Your project should now look like this:: + + mysite/ + manage.py + mysite/ + __init__.py + settings.py + urls.py + wsgi.py + polls/ + admin.py + __init__.py + models.py + templates/ + polls/ + detail.html + index.html + results.html + tests.py + urls.py + views.py + +Your project-wide templates directory should now look like this:: + + mytemplates/ + admin/ + base_site.html + +Looking good! Now would be a good time to confirm that your polls application +still works correctly. How does Django know how to find the new location of +the polls templates even though we didn't modify :setting:`TEMPLATE_DIRS`? +Django has a :setting:`TEMPLATE_LOADERS` setting which contains a list +of callables that know how to import templates from various sources. One of +the defaults is :class:`django.template.loaders.app_directories.Loader` which +looks for a "templates" subdirectory in each of the :setting:`INSTALLED_APPS`. + +The ``polls`` directory could now be copied into a new Django project and +immediately reused. It's not quite ready to be published though. For that, we +need to package the app to make it easy for others to install. + +.. admonition:: Why nested? + + Why create a ``polls`` directory under ``templates`` when we're + already inside the polls app? This directory is needed to avoid conflicts in + Django's ``app_directories`` template loader. For example, if two + apps had a template called ``base.html``, without the extra directory it + wouldn't be possible to distinguish between the two. It's a good convention + to use the name of your app for this directory. + +.. _installing-reusable-apps-prerequisites: + +Installing some prerequisites +============================= + +The current state of Python packaging is a bit muddled with various tools. For +this tutorial, we're going to use distribute_ to build our package. It's a +community-maintained fork of the older ``setuptools`` project. We'll also be +using `pip`_ to uninstall it after we're finished. You should install these +two packages now. If you need help, you can refer to :ref:`how to install +Django with pip`. You can install ``distribute`` +the same way. + +.. _distribute: http://pypi.python.org/pypi/distribute +.. _pip: http://pypi.python.org/pypi/pip + +Packaging your app +================== + +Python *packaging* refers to preparing your app in a specific format that can +be easily installed and used. Django itself is packaged very much like +this. For a small app like polls, this process isn't too difficult. + +1. First, create a parent directory for ``polls``, outside of your Django + project. Call this directory ``django-polls``. + +.. admonition:: Choosing a name for your app + + When choosing a name for your package, check resources like PyPI to avoid + naming conflicts with existing packages. It's often useful to prepend + ``django-`` to your module name when creating a package to distribute. + This helps others looking for Django apps identify your app as Django + specific. + +2. Move the ``polls`` directory into the ``django-polls`` directory. + +3. Create a file ``django-polls/README.txt`` with the following contents:: + + ===== + Polls + ===== + + Polls is a simple Django app to conduct Web-based polls. For each + question, visitors can choose between a fixed number of answers. + + Detailed documentation is in the "docs" directory. + + Quick start + ----------- + + 1. Add "polls" to your INSTALLED_APPS setting like this:: + + INSTALLED_APPS = ( + ... + 'polls', + ) + + 2. Include the polls URLconf in your project urls.py like this:: + + url(r'^polls/', include('polls.urls')), + + 3. Run `python manage.py syncdb` to create the polls models. + + 4. Start the development server and visit http://127.0.0.1:8000/admin/ + to create a poll (you'll need the Admin app enabled). + + 5. Visit http://127.0.0.1:8000/polls/ to participate in the poll. + +4. Create a ``django-polls/LICENSE`` file. Choosing a license is beyond the +scope of this tutorial, but suffice it to say that code released publicly +without a license is *useless*. Django and many Django-compatible apps are +distributed under the BSD license; however, you're free to pick your own +license. Just be aware that your licensing choice will affect who is able +to use your code. + +5. Next we'll create a ``setup.py`` file which provides details about how to +build and install the app. A full explanation of this file is beyond the +scope of this tutorial, but the `distribute docs +`_ have a good explanation. +Create a file ``django-polls/setup.py`` with the following contents:: + + import os + from setuptools import setup + + README = open(os.path.join(os.path.dirname(__file__), 'README.txt')).read() + + # allow setup.py to be run from any path + os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) + + setup( + name = 'django-polls', + version = '0.1', + packages = ['polls'], + include_package_data = True, + license = 'BSD License', # example license + description = 'A simple Django app to conduct Web-based polls.', + long_description = README, + url = 'http://www.example.com/', + author = 'Your Name', + author_email = 'yourname@example.com', + classifiers = [ + 'Environment :: Web Environment', + 'Framework :: Django', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', # example license + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + ], + ) + +.. admonition:: I thought you said we were going to use ``distribute``? + + Distribute is a drop-in replacement for ``setuptools``. Even though we + appear to import from ``setuptools``, since we have ``distribute`` + installed, it will override the import. + +6. Only Python modules and packages are included in the package by default. To + include additional files, we'll need to create a ``MANIFEST.in`` file. The + distribute docs referred to in the previous step discuss this file in more + details. To include the templates and our LICENSE file, create a file + ``django-polls/MANIFEST.in`` with the following contents:: + + include LICENSE + recursive-include polls/templates * + +7. It's optional, but recommended, to include detailed documentation with your + app. Create an empty directory ``django-polls/docs`` for future + documentation. Add an additional line to ``django-polls/MANIFEST.in``:: + + recursive-include docs * + + Note that the ``docs`` directory won't be included in your package unless + you add some files to it. Many Django apps also provide their documentation + online through sites like `readthedocs.org `_. + +8. Try building your package with ``python setup.py sdist`` (run from inside + ``django-polls``). This creates a directory called ``dist`` and builds your + new package, ``django-polls-0.1.tar.gz``. + +For more information on packaging, see `The Hitchhiker's Guide to Packaging +`_. + +Using your own package +====================== + +Since we moved the ``polls`` directory out of the project, it's no longer +working. We'll now fix this by installing our new ``django-polls`` package. + +.. admonition:: Installing as a system library + + The following steps install ``django-polls`` as a system library. In + general, it's best to avoid messing with your system libraries to avoid + breaking things. For this simple example though, the risk is low and it will + help with understanding packaging. We'll explain how to uninstall in + step 4. + + For experienced users, a neater way to manage your packages is to use + "virtualenv" (see below). + +1. Inside ``django-polls/dist``, untar the new package + ``django-polls-0.1.tar.gz`` (e.g. ``tar xzvf django-polls-0.1.tar.gz``). If + you're using Windows, you can download the command-line tool bsdtar_ to do + this, or you can use a GUI-based tool such as 7-zip_. + +2. Change into the directory created in step 1 (e.g. ``cd django-polls-0.1``). + +3. If you're using GNU/Linux, Mac OS X or some other flavor of Unix, enter the + command ``sudo python setup.py install`` at the shell prompt. If you're + using Windows, start up a command shell with administrator privileges and + run the command ``setup.py install``. + + With luck, your Django project should now work correctly again. Run the + server again to confirm this. + +4. To uninstall the package, use pip (you already :ref:`installed it + `, right?):: + + sudo pip uninstall django-polls + +.. _bsdtar: http://gnuwin32.sourceforge.net/packages/bsdtar.htm +.. _7-zip: http://www.7-zip.org/ +.. _pip: http://pypi.python.org/pypi/pip + +Publishing your app +=================== + +Now that we've packaged and tested ``django-polls``, it's ready to share with +the world! If this wasn't just an example, you could now: + +* Email the package to a friend. + +* Upload the package on your Web site. + +* Post the package on a public repository, such as `The Python Package Index + (PyPI) `_. + +For more information on PyPI, see the `Quickstart +`_ +section of The Hitchhiker's Guide to Packaging. One detail this guide mentions +is choosing the license under which your code is distributed. + +Installing Python packages with virtualenv +========================================== + +Earlier, we installed the polls app as a system library. This has some +disadvantages: + +* Modifying the system libraries can affect other Python software on your + system. + +* You won't be able to run multiple versions of this package (or others with + the same name). + +Typically, these situations only arise once you're maintaining several Django +projects. When they do, the best solution is to use `virtualenv +`_. This tool allows you to maintain multiple +isolated Python environments, each with its own copy of the libraries and +package namespace. diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 169e6cd59f..5adfc9a490 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -315,6 +315,12 @@ Load the page in your Web browser, and you should see a bulleted-list containing the "What's up" poll from Tutorial 1. The link points to the poll's detail page. +.. admonition:: Organizing Templates + + Rather than one big templates directory, you can also store templates + within each app. We'll discuss this in more detail in the :doc:`reusable + apps tutorial`. + A shortcut: :func:`~django.shortcuts.render` -------------------------------------------- diff --git a/docs/intro/tutorial04.txt b/docs/intro/tutorial04.txt index 8909caf98b..dfee827056 100644 --- a/docs/intro/tutorial04.txt +++ b/docs/intro/tutorial04.txt @@ -278,5 +278,10 @@ For full details on generic views, see the :doc:`generic views documentation What's next? ============ -The tutorial ends here for the time being. In the meantime, you might want to -check out some pointers on :doc:`where to go from here `. +The beginner tutorial ends here for the time being. In the meantime, you might +want to check out some pointers on :doc:`where to go from here +`. + +If you are familiar with Python packaging and interested in learning how to +turn polls into a "reusable app", check out :doc:`Advanced tutorial: How to +write reusable apps`. -- cgit v1.3 From d55c54a5da241e294c1162a19a07ab3f59e58dc7 Mon Sep 17 00:00:00 2001 From: Brent O'Connor Date: Tue, 30 Oct 2012 16:19:31 -0700 Subject: The timeout variable wasn't defined, which was a little confusing. --- docs/topics/testing.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'docs') diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index f5fd4fe3e6..8fccf32946 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -2084,6 +2084,7 @@ out the `full reference`_ for more details. def test_login(self): from selenium.webdriver.support.wait import WebDriverWait + timeout = 2 ... self.selenium.find_element_by_xpath('//input[@value="Log in"]').click() # Wait until the response is received -- 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') 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 c4b71beb658981011a455753bbb58024bfbeb713 Mon Sep 17 00:00:00 2001 From: Brett Koonce Date: Wed, 31 Oct 2012 15:40:08 -0500 Subject: minor fix (+'.' to end of line) --- docs/faq/general.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/faq/general.txt b/docs/faq/general.txt index 659a8c5ad9..dc569840d1 100644 --- a/docs/faq/general.txt +++ b/docs/faq/general.txt @@ -68,7 +68,7 @@ Who's behind this? Django was originally developed at World Online, the Web department of a newspaper in Lawrence, Kansas, USA. Django's now run by an international team of volunteers; you can read all about them over at the :doc:`list of committers -` +`. Which sites use Django? ----------------------- -- cgit v1.3 From dd0d2c0be56a28f868d501b06975984c138f4830 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 31 Oct 2012 19:56:53 -0400 Subject: Fixed #19216 - Switched to user level installation in apps tutorial. Thanks Nick Coghlan for the suggestion. --- docs/intro/reusable-apps.txt | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) (limited to 'docs') diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index 200051593c..11a441d277 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -291,16 +291,19 @@ Using your own package Since we moved the ``polls`` directory out of the project, it's no longer working. We'll now fix this by installing our new ``django-polls`` package. -.. admonition:: Installing as a system library +.. admonition:: Installing as a user library - The following steps install ``django-polls`` as a system library. In - general, it's best to avoid messing with your system libraries to avoid - breaking things. For this simple example though, the risk is low and it will - help with understanding packaging. We'll explain how to uninstall in - step 4. + The following steps install ``django-polls`` as a user library. Per-user + installs have a lot of advantages over installing the package system-wide, + such as being usable on systems where you don't have administrator access + as well as preventing the package from affecting system services and other + users of the machine. Python 2.6 added support for user libraries, so if + you are using an older version this won't work, but Django 1.5 requires + Python 2.6 or newer anyway. - For experienced users, a neater way to manage your packages is to use - "virtualenv" (see below). + Note that per-user installations can still affect the behavior of system + tools that run as that user, so ``virtualenv`` is a more robust solution + (see below). 1. Inside ``django-polls/dist``, untar the new package ``django-polls-0.1.tar.gz`` (e.g. ``tar xzvf django-polls-0.1.tar.gz``). If @@ -310,9 +313,9 @@ working. We'll now fix this by installing our new ``django-polls`` package. 2. Change into the directory created in step 1 (e.g. ``cd django-polls-0.1``). 3. If you're using GNU/Linux, Mac OS X or some other flavor of Unix, enter the - command ``sudo python setup.py install`` at the shell prompt. If you're - using Windows, start up a command shell with administrator privileges and - run the command ``setup.py install``. + command ``python setup.py install --user`` at the shell prompt. If you're + using Windows, start up a command shell and run the command + ``setup.py install --user``. With luck, your Django project should now work correctly again. Run the server again to confirm this. @@ -320,7 +323,7 @@ working. We'll now fix this by installing our new ``django-polls`` package. 4. To uninstall the package, use pip (you already :ref:`installed it `, right?):: - sudo pip uninstall django-polls + pip uninstall django-polls .. _bsdtar: http://gnuwin32.sourceforge.net/packages/bsdtar.htm .. _7-zip: http://www.7-zip.org/ @@ -347,11 +350,10 @@ is choosing the license under which your code is distributed. Installing Python packages with virtualenv ========================================== -Earlier, we installed the polls app as a system library. This has some +Earlier, we installed the polls app as a user library. This has some disadvantages: -* Modifying the system libraries can affect other Python software on your - system. +* Modifying the user libraries can affect other Python software on your system. * You won't be able to run multiple versions of this package (or others with the same name). -- 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') 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') 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 f975c4857d4442bf3d7c4cf1c28d317d26a2e297 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 2 Nov 2012 09:29:55 +0100 Subject: Fixed #19225 -- Typo in shortcuts docs. Thanks SunPowered for the report. --- docs/topics/http/shortcuts.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 0dc38b1459..b1b4700b73 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -4,7 +4,7 @@ Django shortcut functions .. module:: django.shortcuts :synopsis: - Convenience shortcuts that spam multiple levels of Django's MVC stack. + Convenience shortcuts that span multiple levels of Django's MVC stack. .. index:: shortcuts -- cgit v1.3 From 0d8432da552b2ddf2d2326edccae627dd05a414e Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Fri, 2 Nov 2012 10:51:10 +0100 Subject: Documented minimal python 3.2 version. --- docs/faq/install.txt | 8 ++++---- docs/topics/install.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'docs') diff --git a/docs/faq/install.txt b/docs/faq/install.txt index a772a379d5..a92c9d87ea 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -18,7 +18,7 @@ What are Django's prerequisites? Django requires Python, specifically Python 2.6.5 - 2.7.x. No other Python libraries are required for basic Django usage. Django 1.5 also has -experimental support for Python 3.2 and above. +experimental support for Python 3.2.3 and above. For a development environment -- if you just want to experiment with Django -- you don't need to have a separate Web server installed; Django comes with its @@ -69,14 +69,14 @@ Django version Python versions 1.2 2.4, 2.5, 2.6, 2.7 1.3 2.4, 2.5, 2.6, 2.7 **1.4** **2.5, 2.6, 2.7** -*1.5 (future)* *2.6, 2.7* and *3.2, 3.3 (experimental)* +*1.5 (future)* *2.6, 2.7* and *3.2.3, 3.3 (experimental)* ============== =============== Can I use Django with Python 3? ------------------------------- -Django 1.5 introduces experimental support for Python 3.2 and 3.3. However, we -don't yet suggest that you use Django and Python 3 in production. +Django 1.5 introduces experimental support for Python 3.2.3 and above. However, +we don't yet suggest that you use Django and Python 3 in production. Python 3 support should be considered a "preview". It's offered to bootstrap the transition of the Django ecosystem to Python 3, and to help you start diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 52994ed16a..976e29beeb 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -10,7 +10,7 @@ Install Python Being a Python Web framework, Django requires Python. It works with any Python version from 2.6.5 to 2.7. It also features -experimental support for versions 3.2 and 3.3. +experimental support for versions from 3.2.3 to 3.3. Get Python at http://www.python.org. If you're running Linux or Mac OS X, you probably already have it installed. -- cgit v1.3 From feaf9f279a73d87549c17fc7fb36463f1c7367a1 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 2 Nov 2012 06:54:00 -0400 Subject: Fixed #15361 - Documented performance considerations for QuerySet.get() Thanks mmcnickle for the patch. --- docs/topics/db/optimization.txt | 35 +++++++++++++++++++++++++++++++++++ tests/modeltests/basic/tests.py | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 772792d39d..b5cca52e23 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -132,6 +132,41 @@ Write your own :doc:`custom SQL to retrieve data or populate models `. Use ``django.db.connection.queries`` to find out what Django is writing for you and start from there. +Retrieve individual objects using a unique, indexed column +========================================================== + +There are two reasons to use a column with +:attr:`~django.db.models.Field.unique` or +:attr:`~django.db.models.Field.db_index` when using +:meth:`~django.db.models.query.QuerySet.get` to retrieve individual objects. +First, the query will be quicker because of the underlying database index. +Also, the query could run much slower if multiple objects match the lookup; +having a unique constraint on the column guarantees this will never happen. + +So using the :ref:`example Weblog models `:: + + >>> entry = Entry.objects.get(id=10) + +will be quicker than: + + >>> entry = Entry.object.get(headline="News Item Title") + +because ``id`` is indexed by the database and is guaranteed to be unique. + +Doing the following is potentially quite slow: + + >>> entry = Entry.objects.get(headline__startswith="News") + +First of all, `headline` is not indexed, which will make the underlying +database fetch slower. + +Second, the lookup doesn't guarantee that only one object will be returned. +If the query matches more than one object, it will retrieve and transfer all of +them from the database. This penalty could be substantial if hundreds or +thousands of records are returned. The penalty will be compounded if the +database lives on a separate server, where network overhead and latency also +play a factor. + Retrieve everything at once if you know you will need it ======================================================== diff --git a/tests/modeltests/basic/tests.py b/tests/modeltests/basic/tests.py index ebd70d14d9..1c83b980a7 100644 --- a/tests/modeltests/basic/tests.py +++ b/tests/modeltests/basic/tests.py @@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals from datetime import datetime -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.db.models.fields import Field, FieldDoesNotExist from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.utils import six @@ -128,6 +128,40 @@ class ModelTest(TestCase): b = Article.objects.get(pk=a.id) self.assertEqual(a, b) + # Create a very similar object + a = Article( + id=None, + headline='Area man programs in Python', + pub_date=datetime(2005, 7, 28), + ) + a.save() + + self.assertEqual(Article.objects.count(), 2) + + # Django raises an Article.MultipleObjectsReturned exception if the + # lookup matches more than one object + self.assertRaisesRegexp( + MultipleObjectsReturned, + "get\(\) returned more than one Article -- it returned 2!", + Article.objects.get, + headline__startswith='Area', + ) + + self.assertRaisesRegexp( + MultipleObjectsReturned, + "get\(\) returned more than one Article -- it returned 2!", + Article.objects.get, + pub_date__year=2005, + ) + + self.assertRaisesRegexp( + MultipleObjectsReturned, + "get\(\) returned more than one Article -- it returned 2!", + Article.objects.get, + pub_date__year=2005, + pub_date__month=7, + ) + def test_object_creation(self): # Create an Article. a = Article( -- 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') 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 07361d1fd6b4531e422e2593c91b47bc6bf88993 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 2 Nov 2012 16:33:07 -0400 Subject: Fixed #19167 - Added a warning regarding module-level database queries Thanks Daniele Procida for the patch. --- docs/topics/testing.txt | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'docs') diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 8fccf32946..a1524e4f15 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -379,6 +379,15 @@ control the particular collation used by the test database. See the :doc:`settings documentation ` for details of these advanced settings. +.. admonition:: Finding data from your production database when running tests? + + If your code attempts to access the database when its modules are compiled, + this will occur *before* the test database is set up, with potentially + unexpected results. For example, if you have a database query in + module-level code and a real database exists, production data could pollute + your tests. *It is a bad idea to have such import-time database queries in + your code* anyway - rewrite your code so that it doesn't do this. + .. _topics-testing-masterslave: Testing master/slave configurations -- 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') 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') 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') 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') 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 0546794397130b1574a667d57667bd032bff78d3 Mon Sep 17 00:00:00 2001 From: Markus Zapke-Gründemann Date: Sat, 3 Nov 2012 17:04:53 +0100 Subject: Fixed #19230 -- Extended the handler403 documentation. Added a paragraph on how to use the PermissionDenied exception to create a 403 response and use handler403. --- docs/topics/http/views.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'docs') diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt index 7c4d1bbb6e..caa2882f37 100644 --- a/docs/topics/http/views.txt +++ b/docs/topics/http/views.txt @@ -209,6 +209,17 @@ This view loads and renders the template ``403.html`` in your root template directory, or if this file does not exist, instead serves the text "403 Forbidden", as per :rfc:`2616` (the HTTP 1.1 Specification). +``django.views.defaults.permission_denied`` is triggered by a +:exc:`~django.core.exceptions.PermissionDenied` exception. To deny access in a +view you can use code like this:: + + from django.core.exceptions import PermissionDenied + + def edit(request, pk): + if not request.user.is_staff: + raise PermissionDenied + # ... + It is possible to override ``django.views.defaults.permission_denied`` in the same way you can for the 404 and 500 views by specifying a ``handler403`` in your URLconf:: -- cgit v1.3 From fc10418fba4fb906e4265650b62c510d526d63f7 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 3 Nov 2012 21:43:11 +0100 Subject: Fixed #18963 -- Used a subclass-friendly pattern for Python 2 object model compatibility methods. --- django/contrib/auth/context_processors.py | 4 +++- django/contrib/gis/measure.py | 16 ++++++++++++---- django/core/files/base.py | 8 ++++++-- django/core/serializers/base.py | 4 +--- django/core/serializers/xml_serializer.py | 2 -- django/db/backends/oracle/base.py | 4 +--- django/db/models/expressions.py | 12 ++++++++---- django/db/models/query.py | 4 +++- django/dispatch/saferef.py | 16 +++++++++------- django/forms/formsets.py | 4 +++- django/http/multipartparser.py | 16 ++++------------ django/http/response.py | 4 +--- django/utils/tree.py | 4 +++- docs/topics/python3.txt | 13 +++++++------ 14 files changed, 61 insertions(+), 50 deletions(-) (limited to 'docs') diff --git a/django/contrib/auth/context_processors.py b/django/contrib/auth/context_processors.py index 5929505359..3d17fe2754 100644 --- a/django/contrib/auth/context_processors.py +++ b/django/contrib/auth/context_processors.py @@ -18,7 +18,9 @@ class PermLookupDict(object): def __bool__(self): return self.user.has_module_perms(self.module_name) - __nonzero__ = __bool__ # Python 2 + + def __nonzero__(self): # Python 2 compatibility + return type(self).__bool__(self) class PermWrapper(object): diff --git a/django/contrib/gis/measure.py b/django/contrib/gis/measure.py index 6e074be355..e2e6b6bca8 100644 --- a/django/contrib/gis/measure.py +++ b/django/contrib/gis/measure.py @@ -151,7 +151,9 @@ class MeasureBase(object): **{self.STANDARD_UNIT: (self.standard / other)}) else: raise TypeError('%(class)s must be divided with number or %(class)s' % {"class":pretty_name(self)}) - __div__ = __truediv__ # Python 2 compatibility + + def __div__(self, other): # Python 2 compatibility + return type(self).__truediv__(self, other) def __itruediv__(self, other): if isinstance(other, NUMERIC_TYPES): @@ -159,11 +161,15 @@ class MeasureBase(object): return self else: raise TypeError('%(class)s must be divided with number' % {"class":pretty_name(self)}) - __idiv__ = __itruediv__ # Python 2 compatibility + + def __idiv__(self, other): # Python 2 compatibility + return type(self).__itruediv__(self, other) def __bool__(self): return bool(self.standard) - __nonzero__ = __bool__ # Python 2 compatibility + + def __nonzero__(self): # Python 2 compatibility + return type(self).__bool__(self) def default_units(self, kwargs): """ @@ -314,7 +320,9 @@ class Area(MeasureBase): **{self.STANDARD_UNIT: (self.standard / other)}) else: raise TypeError('%(class)s must be divided by a number' % {"class":pretty_name(self)}) - __div__ = __truediv__ # Python 2 compatibility + + def __div__(self, other): # Python 2 compatibility + return type(self).__truediv__(self, other) # Shortcuts diff --git a/django/core/files/base.py b/django/core/files/base.py index b81e180292..71de5ab741 100644 --- a/django/core/files/base.py +++ b/django/core/files/base.py @@ -28,7 +28,9 @@ class File(FileProxyMixin): def __bool__(self): return bool(self.name) - __nonzero__ = __bool__ # Python 2 + + def __nonzero__(self): # Python 2 compatibility + return type(self).__bool__(self) def __len__(self): return self.size @@ -142,7 +144,9 @@ class ContentFile(File): def __bool__(self): return True - __nonzero__ = __bool__ # Python 2 + + def __nonzero__(self): # Python 2 compatibility + return type(self).__bool__(self) def open(self, mode=None): self.seek(0) diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index 276f9a4738..294934a04a 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -112,7 +112,7 @@ class Serializer(object): if callable(getattr(self.stream, 'getvalue', None)): return self.stream.getvalue() -class Deserializer(object): +class Deserializer(six.Iterator): """ Abstract base deserializer class. """ @@ -138,8 +138,6 @@ class Deserializer(object): """Iteration iterface -- return the next item in the stream""" raise NotImplementedError - next = __next__ # Python 2 compatibility - class DeserializedObject(object): """ A deserialized model. diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 666587dc77..ea333a22bd 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -161,8 +161,6 @@ class Deserializer(base.Deserializer): return self._handle_object(node) raise StopIteration - next = __next__ # Python 2 compatibility - def _handle_object(self, node): """ Convert an node to a DeserializedObject. diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index aad52992dd..dfdfd4fd49 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -774,7 +774,7 @@ class FormatStylePlaceholderCursor(object): return CursorIterator(self.cursor) -class CursorIterator(object): +class CursorIterator(six.Iterator): """Cursor iterator wrapper that invokes our custom row factory.""" @@ -788,8 +788,6 @@ class CursorIterator(object): def __next__(self): return _rowfactory(next(self.iter), self.cursor) - next = __next__ # Python 2 compatibility - def _rowfactory(row, cursor): # Cast numeric values as the appropriate Python type based upon the diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 30c44bacde..3566d777c6 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -62,7 +62,9 @@ class ExpressionNode(tree.Node): def __truediv__(self, other): return self._combine(other, self.DIV, False) - __div__ = __truediv__ # Python 2 compatibility + + def __div__(self, other): # Python 2 compatibility + return type(self).__truediv__(self, other) def __mod__(self, other): return self._combine(other, self.MOD, False) @@ -94,7 +96,9 @@ class ExpressionNode(tree.Node): def __rtruediv__(self, other): return self._combine(other, self.DIV, True) - __rdiv__ = __rtruediv__ # Python 2 compatibility + + def __rdiv__(self, other): # Python 2 compatibility + return type(self).__rtruediv__(self, other) def __rmod__(self, other): return self._combine(other, self.MOD, True) @@ -151,10 +155,10 @@ class DateModifierNode(ExpressionNode): (A custom function is used in order to preserve six digits of fractional second information on sqlite, and to format both date and datetime values.) - Note that microsecond comparisons are not well supported with MySQL, since + Note that microsecond comparisons are not well supported with MySQL, since MySQL does not store microsecond information. - Only adding and subtracting timedeltas is supported, attempts to use other + Only adding and subtracting timedeltas is supported, attempts to use other operations raise a TypeError. """ def __init__(self, children, connector, negated=False): diff --git a/django/db/models/query.py b/django/db/models/query.py index c00080abef..a3b28e9228 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -136,7 +136,9 @@ class QuerySet(object): except StopIteration: return False return True - __nonzero__ = __bool__ # Python 2 + + def __nonzero__(self): # Python 2 compatibility + return type(self).__bool__(self) def __contains__(self, val): # The 'in' operator works without this method, due to __iter__. This diff --git a/django/dispatch/saferef.py b/django/dispatch/saferef.py index 84d1b2183c..7423669c96 100644 --- a/django/dispatch/saferef.py +++ b/django/dispatch/saferef.py @@ -67,9 +67,9 @@ class BoundMethodWeakref(object): same BoundMethodWeakref instance. """ - + _allInstances = weakref.WeakValueDictionary() - + def __new__( cls, target, onDelete=None, *arguments,**named ): """Create new instance or return current instance @@ -92,7 +92,7 @@ class BoundMethodWeakref(object): cls._allInstances[key] = base base.__init__( target, onDelete, *arguments,**named) return base - + def __init__(self, target, onDelete=None): """Return a weak-reference-like instance for a bound method @@ -132,7 +132,7 @@ class BoundMethodWeakref(object): self.weakFunc = weakref.ref(target.__func__, remove) self.selfName = str(target.__self__) self.funcName = str(target.__func__.__name__) - + def calculateKey( cls, target ): """Calculate the reference key for this reference @@ -141,7 +141,7 @@ class BoundMethodWeakref(object): """ return (id(target.__self__),id(target.__func__)) calculateKey = classmethod( calculateKey ) - + def __str__(self): """Give a friendly representation of the object""" return """%s( %s.%s )"""%( @@ -157,14 +157,16 @@ class BoundMethodWeakref(object): def __bool__( self ): """Whether we are still a valid reference""" return self() is not None - __nonzero__ = __bool__ # Python 2 + + def __nonzero__(self): # Python 2 compatibility + return type(self).__bool__(self) def __eq__(self, other): """Compare with another reference""" if not isinstance(other, self.__class__): return self.__class__ == type(other) return self.key == other.key - + def __call__(self): """Return a strong reference to the bound method diff --git a/django/forms/formsets.py b/django/forms/formsets.py index c646eed506..a0a38f336f 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -69,7 +69,9 @@ class BaseFormSet(object): def __bool__(self): """All formsets have a management form which is not included in the length""" return True - __nonzero__ = __bool__ # Python 2 + + def __nonzero__(self): # Python 2 compatibility + return type(self).__bool__(self) @property def management_form(self): diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py index 5bcc874982..9413a1eabb 100644 --- a/django/http/multipartparser.py +++ b/django/http/multipartparser.py @@ -256,7 +256,7 @@ class MultiPartParser(object): """Cleanup filename from Internet Explorer full paths.""" return filename and filename[filename.rfind("\\")+1:].strip() -class LazyStream(object): +class LazyStream(six.Iterator): """ The LazyStream wrapper allows one to get and "unget" bytes from a stream. @@ -323,8 +323,6 @@ class LazyStream(object): self.position += len(output) return output - next = __next__ # Python 2 compatibility - def close(self): """ Used to invalidate/disable this lazy stream. @@ -369,7 +367,7 @@ class LazyStream(object): " if there is none, report this to the Django developers." ) -class ChunkIter(object): +class ChunkIter(six.Iterator): """ An iterable that will yield chunks of data. Given a file-like object as the constructor, this object will yield chunks of read operations from that @@ -389,12 +387,10 @@ class ChunkIter(object): else: raise StopIteration() - next = __next__ # Python 2 compatibility - def __iter__(self): return self -class InterBoundaryIter(object): +class InterBoundaryIter(six.Iterator): """ A Producer that will iterate over boundaries. """ @@ -411,9 +407,7 @@ class InterBoundaryIter(object): except InputStreamExhausted: raise StopIteration() - next = __next__ # Python 2 compatibility - -class BoundaryIter(object): +class BoundaryIter(six.Iterator): """ A Producer that is sensitive to boundaries. @@ -489,8 +483,6 @@ class BoundaryIter(object): stream.unget(chunk[-rollback:]) return chunk[:-rollback] - next = __next__ # Python 2 compatibility - def _find_boundary(self, data, eof = False): """ Finds a multipart boundary in data. diff --git a/django/http/response.py b/django/http/response.py index 56e3d00096..df0a955b18 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -23,7 +23,7 @@ class BadHeaderError(ValueError): pass -class HttpResponseBase(object): +class HttpResponseBase(six.Iterator): """ An HTTP response base class with dictionary-accessed headers. @@ -218,8 +218,6 @@ class HttpResponseBase(object): # Subclasses must define self._iterator for this function. return self.make_bytes(next(self._iterator)) - next = __next__ # Python 2 compatibility - # These methods partially implement the file-like object interface. # See http://docs.python.org/lib/bltin-file-objects.html diff --git a/django/utils/tree.py b/django/utils/tree.py index 717181d2b9..ce490224e0 100644 --- a/django/utils/tree.py +++ b/django/utils/tree.py @@ -73,7 +73,9 @@ class Node(object): For truth value testing. """ return bool(self.children) - __nonzero__ = __bool__ # Python 2 + + def __nonzero__(self): # Python 2 compatibility + return type(self).__bool__(self) def __contains__(self, other): """ diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index f5749faaf2..e6dc165399 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -278,15 +278,13 @@ Iterators :: - class MyIterator(object): + class MyIterator(six.Iterator): def __iter__(self): return self # implement some logic here def __next__(self): raise StopIteration # implement some logic here - next = __next__ # Python 2 compatibility - Boolean evaluation ~~~~~~~~~~~~~~~~~~ @@ -297,7 +295,8 @@ Boolean evaluation def __bool__(self): return True # implement some logic here - __nonzero__ = __bool__ # Python 2 compatibility + def __nonzero__(self): # Python 2 compatibility + return type(self).__bool__(self) Division ~~~~~~~~ @@ -309,12 +308,14 @@ Division def __truediv__(self, other): return self / other # implement some logic here - __div__ = __truediv__ # Python 2 compatibility + def __div__(self, other): # Python 2 compatibility + return type(self).__truediv__(self, other) def __itruediv__(self, other): return self // other # implement some logic here - __idiv__ = __itruediv__ # Python 2 compatibility + def __idiv__(self, other): # Python 2 compatibility + return type(self).__itruediv__(self, other) .. module: django.utils.six -- cgit v1.3 From 4e8d9524c62d071718a85d1d55a18310be91b4f7 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 3 Nov 2012 23:53:51 +0100 Subject: Fixed #6234 -- Removed obsolete note about json and ensure_ascii Thanks aaron at cellmap.ca for the report. --- docs/topics/serialization.txt | 9 --------- 1 file changed, 9 deletions(-) (limited to 'docs') diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index 9b44166e42..28f600e223 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -166,15 +166,6 @@ Notes for specific serialization formats json ^^^^ -If you're using UTF-8 (or any other non-ASCII encoding) data with the JSON -serializer, you must pass ``ensure_ascii=False`` as a parameter to the -``serialize()`` call. Otherwise, the output won't be encoded correctly. - -For example:: - - json_serializer = serializers.get_serializer("json")() - json_serializer.serialize(queryset, ensure_ascii=False, stream=response) - Be aware that not all Django output can be passed unmodified to :mod:`json`. In particular, :ref:`lazy translation objects ` need a `special encoder`_ written for them. Something like this will work:: -- 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') 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') 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') 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 aee9c7b094cbd36c898ea465269935f156412ab9 Mon Sep 17 00:00:00 2001 From: Preston Holmes Date: Sun, 4 Nov 2012 12:25:48 -0800 Subject: Added a note and link to CLA from contributing docs --- docs/internals/contributing/writing-code/submitting-patches.txt | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'docs') diff --git a/docs/internals/contributing/writing-code/submitting-patches.txt b/docs/internals/contributing/writing-code/submitting-patches.txt index b51ac0d906..a90dc32605 100644 --- a/docs/internals/contributing/writing-code/submitting-patches.txt +++ b/docs/internals/contributing/writing-code/submitting-patches.txt @@ -52,8 +52,15 @@ and time availability), claim it by following these steps: page, 2. then click "Submit changes." +.. note:: + The Django software foundation requests that anyone contributing more than + a trivial patch to Django sign and submit a `Contributor License + Agreement`_, this ensures that the Django Software Foundation has clear + license to all contributions allowing for a clear license for all users. + .. _Create an account: https://www.djangoproject.com/accounts/register/ .. _password reset page: https://www.djangoproject.com/accounts/password/reset/ +.. _Contributor License Agreement: https://www.djangoproject.com/foundation/cla/ Ticket claimers' responsibility ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- 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') 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 0a49e6164c78ab6c828c08896856a77e2b423c91 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 4 Nov 2012 15:44:20 -0800 Subject: Corrected a typo that inadvertently made its way into the docs. --- docs/releases/1.5.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index a8024424bd..8b7af2cf36 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -304,7 +304,7 @@ Django 1.5 also includes several smaller improvements worth noting: * Multi-column indexes can now be created on models. Read the :attr:`~django.db.models.Options.index_together` documentation for more - infomration. + information. 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') 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') 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') 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') 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') 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 620e0bba4969f27230d35f75bc6a1624c3fac747 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 Nov 2012 08:53:10 -0500 Subject: Fixed #19154 - Noted commit_manually requires commit/rollback for reads Thanks als for the report. --- docs/topics/db/transactions.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 4a52c5af35..3c0ec4f187 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -161,8 +161,12 @@ managers, too. transactions. It tells Django you'll be managing the transaction on your own. - If your view changes data and doesn't ``commit()`` or ``rollback()``, - Django will raise a ``TransactionManagementError`` exception. + Whether you are writing or simply reading from the database, you must + ``commit()`` or ``rollback()`` explicitly or Django will raise a + :exc:`TransactionManagementError` exception. This is required when reading + from the database because ``SELECT`` statements may call functions which + modify tables, and thus it is impossible to know if any data has been + modified. Manual transaction management looks like this:: -- 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') 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 e8f696097b19abbd3a98990ea4ca5f58d0444826 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 Nov 2012 19:44:49 -0500 Subject: Fixed #19161 - Added missing clean_password method in custom user docs Thanks DavidW for the report. --- docs/topics/auth.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index f7a9084008..aed482f710 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -408,7 +408,7 @@ installation supports. The first entry in this list (that is, ``settings.PASSWORD_HASHERS[0]``) will be used to store passwords, and all the other entries are valid hashers that can be used to check existing passwords. This means that if you want to use a different algorithm, you'll need to modify -:setting:`PASSWORD_HASHERS` to list your prefered algorithm first in the list. +:setting:`PASSWORD_HASHERS` to list your preferred algorithm first in the list. The default for :setting:`PASSWORD_HASHERS` is:: @@ -2283,13 +2283,19 @@ code would be required in the app's ``admin.py`` file:: class UserChangeForm(forms.ModelForm): """A form for updateing users. Includes all the fields on the user, but replaces the password field with admin's - pasword hash display field. + password hash display field. """ password = ReadOnlyPasswordHashField() class Meta: model = MyUser + def clean_password(self): + # Regardless of what the user provides, return the initial value. + # This is done here, rather than on the field, because the + # field does not have access to the initial value + return self.initial["password"] + class MyUserAdmin(UserAdmin): # The forms to add and change user instances -- 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') 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') 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') 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 a79d920a56e7200b6259e60f7811162c07c7651d Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 9 Nov 2012 09:00:27 +0100 Subject: Fixed #19266 -- Added Texinfo documentation target Thanks orontee for the report and initial patch. --- docs/Makefile | 6 ++++++ docs/conf.py | 10 ++++++++++ docs/make.bat | 9 +++++++++ 3 files changed, 25 insertions(+) (limited to 'docs') diff --git a/docs/Makefile b/docs/Makefile index bdf48549a3..f6293a8e7f 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -31,6 +31,7 @@ help: @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" + @echo " texinfo to make a Texinfo source file" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @@ -116,6 +117,11 @@ man: @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished; the Texinfo files are in $(BUILDDIR)/texinfo." + gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo diff --git a/docs/conf.py b/docs/conf.py index ced3fef5f7..939d70b7b4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -248,6 +248,16 @@ man_pages = [ ] +# -- Options for Texinfo output ------------------------------------------------ + +# List of tuples (startdocname, targetname, title, author, dir_entry, +# description, category, toctree_only) +texinfo_documents=[( + master_doc, "django", "", "", "Django", + "Documentation of the Django framework", "Web development", False +)] + + # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. diff --git a/docs/make.bat b/docs/make.bat index d6299521eb..d7f54b2059 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -29,6 +29,7 @@ if "%1" == "help" ( echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages + echo. texinfo to make a Texinfo source file echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity @@ -143,6 +144,14 @@ if "%1" == "man" ( goto end ) +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + if "%%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 -- 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') 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') 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') 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') 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 a72b8a224721775b31e2075c99165f1e3ca28092 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 13 Nov 2012 05:45:08 -0500 Subject: Fixed #19260 - Added a comment to tutorial 1. Thanks terwey for the suggestion. --- docs/intro/tutorial01.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 1e2231d1e0..5712d557c6 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -52,7 +52,7 @@ code, then run the following command: django-admin.py startproject mysite -This will create a ``mysite`` directory in your current directory. If it didn't +This will create a ``mysite`` directory in your current directory. If it didn't work, see :doc:`Troubleshooting `. .. admonition:: Script name may differ in distribution packages @@ -666,6 +666,7 @@ Save these changes and start a new Python interactive shell by running >>> Poll.objects.get(pub_date__year=2012) + # Request an ID that doesn't exist, this will raise an exception. >>> Poll.objects.get(id=2) Traceback (most recent call last): ... -- 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') 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 54fbe6ce5f758d63213ef571f175978823881834 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Tue, 13 Nov 2012 13:51:50 -0800 Subject: Correct link to Sentry django-sentry is no longer maintained, and sentry is the replacement. --- docs/topics/logging.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index 7bd56e92ec..bbcd011b59 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -470,13 +470,13 @@ Python logging module. with names and values of local variables at each level of the stack, plus the values of your Django settings. This information is potentially very sensitive, and you may not want to send it over email. Consider using - something such as `django-sentry`_ to get the best of both worlds -- the + something such as `Sentry`_ to get the best of both worlds -- the rich information of full tracebacks plus the security of *not* sending the information over email. You may also explicitly designate certain sensitive information to be filtered out of error reports -- learn more on :ref:`Filtering error reports`. -.. _django-sentry: http://pypi.python.org/pypi/django-sentry +.. _django-sentry: http://pypi.python.org/pypi/sentry Filters -- cgit v1.3 From 1e34fd3c03e8f9a9e2b9be35488b8209178a4df0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 13 Nov 2012 14:48:23 -0800 Subject: fixed a broken link in the docs --- docs/topics/logging.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index bbcd011b59..d016b59969 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -476,7 +476,7 @@ Python logging module. sensitive information to be filtered out of error reports -- learn more on :ref:`Filtering error reports`. -.. _django-sentry: http://pypi.python.org/pypi/sentry +.. _Sentry: http://pypi.python.org/pypi/sentry Filters -- cgit v1.3 From 2dbfa66f4db54c2bbac0f160de96a91fcf39997d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 14 Nov 2012 05:46:30 -0500 Subject: Fixed #19289 - Removed an out of place sentence in tutorial 2. Thanks colinnkeenan for the report. --- docs/intro/tutorial02.txt | 5 ----- 1 file changed, 5 deletions(-) (limited to 'docs') diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index b87b280d7c..2c8d25ae6f 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -445,11 +445,6 @@ live anywhere on your filesystem that Django can access. (Django runs as whatever user your server runs.) However, keeping your templates within the project is a good convention to follow. -When you’ve done that, create a directory polls in your template directory. -Within that, create a file called index.html. Note that our -``loader.get_template('polls/index.html')`` code from above maps to -[template_directory]/polls/index.html” on the filesystem. - By default, :setting:`TEMPLATE_DIRS` is empty. So, let's add a line to it, to tell Django where our templates live:: -- 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') 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 44046e8a38225067d4d0feac35367eeae133446a Mon Sep 17 00:00:00 2001 From: Preston Holmes Date: Fri, 16 Nov 2012 16:50:50 -0800 Subject: Fixed #18985 -- made DeprecationWarnings loud Capture warnings in Python >= 2.7 and route through console handler, which is subject to DEBUG==True Thanks to dstufft for the idea, and claudep for initial patch --- django/conf/__init__.py | 10 ++++++++++ django/utils/log.py | 3 +++ docs/releases/1.5.txt | 7 +++++++ tests/regressiontests/logging_tests/tests.py | 29 ++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+) (limited to 'docs') diff --git a/django/conf/__init__.py b/django/conf/__init__.py index 4e6f0f9211..dec4cf9418 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -6,6 +6,7 @@ variable, and then from django.conf.global_settings; see the global settings fil a list of all possible variables. """ +import logging import os import time # Needed for Windows import warnings @@ -55,6 +56,15 @@ class LazySettings(LazyObject): """ Setup logging from LOGGING_CONFIG and LOGGING settings. """ + try: + # Route warnings through python logging + logging.captureWarnings(True) + # Allow DeprecationWarnings through the warnings filters + warnings.simplefilter("default", DeprecationWarning) + except AttributeError: + # No captureWarnings on Python 2.6, DeprecationWarnings are on anyway + pass + if self.LOGGING_CONFIG: from django.utils.log import DEFAULT_LOGGING # First find the logging configuration function ... diff --git a/django/utils/log.py b/django/utils/log.py index 342ca1fc10..4806527e84 100644 --- a/django/utils/log.py +++ b/django/utils/log.py @@ -62,6 +62,9 @@ DEFAULT_LOGGING = { 'level': 'ERROR', 'propagate': False, }, + 'py.warnings': { + 'handlers': ['console'], + }, } } diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 8b7af2cf36..c53518feaa 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -306,6 +306,13 @@ Django 1.5 also includes several smaller improvements worth noting: :attr:`~django.db.models.Options.index_together` documentation for more information. +* During Django's logging configuration verbose Deprecation warnings are + enabled and warnings are captured into the logging system. Logged warnings + are routed through the ``console`` logging handler, which by default requires + :setting:`DEBUG` to be True for output to be generated. The result is that + DeprecationWarnings should be printed to the console in development + environments the way they have been in Python versions < 2.7. + Backwards incompatible changes in 1.5 ===================================== diff --git a/tests/regressiontests/logging_tests/tests.py b/tests/regressiontests/logging_tests/tests.py index 96f81981c6..0e56195c41 100644 --- a/tests/regressiontests/logging_tests/tests.py +++ b/tests/regressiontests/logging_tests/tests.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import copy import logging +import sys import warnings from django.conf import compat_patch_logging_config, LazySettings @@ -10,9 +11,11 @@ from django.test import TestCase, RequestFactory from django.test.utils import override_settings from django.utils.log import CallbackFilter, RequireDebugFalse from django.utils.six import StringIO +from django.utils.unittest import skipUnless from ..admin_scripts.tests import AdminScriptTestCase +PYVERS = sys.version_info[:2] # logging config prior to using filter with mail_admins OLD_LOGGING = { @@ -131,6 +134,32 @@ class DefaultLoggingTest(TestCase): self.logger.error("Hey, this is an error.") self.assertEqual(output.getvalue(), 'Hey, this is an error.\n') +@skipUnless(PYVERS > (2,6), "warnings captured only in Python >= 2.7") +class WarningLoggerTests(TestCase): + """ + Tests that warnings output for DeprecationWarnings is enabled + and captured to the logging system + """ + def setUp(self): + self.logger = logging.getLogger('py.warnings') + self.old_stream = self.logger.handlers[0].stream + + def tearDown(self): + self.logger.handlers[0].stream = self.old_stream + + @override_settings(DEBUG=True) + def test_warnings_capture(self): + output = StringIO() + self.logger.handlers[0].stream = output + warnings.warn('Foo Deprecated', DeprecationWarning) + self.assertTrue('Foo Deprecated' in output.getvalue()) + + def test_warnings_capture_debug_false(self): + output = StringIO() + self.logger.handlers[0].stream = output + warnings.warn('Foo Deprecated', DeprecationWarning) + self.assertFalse('Foo Deprecated' in output.getvalue()) + class CallbackFilterTest(TestCase): def test_sense(self): -- 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') 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 7a38e86bde3cae12353fe0a4b5de9420ba780fb1 Mon Sep 17 00:00:00 2001 From: Preston Holmes Date: Sat, 17 Nov 2012 07:05:34 -0800 Subject: Fixed #19310 -- changed method docs formatting for custom file storage docs --- docs/howto/custom-file-storage.txt | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'docs') diff --git a/docs/howto/custom-file-storage.txt b/docs/howto/custom-file-storage.txt index 5f1dae17ef..51558d9715 100644 --- a/docs/howto/custom-file-storage.txt +++ b/docs/howto/custom-file-storage.txt @@ -27,9 +27,9 @@ You'll need to follow these steps: option = settings.CUSTOM_STORAGE_OPTIONS ... -#. Your storage class must implement the ``_open()`` and ``_save()`` methods, - along with any other methods appropriate to your storage class. See below for - more on these methods. +#. Your storage class must implement the :meth:`_open()` and :meth:`_save() + methods, along with any other methods appropriate to your storage class. See + below for more on these methods. In addition, if your class provides local file storage, it must override the ``path()`` method. @@ -46,8 +46,7 @@ Your custom storage system may override any of the storage methods explained in You'll also usually want to use hooks specifically designed for custom storage objects. These are: -``_open(name, mode='rb')`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. method:: _open(name, mode='rb') **Required**. @@ -56,8 +55,7 @@ uses to open the file. This must return a ``File`` object, though in most cases, you'll want to return some subclass here that implements logic specific to the backend storage system. -``_save(name, content)`` -~~~~~~~~~~~~~~~~~~~~~~~~ +.. method:: _save(name, content) Called by ``Storage.save()``. The ``name`` will already have gone through ``get_valid_name()`` and ``get_available_name()``, and the ``content`` will be a @@ -67,8 +65,8 @@ Should return the actual name of name of the file saved (usually the ``name`` passed in, but if the storage needs to change the file name return the new name instead). -``get_valid_name(name)`` -~~~~~~~~~~~~~~~~~~~~~~~~ +.. method:: get_valid_name(name) + Returns a filename suitable for use with the underlying storage system. The ``name`` argument passed to this method is the original filename sent to the @@ -78,8 +76,7 @@ how non-standard characters are converted to safe filenames. The code provided on ``Storage`` retains only alpha-numeric characters, periods and underscores from the original filename, removing everything else. -``get_available_name(name)`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. method:: get_available_name(name) Returns a filename that is available in the storage mechanism, possibly taking the provided filename into account. The ``name`` argument passed to this method -- cgit v1.3 From 7058b595b668b49daef8beb76a2b5c5f1d991b00 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 30 Oct 2012 20:09:49 -0400 Subject: Fixed #16779 - Added a contributing tutorial Thank-you Taavi Taijala for the draft patch! --- docs/index.txt | 3 +- .../contributing/writing-code/unit-tests.txt | 4 + docs/intro/contributing.txt | 580 +++++++++++++++++++++ docs/intro/index.txt | 1 + 4 files changed, 587 insertions(+), 1 deletion(-) create mode 100644 docs/intro/contributing.txt (limited to 'docs') diff --git a/docs/index.txt b/docs/index.txt index a6d9ed2b13..e6eb77c98f 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -47,7 +47,8 @@ Are you new to Django or to programming? This is the place to start! :doc:`Part 4 ` * **Advanced Tutorials:** - :doc:`How to write reusable apps ` + :doc:`How to write reusable apps ` | + :doc:`Writing your first patch for Django ` The model layer =============== diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index a828b06b36..71666a169e 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -38,6 +38,8 @@ with this sample ``settings`` module, ``cd`` into the Django If you get an ``ImportError: No module named django.contrib`` error, you need to add your install of Django to your ``PYTHONPATH``. +.. _running-unit-tests-settings: + Using another ``settings`` module ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -133,6 +135,8 @@ Then, run the tests normally, for example: ./runtests.py --settings=test_sqlite admin_inlines +.. _running-unit-tests-dependencies: + Running all the tests ~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt new file mode 100644 index 0000000000..a343814c02 --- /dev/null +++ b/docs/intro/contributing.txt @@ -0,0 +1,580 @@ +=================================== +Writing your first patch for Django +=================================== + +Introduction +============ + +Interested in giving back to the community a little? Maybe you've found a bug +in Django that you'd like to see fixed, or maybe there's a small feature you +want added. + +Contributing back to Django itself is the best way to see your own concerns +addressed. This may seem daunting at first, but it's really pretty simple. +We'll walk you through the entire process, so you can learn by example. + +Who's this tutorial for? +------------------------ + +For this tutorial, we expect that you have at least a basic understanding of +how Django works. This means you should be comfortable going through the +existing tutorials on :doc:`writing your first Django app`. +In addition, you should have a good understanding of Python itself. But if you +don't, `Dive Into Python`__ is a fantastic (and free) online book for beginning +Python programmers. + +Those of you who are unfamiliar with version control systems and Trac will find +that this tutorial and its links include just enough information to get started. +However, you'll probably want to read some more about these different tools if +you plan on contributing to Django regularly. + +For the most part though, this tutorial tries to explain as much as possible, +so that it can be of use to the widest audience. + +.. admonition:: Where to get help: + + If you're having trouble going through this tutorial, please post a message + to `django-developers`__ or drop by `#django-dev on irc.freenode.net`__ to + chat with other Django users who might be able to help. + +__ http://diveintopython.net/toc/index.html +__ http://groups.google.com/group/django-developers +__ irc://irc.freenode.net/django-dev + +What does this tutorial cover? +------------------------------ + +We'll be walking you through contributing a patch to Django for the first time. +By the end of this tutorial, you should have a basic understanding of both the +tools and the processes involved. Specifically, we'll be covering the following: + +* Installing Git. +* How to download a development copy of Django. +* Running Django's test suite. +* Writing a test for your patch. +* Writing the code for your patch. +* Testing your patch. +* Generating a patch file for your changes. +* Where to look for more information. + +Once you're done with the tutorial, you can look through the rest of +:doc:`Django's documentation on contributing`. +It contains lots of great information and is a must read for anyone who'd like +to become a regular contributor to Django. If you've got questions, it's +probably got the answers. + +Installing Git +============== + +For this tutorial, you'll need Git installed to download the current +development version of Django and to generate patch files for the changes you +make. + +To check whether or not you have Git installed, enter ``git`` into the command +line. If you get messages saying that this command could be found, you'll have +to download and install it, see `Git's download page`__. + +If you're not that familiar with Git, you can always find out more about its +commands (once it's installed) by typing ``git help`` into the command line. + +__ http://git-scm.com/download + +Getting a copy of Django's development version +============================================== + +The first step to contributing to Django is to get a copy of the source code. +From the command line, use the ``cd`` command to navigate to the directory +where you'll want your local copy of Django to live. + +Download the Django source code repository using the following command:: + + git clone https://github.com/django/django.git + +.. note:: + + For users who wish to use `virtualenv`__, you can use:: + + pip install -e /path/to/your/local/clone/django/ + + to link your cloned checkout into a virtual environment. This is a great + option to isolate your development copy of Django from the rest of your + system and avoids potential package conflicts. + +__ http://www.virtualenv.org + +Rolling back to a previous revision of Django +============================================= + +For this tutorial, we'll be using `ticket #17549`__ as a case study, so we'll +rewind Django's version history in git to before that ticket's patch was +applied. This will allow us to go through all of the steps involved in writing +that patch from scratch, including running Django's test suite. + +**Keep in mind that while we'll be using an older revision of Django's trunk +for the purposes of the tutorial below, you should always use the current +development revision of Django when working on your own patch for a ticket!** + +.. note:: + + The patch for this ticket was written by Ulrich Petri, and it was applied + to Django as `commit ac2052ebc84c45709ab5f0f25e685bf656ce79bc`__. + Consequently, we'll be using the revision of Django just prior to that, + `commit 39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac`__. + +__ https://code.djangoproject.com/ticket/17549 +__ https://github.com/django/django/commit/ac2052ebc84c45709ab5f0f25e685bf656ce79bc +__ https://github.com/django/django/commit/39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac + +Navigate into Django's root directory (that's the one that contains ``django``, +``docs``, ``tests``, ``AUTHORS``, etc.). You can then check out the older +revision of Django that we'll be using in the tutorial below:: + + git checkout 39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac + +Running Django's test suite for the first time +============================================== + +When contributing to Django it's very important that your code changes don't +introduce bugs into other areas of Django. One way to check that Django still +works after you make your changes is by running Django's test suite. If all +the tests still pass, then you can be reasonably sure that your changes +haven't completely broken Django. If you've never run Django's test suite +before, it's a good idea to run it once beforehand just to get familiar with +what its output is supposed to look like. + +We can run the test suite by simply ``cd``-ing into the Django ``tests/`` +directory and, if you're using GNU/Linux, Mac OS X or some other flavor of +Unix, run:: + + PYTHONPATH=.. python runtests.py --settings=test_sqlite + +If you're on Windows, the above should work provided that you are using +"Git Bash" provided by the default Git install. GitHub has a `nice tutorial`__. + +__ https://help.github.com/articles/set-up-git#platform-windows + +.. note:: + + If you're using ``virtualenv``, you can omit ``PYTHONPATH=..`` when running + the tests. This instructs Python to look for Django in the parent directory + of ``tests``. ``virtualenv`` puts your copy of Django on the ``PYTHONPATH`` + automatically. + +Now sit back and relax. Django's entire test suite has over 4800 different +tests, so it can take anywhere from 5 to 15 minutes to run, depending on the +speed of your computer. + +While Django's test suite is running, you'll see a stream of characters +representing the status of each test as it's run. ``E`` indicates that an error +was raised during a test, and ``F`` indicates that a test's assertions failed. +Both of these are considered to be test failures. Meanwhile, ``x`` and ``s`` +indicated expected failures and skipped tests, respectively. Dots indicate +passing tests. + +Skipped tests are typically due to missing external libraries required to run +the test; see :ref:`running-unit-tests-dependencies` for a list of dependencies +and be sure to install any for tests related to the changes you are making (we +won't need any for this tutorial). + +Once the tests complete, you should be greeted with a message informing you +whether the test suite passed or failed. Since you haven't yet made any changes +to Django's code, the entire test suite **should** pass. If you get failures or +errors make sure you've followed all of the previous steps properly. See +:ref:`running-unit-tests` for more information. + +Note that the latest Django trunk may not always be stable. When developing +against trunk, you can check `Django's continuous integration builds`__ to +determine if the failures are specific to your machine or if they are also +present in Django's official builds. If you click to view a particular build, +you can view the "Configuration Matrix" which shows failures broken down by +Python version and database backend. + +__ http://ci.djangoproject.com/ + +.. note:: + + For this tutorial and the ticket we're working on, testing against SQLite + is sufficient, however, it's possible (and sometimes necessary) to + :ref:`run the tests using a different database + `. + +Writing some tests for your ticket +================================== + +In most cases, for a patch to be accepted into Django it has to include tests. +For bug fix patches, this means writing a regression test to ensure that the +bug is never reintroduced into Django later on. A regression test should be +written in such a way that it will fail while the bug still exists and pass +once the bug has been fixed. For patches containing new features, you'll need +to include tests which ensure that the new features are working correctly. +They too should fail when the new feature is not present, and then pass once it +has been implemented. + +A good way to do this is to write your new tests first, before making any +changes to the code. This style of development is called +`test-driven development`__ and can be applied to both entire projects and +single patches. After writing your tests, you then run them to make sure that +they do indeed fail (since you haven't fixed that bug or added that feature +yet). If your new tests don't fail, you'll need to fix them so that they do. +After all, a regression test that passes regardless of whether a bug is present +is not very helpful at preventing that bug from reoccurring down the road. + +Now for our hands-on example. + +__ http://en.wikipedia.org/wiki/Test-driven_development + +Writing some tests for ticket #17549 +------------------------------------ + +`Ticket #17549`__ describes the following, small feature addition: + + It's useful for URLField to give you a way to open the URL; otherwise you + might as well use a CharField. + +In order to resolve this ticket, we'll add a ``render`` method to the +``AdminURLFieldWidget`` in order to display a clickable link above the input +widget. Before we make those changes though, we're going to write a couple +tests to verify that our modification functions correctly and continues to +function correctly in the future. + +Navigate to Django's ``tests/regressiontests/admin_widgets/`` folder and +open the ``tests.py`` file. Add the following code on line 269 right before the +``AdminFileWidgetTest`` class:: + + 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:

' + ) + +The new tests check to see that the ``render`` method we'll be adding works +correctly in a couple different situations. + +.. admonition:: But this testing thing looks kinda hard... + + If you've never had to deal with tests before, they can look a little hard + to write at first glance. Fortunately, testing is a *very* big subject in + computer programming, so there's lots of information out there: + + * A good first look at writing tests for Django can be found in the + documentation on :doc:`Testing Django applications`. + * Dive Into Python (a free online book for beginning Python developers) + includes a great `introduction to Unit Testing`__. + * After reading those, if you want something a little meatier to sink + your teeth into, there's always the `Python unittest documentation`__. + +__ https://code.djangoproject.com/ticket/17549 +__ http://diveintopython.net/unit_testing/index.html +__ http://docs.python.org/library/unittest.html + +Running your new test +--------------------- + +Remember that we haven't actually made any modifications to +``AdminURLFieldWidget`` yet, so our tests are going to fail. Let's run all the +tests in the ``model_forms_regress`` folder to make sure that's really what +happens. From the command line, ``cd`` into the Django ``tests/`` directory +and run:: + + PYTHONPATH=.. python runtests.py --settings=test_sqlite admin_widgets + +If the tests ran correctly, you should see three failures corresponding to each +of the test methods we added. If all of the tests passed, then you'll want to +make sure that you added the new test shown above to the appropriate folder and +class. + +Writing the code for your ticket +================================ + +Next we'll be adding the functionality described in `ticket #17549`__ to Django. + +Writing the code for ticket #17549 +---------------------------------- + +Navigate to the ``django/django/contrib/admin/`` folder and open the +``widgets.py`` file. Find the ``AdminURLFieldWidget`` class on line 302 and add +the following ``render`` method after the existing ``__init__`` method:: + + 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 + +Verifying your test now passes +------------------------------ + +Once you're done modifying Django, we need to make sure that the tests we wrote +earlier pass, so we can see whether the code we wrote above is working +correctly. To run the tests in the ``admin_widgets`` folder, ``cd`` into the +Django ``tests/`` directory and run:: + + PYTHONPATH=.. python runtests.py --settings=test_sqlite admin_widgets + +Oops, good thing we wrote those tests! You should still see 3 failures with +the following exception:: + + NameError: global name 'smart_urlquote' is not defined + +We forgot to add the import for that method. Go ahead and add the +``smart_urlquote`` import at the end of line 13 of +``django/contrib/admin/widgets.py`` so it looks as follows:: + + from django.utils.html import escape, format_html, format_html_join, smart_urlquote + +Re-run the tests and everything should pass. If it doesn't, make sure you +correctly modified the ``AdminURLFieldWidget`` class as shown above and +copied the new tests correctly. + +__ https://code.djangoproject.com/ticket/17549 + +Running Django's test suite for the second time +=============================================== + +Once you've verified that your patch and your test are working correctly, it's +a good idea to run the entire Django test suite just to verify that your change +hasn't introduced any bugs into other areas of Django. While successfully +passing the entire test suite doesn't guarantee your code is bug free, it does +help identify many bugs and regressions that might otherwise go unnoticed. + +To run the entire Django test suite, ``cd`` into the Django ``tests/`` +directory and run:: + + PYTHONPATH=.. python runtests.py --settings=test_sqlite + +As long as you don't see any failures, you're good to go. Note that this fix +also made a `small CSS change`__ to format the new widget. You can make the +change if you'd like, but we'll skip it for now in the interest of brevity. + +__ https://github.com/django/django/commit/ac2052ebc84c45709ab5f0f25e685bf656ce79bc#diff-0 + +Writing Documentation +===================== + +This is a new feature, so it should be documented. Add the following on line +925 of ``django/docs/ref/models/fields.txt`` beneath the existing docs for +``URLField``:: + + .. versionadded:: 1.5 + + The current value of the field will be displayed as a clickable link above the + input widget. + +For more information on writing documentation, including an explanation of what +the ``versionadded`` bit is all about, see +:doc:`/internals/contributing/writing-documentation`. That page also includes +an explanation of how to build a copy of the documentation locally, so you can +preview the HTML that will be generated. + +Generating a patch for your changes +=================================== + +Now it's time to generate a patch file that can be uploaded to Trac or applied +to another copy of Django. To get a look at the content of your patch, run the +following command:: + + git diff + +This will display the differences between your current copy of Django (with +your changes) and the revision that you initially checked out earlier in the +tutorial. + +Once you're done looking at the patch, hit the ``q`` key to exit back to the +command line. If the patch's content looked okay, you can run the following +command to save the patch file to your current working directory:: + + git diff > 17549.diff + +You should now have a file in the root Django directory called ``17549.diff``. +This patch file contains all your changes and should look this: + +.. code-block:: diff + + diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py + index 1e0bc2d..9e43a10 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,18 @@ 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 809d56e..d44f85f 100644 + --- a/docs/ref/models/fields.txt + +++ b/docs/ref/models/fields.txt + @@ -922,6 +922,10 @@ 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 4b11543..94acc6d 100644 + --- a/tests/regressiontests/admin_widgets/tests.py + +++ b/tests/regressiontests/admin_widgets/tests.py + @@ -265,6 +265,35 @@ class AdminSplitDateTimeWidgetTest(DjangoTestCase): + '

Datum:
Zeit:

', + ) + + +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): + +So what do I do next? +===================== + +Congratulations, you've generated your very first Django patch! Now that you've +got that under your belt, you can put those skills to good use by helping to +improve Django's codebase. Generating patches and attaching them to Trac +tickets is useful, however, since we are using git - adopting a more :doc:`git +oriented workflow ` is +recommended. + +Since we never committed our changes locally, perform the following to get your +git branch back to a good starting point:: + + git reset --hard HEAD + git checkout master + +More information for new contributors +------------------------------------- + +Before you get too into writing patches for Django, there's a little more +information on contributing that you should probably take a look at: + +* You should make sure to read Django's documentation on + :doc:`claiming tickets and submitting patches + `. + It covers Trac etiquette, how to claim tickets for yourself, expected + coding style for patches, and many other important details. +* First time contributors should also read Django's :doc:`documentation + for first time contributors`. + It has lots of good advice for those of us who are new to helping out + with Django. +* After those, if you're still hungry for more information about + contributing, you can always browse through the rest of + :doc:`Django's documentation on contributing`. + It contains a ton of useful information and should be your first source + for answering any questions you might have. + +Finding your first real ticket +------------------------------ + +Once you've looked through some of that information, you'll be ready to go out +and find a ticket of your own to write a patch for. Pay special attention to +tickets with the "easy pickings" criterion. These tickets are often much +simpler in nature and are great for first time contributors. Once you're +familiar with contributing to Django, you can move on to writing patches for +more difficult and complicated tickets. + +If you just want to get started already (and nobody would blame you!), try +taking a look at the list of `easy tickets that need patches`__ and the +`easy tickets that have patches which need improvement`__. If you're familiar +with writing tests, you can also look at the list of +`easy tickets that need tests`__. Just remember to follow the guidelines about +claiming tickets that were mentioned in the link to Django's documentation on +:doc:`claiming tickets and submitting patches +`. + +__ https://code.djangoproject.com/query?status=new&status=reopened&has_patch=0&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority +__ https://code.djangoproject.com/query?status=new&status=reopened&needs_better_patch=1&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority +__ https://code.djangoproject.com/query?status=new&status=reopened&needs_tests=1&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority + +What's next? +------------ + +After a ticket has a patch, it needs to be reviewed by a second set of eyes. +After uploading a patch or submitting a pull request, be sure to update the +ticket metadata by setting the flags on the ticket to say "has patch", +"doesn't need tests", etc, so others can find it for review. Contributing +doesn't necessarily always mean writing a patch from scratch. Reviewing +existing patches is also a very helpful contribution. See +:doc:`/internals/contributing/triaging-tickets` for details. diff --git a/docs/intro/index.txt b/docs/intro/index.txt index afb1825b87..bca2d7712b 100644 --- a/docs/intro/index.txt +++ b/docs/intro/index.txt @@ -15,6 +15,7 @@ place: read this material to quickly get up and running. tutorial04 reusable-apps whatsnext + contributing .. seealso:: -- cgit v1.3 From 1520748dac95a7f114e4bb2feeee04d46c720494 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sat, 17 Nov 2012 20:24:54 +0100 Subject: Fixed #2550 -- Allow the auth backends to raise the PermissionDenied exception to completely stop the authentication chain. Many thanks to namn, danielr, Dan Julius, Łukasz Rekucki, Aashu Dwivedi and umbrae for working this over the years. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- django/contrib/auth/__init__.py | 5 +++- django/contrib/auth/tests/auth_backends.py | 37 +++++++++++++++++++++++++++++- docs/releases/1.6.txt | 6 +++++ docs/topics/auth.txt | 6 +++++ 4 files changed, 52 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index dd4a8484f5..5dbda44501 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -1,6 +1,6 @@ import re -from django.core.exceptions import ImproperlyConfigured +from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.utils.importlib import import_module from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed @@ -60,6 +60,9 @@ def authenticate(**credentials): except TypeError: # This backend doesn't accept these credentials as arguments. Try the next one. continue + except PermissionDenied: + # This backend says to stop in our tracks - this user should not be allowed in at all. + return None if user is None: continue # Annotate the user object with the path of the backend. diff --git a/django/contrib/auth/tests/auth_backends.py b/django/contrib/auth/tests/auth_backends.py index e92f159ff9..2ab0bd0efa 100644 --- a/django/contrib/auth/tests/auth_backends.py +++ b/django/contrib/auth/tests/auth_backends.py @@ -6,7 +6,8 @@ from django.contrib.auth.models import User, Group, Permission, AnonymousUser from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.custom_user import ExtensionUser from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ImproperlyConfigured +from django.core.exceptions import ImproperlyConfigured, PermissionDenied +from django.contrib.auth import authenticate from django.test import TestCase from django.test.utils import override_settings @@ -323,3 +324,37 @@ class InActiveUserBackendTest(TestCase): def test_has_module_perms(self): self.assertEqual(self.user1.has_module_perms("app1"), False) self.assertEqual(self.user1.has_module_perms("app2"), False) + + +class PermissionDeniedBackend(object): + """ + Always raises PermissionDenied. + """ + supports_object_permissions = True + supports_anonymous_user = True + supports_inactive_user = True + + def authenticate(self, username=None, password=None): + raise PermissionDenied + + +class PermissionDeniedBackendTest(TestCase): + """ + Tests that other backends are not checked once a backend raises PermissionDenied + """ + backend = 'django.contrib.auth.tests.auth_backends.PermissionDeniedBackend' + + def setUp(self): + self.user1 = User.objects.create_user('test', 'test@example.com', 'test') + self.user1.save() + + @override_settings(AUTHENTICATION_BACKENDS=(backend, ) + + tuple(settings.AUTHENTICATION_BACKENDS)) + def test_permission_denied(self): + "user is not authenticated after a backend raises permission denied #2550" + self.assertEqual(authenticate(username='test', password='test'), None) + + @override_settings(AUTHENTICATION_BACKENDS=tuple( + settings.AUTHENTICATION_BACKENDS) + (backend, )) + def test_authenticates(self): + self.assertEqual(authenticate(username='test', password='test'), self.user1) diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt index ef162a8de3..49649bc6b8 100644 --- a/docs/releases/1.6.txt +++ b/docs/releases/1.6.txt @@ -17,6 +17,12 @@ deprecation process for some features`_. What's new in Django 1.6 ======================== +Minor features +~~~~~~~~~~~~~~ + +* Authentication backends can raise ``PermissionDenied`` to immediately fail + the authentication chain. + Backwards incompatible changes in 1.6 ===================================== diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index aed482f710..6d8e3c66c3 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -2391,6 +2391,12 @@ processing at the first positive match. you need to force users to re-authenticate using different methods. A simple way to do that is simply to execute ``Session.objects.all().delete()``. +.. versionadded:: 1.6 + +If a backend raises a :class:`~django.core.exceptions.PermissionDenied` +exception, authentication will immediately fail. Django won't check the +backends that follow. + Writing an authentication backend --------------------------------- -- 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') 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 4585e123187a8a94a0db11d11358398911b10168 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 17 Nov 2012 23:25:37 +0100 Subject: Fix typo in file storage docs. --- docs/howto/custom-file-storage.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/howto/custom-file-storage.txt b/docs/howto/custom-file-storage.txt index 51558d9715..090cc089cb 100644 --- a/docs/howto/custom-file-storage.txt +++ b/docs/howto/custom-file-storage.txt @@ -27,7 +27,7 @@ You'll need to follow these steps: option = settings.CUSTOM_STORAGE_OPTIONS ... -#. Your storage class must implement the :meth:`_open()` and :meth:`_save() +#. Your storage class must implement the :meth:`_open()` and :meth:`_save()` methods, along with any other methods appropriate to your storage class. See below for more on these methods. -- 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') 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 ``