From 70f2d9aaffcdd255b1fde6c83b7fe584c4ee3d45 Mon Sep 17 00:00:00 2001
From: Jacob Kaplan-Moss
Date: Fri, 28 Jun 2013 08:15:10 -0500
Subject: [PATCH 001/944] Updated version numbers and setup.py for 1.6beta.
---
django/__init__.py | 2 +-
setup.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/django/__init__.py b/django/__init__.py
index 5a1c74efa780..d0c5d77bfa17 100644
--- a/django/__init__.py
+++ b/django/__init__.py
@@ -1,4 +1,4 @@
-VERSION = (1, 6, 0, 'alpha', 1)
+VERSION = (1, 6, 0, 'beta', 1)
def get_version(*args, **kwargs):
# Don't litter django/__init__.py with all the get_version stuff.
diff --git a/setup.py b/setup.py
index 2c64868d0014..3abbac8ae79d 100644
--- a/setup.py
+++ b/setup.py
@@ -93,7 +93,7 @@ def is_package(package_name):
package_data=package_data,
scripts=['django/bin/django-admin.py'],
classifiers=[
- 'Development Status :: 3 - Alpha',
+ 'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
From ec2a102d8491a708b56abb19caeebdc5562c2a9c Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Fri, 28 Jun 2013 16:27:07 +0200
Subject: [PATCH 002/944] [1.6.x] Updated FAQ entry about python 3
Backport of 94f420ef4 from master.
---
docs/faq/install.txt | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/docs/faq/install.txt b/docs/faq/install.txt
index 5a4cab94cfac..5ca7a471c85c 100644
--- a/docs/faq/install.txt
+++ b/docs/faq/install.txt
@@ -77,15 +77,12 @@ Django version Python versions
Can I use Django with Python 3?
-------------------------------
-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.
+Yes, you can!
-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
-porting your apps for future Python 3 compatibility. But we're not yet
-confident enough to promise stability in production.
+Django 1.5 introduced experimental support for Python 3.2.3 and above.
-Our current plan is to make Django 1.6 suitable for general use with Python 3.
+As of Django 1.6, Python 3 support is considered stable and you can safely use
+it in production. See also :doc:`/topics/python3`.
Will Django run under shared hosting (like TextDrive or Dreamhost)?
-------------------------------------------------------------------
From dc99128343a08dce8a056e50047ea8bc909740d5 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Fri, 28 Jun 2013 16:38:55 +0200
Subject: [PATCH 003/944] [1.6.x] Updated FAQ to reflect official Python 3
support
Backport of 8809da67a from master.
---
docs/faq/install.txt | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/docs/faq/install.txt b/docs/faq/install.txt
index 5ca7a471c85c..d221f93d0226 100644
--- a/docs/faq/install.txt
+++ b/docs/faq/install.txt
@@ -16,9 +16,8 @@ How do I get started?
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.3 and above.
+Django requires Python, specifically Python 2.6.5 - 2.7.x, or 3.2.3 and above.
+No other Python libraries are required for basic Django usage.
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
@@ -43,7 +42,7 @@ Do I lose anything by using Python 2.6 versus newer Python versions, such as Pyt
----------------------------------------------------------------------------------------
Not in the core framework. Currently, Django itself officially supports
-Python 2.6 (2.6.5 or higher) and 2.7. However, newer versions of
+Python 2.6 (2.6.5 or higher), 2.7, 3.2.3 or higher. However, newer versions of
Python are often faster, have more features, and are better supported. If you
use a newer version of Python you will also have access to some APIs that
aren't available under older versions of Python.
@@ -51,12 +50,9 @@ aren't available under older versions of Python.
Third-party applications for use with Django are, of course, free to set their
own version requirements.
-All else being equal, we recommend that you use the latest 2.x release
-(currently Python 2.7). This will let you take advantage of the numerous
-improvements and optimizations to the Python language since version 2.6.
-
-Generally speaking, we don't recommend running Django on Python 3 yet; see
-below for more.
+All else being equal, we recommend that you use the latest 2.7 or 3.x release.
+This will let you take advantage of the numerous improvements and optimizations
+to the Python language since version 2.6.
What Python version can I use with Django?
------------------------------------------
From e628753e7dd1611fb4cf770a78126e96a02ab1d7 Mon Sep 17 00:00:00 2001
From: Simon Charette
Date: Fri, 28 Jun 2013 15:33:20 -0400
Subject: [PATCH 004/944] [1.6.x] Added missing deprecation note for model
permission methods.
refs #20642.
Backport of e1dd24d2f from master.
---
docs/internals/deprecation.txt | 3 +++
1 file changed, 3 insertions(+)
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 9672746717c5..5513c7996608 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -404,6 +404,9 @@ these changes.
forms for ManyToMany model fields will not be performed by Django anymore
either at the model or forms layer.
+* The ``Model._meta.get_(add|change|delete)_permission`` methods will
+ be removed.
+
2.0
---
From 7bd9c32f14a0a56089358f7927a3326e78bee093 Mon Sep 17 00:00:00 2001
From: Florian Apolloner
Date: Sat, 29 Jun 2013 10:50:04 +0200
Subject: [PATCH 005/944] [1.6.x] Fixed 1.6 release notes.
Backport of adc6f38867dd5b57c9e1c50395eec01f6bdd651f from master.
---
docs/releases/1.6.txt | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index 1fd98e127175..4219edb1a0cc 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -41,9 +41,9 @@ Simplified default project and app templates
The default templates used by :djadmin:`startproject` and :djadmin:`startapp`
have been simplified and modernized. The :doc:`admin
` is now enabled by default in new projects; the
-:doc:`sites ` framework no longer is. :ref:`Language
-detection ` and :ref:`clickjacking
-prevention ` are turned on.
+:doc:`sites ` framework no longer is. :ref:`clickjacking
+prevention ` is now on and the database defaults to
+SQLite.
If the default templates don't suit your tastes, you can use :ref:`custom
project and app templates `.
From 6908b6593949a205d05da342060a9d952bd7b77c Mon Sep 17 00:00:00 2001
From: Aymeric Augustin
Date: Sat, 29 Jun 2013 11:40:31 +0200
Subject: [PATCH 006/944] [1.6.x] Removed obsolete comment. Refs #20079.
Thanks Gavin Wahl.
---
django/contrib/auth/models.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index 5fec99a783cc..406852a41646 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -14,7 +14,6 @@
from django.utils import timezone
from django.contrib import auth
-# UNUSABLE_PASSWORD is still imported here for backwards compatibility
from django.contrib.auth.hashers import (
check_password, make_password, is_password_usable)
from django.contrib.auth.signals import user_logged_in
From c55cb6fa4524884c8b881b255ed0bb20f5e0b26a Mon Sep 17 00:00:00 2001
From: Florian Apolloner
Date: Sat, 29 Jun 2013 16:28:05 +0200
Subject: [PATCH 007/944] [1.6.x] Removed comment from setup.cfg which broke
newer wheel versions.
Backport of b5f709e6f4c67020bedb141b9b18c5cd1e05f829 from master.
---
setup.cfg | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.cfg b/setup.cfg
index 330eff69770c..4c25b840b5b3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,4 +6,4 @@ install-script = scripts/rpm-install.sh
license-file = LICENSE
[wheel]
-universal = 1 # use py2.py3 tag for pure-python dist
+universal = 1
From b6aed803b20cc7898a82fd65845e97676276f3fa Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Thu, 27 Jun 2013 10:58:05 +0200
Subject: [PATCH 008/944] [1.6.x] Fixed #20660 -- Do not try to delete an unset
FieldFile
Thanks stanislas.guerra at gmail.com for the report and
Baptiste Mispelon for the review.
Backport of ea3fe78a9d from master.
---
django/db/models/fields/files.py | 2 ++
tests/model_fields/tests.py | 11 +++++++++++
2 files changed, 13 insertions(+)
diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py
index e631f177e92c..3b3c1ec2b658 100644
--- a/django/db/models/fields/files.py
+++ b/django/db/models/fields/files.py
@@ -96,6 +96,8 @@ def save(self, name, content, save=True):
save.alters_data = True
def delete(self, save=True):
+ if not self:
+ return
# Only close the file if it's already open, which we know by the
# presence of self._file
if hasattr(self, '_file'):
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py
index ccff8b8cfa9a..6abeed8c4290 100644
--- a/tests/model_fields/tests.py
+++ b/tests/model_fields/tests.py
@@ -432,6 +432,17 @@ def test_changed(self):
field.save_form_data(d, 'else.txt')
self.assertEqual(d.myfile, 'else.txt')
+ def test_delete_when_file_unset(self):
+ """
+ Calling delete on an unset FileField should not call the file deletion
+ process, but fail silently (#20660).
+ """
+ d = Document()
+ try:
+ d.myfile.delete()
+ except OSError:
+ self.fail("Deleting an unset FileField should not raise OSError.")
+
class BinaryFieldTests(test.TestCase):
binary_data = b'\x00\x46\xFE'
From a9b5a1e506a9e8492407399b8bec1c2a8420be60 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Thu, 27 Jun 2013 10:59:30 +0200
Subject: [PATCH 009/944] [1.6.x] Do not allow FileSystemStorage.delete to
receive an empty name
Refs #20660.
Backport of 7fbab3eba from master.
---
django/core/files/storage.py | 1 +
tests/file_storage/tests.py | 8 ++++++++
2 files changed, 9 insertions(+)
diff --git a/django/core/files/storage.py b/django/core/files/storage.py
index 977b6a68a8b5..5d301a317c5a 100644
--- a/django/core/files/storage.py
+++ b/django/core/files/storage.py
@@ -231,6 +231,7 @@ def _save(self, name, content):
return name
def delete(self, name):
+ assert name, "The name argument is not allowed to be empty."
name = self.path(name)
# If the file exists, delete it from the filesystem.
# Note that there is a race between os.path.exists and os.remove:
diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py
index 6c3c559660bd..e6caabf9d996 100644
--- a/tests/file_storage/tests.py
+++ b/tests/file_storage/tests.py
@@ -364,6 +364,14 @@ def failing_chunks():
with self.assertRaises(IOError):
self.storage.save('error.file', f1)
+ def test_delete_no_name(self):
+ """
+ Calling delete with an empty name should not try to remove the base
+ storage directory, but fail loudly (#20660).
+ """
+ with self.assertRaises(AssertionError):
+ self.storage.delete('')
+
class CustomStorage(FileSystemStorage):
def get_available_name(self, name):
From 59be2c6875eaf744798d14c9dc8498552c8d48d5 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Sat, 29 Jun 2013 18:44:41 +0200
Subject: [PATCH 010/944] [1.6.x] Fixed #18592 -- Prevented crash when
accessing MySQL _last_executed
Thanks reames at asymmetricventures.com for the report.
Backport of 59b0c48ce from master.
---
django/db/backends/mysql/base.py | 2 +-
tests/backends/tests.py | 11 +++++++++++
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index ea687715e4e0..d10be94f4396 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -284,7 +284,7 @@ def last_executed_query(self, cursor, sql, params):
# With MySQLdb, cursor objects have an (undocumented) "_last_executed"
# attribute where the exact query sent to the database is saved.
# See MySQLdb/cursors.py in the source distribution.
- return force_text(cursor._last_executed, errors='replace')
+ return force_text(getattr(cursor, '_last_executed', None), errors='replace')
def no_limit_value(self):
# 2**64 - 1, as recommended by the MySQL documentation
diff --git a/tests/backends/tests.py b/tests/backends/tests.py
index c1a26df7fcfa..488f8d518b01 100644
--- a/tests/backends/tests.py
+++ b/tests/backends/tests.py
@@ -164,6 +164,17 @@ def test_django_date_extract(self):
@override_settings(DEBUG=True)
class LastExecutedQueryTest(TestCase):
+ def test_last_executed_query(self):
+ """
+ last_executed_query should not raise an exception even if no previous
+ query has been run.
+ """
+ cursor = connection.cursor()
+ try:
+ connection.ops.last_executed_query(cursor, '', ())
+ except Exception:
+ self.fail("'last_executed_query' should not raise an exception.")
+
def test_debug_sql(self):
list(models.Reporter.objects.filter(first_name="test"))
sql = connection.queries[-1]['sql'].lower()
From b930733a67cbc1235daf675de06d73e3b6406113 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sat, 29 Jun 2013 14:14:32 -0400
Subject: [PATCH 011/944] [1.6.x] Fixed #20677 - Typos in
generic_inlineformset_factory docs.
Thanks Riley Strong for the report.
Backport of 3fd0ee5b46 from master
---
django/contrib/contenttypes/generic.py | 4 ++--
docs/ref/contrib/contenttypes.txt | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index 26db4ab17110..e14aa5ecee92 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -434,8 +434,8 @@ def generic_inlineformset_factory(model, form=ModelForm,
"""
Returns a ``GenericInlineFormSet`` for the given kwargs.
- You must provide ``ct_field`` and ``object_id`` if they different from the
- defaults ``content_type`` and ``object_id`` respectively.
+ You must provide ``ct_field`` and ``fk_field`` if they are different from
+ the defaults ``content_type`` and ``object_id`` respectively.
"""
opts = model._meta
# if there is no field called `ct_field` let the exception propagate
diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt
index de9c5dcbd6bf..199401c64aa1 100644
--- a/docs/ref/contrib/contenttypes.txt
+++ b/docs/ref/contrib/contenttypes.txt
@@ -506,9 +506,9 @@ information.
Returns a ``GenericInlineFormSet`` using
:func:`~django.forms.models.modelformset_factory`.
- You must provide ``ct_field`` and ``object_id`` if they different from the
- defaults, ``content_type`` and ``object_id`` respectively. Other parameters
- are similar to those documented in
+ You must provide ``ct_field`` and ``fk_field`` if they are different from
+ the defaults, ``content_type`` and ``object_id`` respectively. Other
+ parameters are similar to those documented in
:func:`~django.forms.models.modelformset_factory` and
:func:`~django.forms.models.inlineformset_factory`.
From d2550042894726c5b74a9a2374304b75a4ac30bc Mon Sep 17 00:00:00 2001
From: Aymeric Augustin
Date: Sun, 30 Jun 2013 14:17:33 +0200
Subject: [PATCH 012/944] [1.6.x] Stopped calling loaddata with commit=False.
This was a stealth option only used by the tests, and it isn't useful
any more since `atomic` provides nested transactions.
Backport of 2c40681 from master.
---
tests/fixtures/tests.py | 56 +++++++++++++--------------
tests/fixtures_model_package/tests.py | 7 ++--
tests/fixtures_regress/tests.py | 32 ---------------
tests/proxy_models/tests.py | 2 +-
4 files changed, 31 insertions(+), 66 deletions(-)
diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py
index d0942afdb708..6f2218b19ec5 100644
--- a/tests/fixtures/tests.py
+++ b/tests/fixtures/tests.py
@@ -56,7 +56,7 @@ def test_initial_data(self):
def test_loading_and_dumping(self):
Site.objects.all().delete()
# Load fixture 1. Single JSON file, with two objects.
- management.call_command('loaddata', 'fixture1.json', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture1.json', verbosity=0)
self.assertQuerysetEqual(Article.objects.all(), [
'',
'',
@@ -87,7 +87,7 @@ def test_loading_and_dumping(self):
self._dumpdata_assert(['fixtures.Category', 'sites'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 1, "model": "sites.site", "fields": {"domain": "example.com", "name": "example.com"}}]')
# Load fixture 2. JSON file imported by default. Overwrites some existing objects
- management.call_command('loaddata', 'fixture2.json', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture2.json', verbosity=0)
self.assertQuerysetEqual(Article.objects.all(), [
'',
'',
@@ -95,7 +95,7 @@ def test_loading_and_dumping(self):
])
# Load fixture 3, XML format.
- management.call_command('loaddata', 'fixture3.xml', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture3.xml', verbosity=0)
self.assertQuerysetEqual(Article.objects.all(), [
'',
'',
@@ -104,14 +104,14 @@ def test_loading_and_dumping(self):
])
# Load fixture 6, JSON file with dynamic ContentType fields. Testing ManyToOne.
- management.call_command('loaddata', 'fixture6.json', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture6.json', verbosity=0)
self.assertQuerysetEqual(Tag.objects.all(), [
' tagged "copyright">',
' tagged "law">',
], ordered=False)
# Load fixture 7, XML file with dynamic ContentType fields. Testing ManyToOne.
- management.call_command('loaddata', 'fixture7.xml', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture7.xml', verbosity=0)
self.assertQuerysetEqual(Tag.objects.all(), [
' tagged "copyright">',
' tagged "legal">',
@@ -120,7 +120,7 @@ def test_loading_and_dumping(self):
], ordered=False)
# Load fixture 8, JSON file with dynamic Permission fields. Testing ManyToMany.
- management.call_command('loaddata', 'fixture8.json', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture8.json', verbosity=0)
self.assertQuerysetEqual(Visa.objects.all(), [
'',
'',
@@ -128,7 +128,7 @@ def test_loading_and_dumping(self):
], ordered=False)
# Load fixture 9, XML file with dynamic Permission fields. Testing ManyToMany.
- management.call_command('loaddata', 'fixture9.xml', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture9.xml', verbosity=0)
self.assertQuerysetEqual(Visa.objects.all(), [
'',
'',
@@ -143,15 +143,13 @@ def test_loading_and_dumping(self):
# Loading a fixture that doesn't exist emits a warning
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
- management.call_command('loaddata', 'unknown.json', verbosity=0,
- commit=False)
+ management.call_command('loaddata', 'unknown.json', verbosity=0)
self.assertEqual(len(w), 1)
self.assertTrue(w[0].message, "No fixture named 'unknown' found.")
# An attempt to load a nonexistent 'initial_data' fixture isn't an error
with warnings.catch_warnings(record=True) as w:
- management.call_command('loaddata', 'initial_data.json', verbosity=0,
- commit=False)
+ management.call_command('loaddata', 'initial_data.json', verbosity=0)
self.assertEqual(len(w), 0)
# object list is unaffected
@@ -178,7 +176,7 @@ def test_loading_and_dumping(self):
def test_dumpdata_with_excludes(self):
# Load fixture1 which has a site, two articles, and a category
Site.objects.all().delete()
- management.call_command('loaddata', 'fixture1.json', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture1.json', verbosity=0)
# Excluding fixtures app should only leave sites
self._dumpdata_assert(
@@ -226,8 +224,8 @@ def test_dumpdata_with_filtering_manager(self):
self._dumpdata_assert(['fixtures.Spy'], '[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": true}}, {"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % (spy2.pk, spy1.pk), use_base_manager=True)
def test_dumpdata_with_pks(self):
- management.call_command('loaddata', 'fixture1.json', verbosity=0, commit=False)
- management.call_command('loaddata', 'fixture2.json', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture1.json', verbosity=0)
+ management.call_command('loaddata', 'fixture2.json', verbosity=0)
self._dumpdata_assert(
['fixtures.Article'],
'[{"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Copyright is fine the way it is", "pub_date": "2006-06-16T14:00:00"}}]',
@@ -266,21 +264,21 @@ def test_dumpdata_with_pks(self):
def test_compress_format_loading(self):
# Load fixture 4 (compressed), using format specification
- management.call_command('loaddata', 'fixture4.json', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture4.json', verbosity=0)
self.assertQuerysetEqual(Article.objects.all(), [
'',
])
def test_compressed_specified_loading(self):
# Load fixture 5 (compressed), using format *and* compression specification
- management.call_command('loaddata', 'fixture5.json.zip', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture5.json.zip', verbosity=0)
self.assertQuerysetEqual(Article.objects.all(), [
'',
])
def test_compressed_loading(self):
# Load fixture 5 (compressed), only compression specification
- management.call_command('loaddata', 'fixture5.zip', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture5.zip', verbosity=0)
self.assertQuerysetEqual(Article.objects.all(), [
'',
])
@@ -288,13 +286,13 @@ def test_compressed_loading(self):
def test_ambiguous_compressed_fixture(self):
# The name "fixture5" is ambigous, so loading it will raise an error
with self.assertRaises(management.CommandError) as cm:
- management.call_command('loaddata', 'fixture5', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture5', verbosity=0)
self.assertIn("Multiple fixtures named 'fixture5'", cm.exception.args[0])
def test_db_loading(self):
# Load db fixtures 1 and 2. These will load using the 'default' database identifier implicitly
- management.call_command('loaddata', 'db_fixture_1', verbosity=0, commit=False)
- management.call_command('loaddata', 'db_fixture_2', verbosity=0, commit=False)
+ management.call_command('loaddata', 'db_fixture_1', verbosity=0)
+ management.call_command('loaddata', 'db_fixture_2', verbosity=0)
self.assertQuerysetEqual(Article.objects.all(), [
'',
'',
@@ -312,13 +310,13 @@ def test_loaddata_error_message(self):
if connection.vendor == 'mysql':
connection.cursor().execute("SET sql_mode = 'TRADITIONAL'")
with self.assertRaises(IntegrityError) as cm:
- management.call_command('loaddata', 'invalid.json', verbosity=0, commit=False)
+ management.call_command('loaddata', 'invalid.json', verbosity=0)
self.assertIn("Could not load fixtures.Article(pk=1):", cm.exception.args[0])
def test_loading_using(self):
# Load db fixtures 1 and 2. These will load using the 'default' database identifier explicitly
- management.call_command('loaddata', 'db_fixture_1', verbosity=0, using='default', commit=False)
- management.call_command('loaddata', 'db_fixture_2', verbosity=0, using='default', commit=False)
+ management.call_command('loaddata', 'db_fixture_1', verbosity=0, using='default')
+ management.call_command('loaddata', 'db_fixture_2', verbosity=0, using='default')
self.assertQuerysetEqual(Article.objects.all(), [
'',
'',
@@ -327,18 +325,18 @@ def test_loading_using(self):
def test_unmatched_identifier_loading(self):
# Try to load db fixture 3. This won't load because the database identifier doesn't match
with warnings.catch_warnings(record=True):
- management.call_command('loaddata', 'db_fixture_3', verbosity=0, commit=False)
+ management.call_command('loaddata', 'db_fixture_3', verbosity=0)
with warnings.catch_warnings(record=True):
- management.call_command('loaddata', 'db_fixture_3', verbosity=0, using='default', commit=False)
+ management.call_command('loaddata', 'db_fixture_3', verbosity=0, using='default')
self.assertQuerysetEqual(Article.objects.all(), [])
def test_output_formats(self):
# Load back in fixture 1, we need the articles from it
- management.call_command('loaddata', 'fixture1', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture1', verbosity=0)
# Try to load fixture 6 using format discovery
- management.call_command('loaddata', 'fixture6', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture6', verbosity=0)
self.assertQuerysetEqual(Tag.objects.all(), [
' tagged "copyright">',
' tagged "law">'
@@ -364,7 +362,7 @@ class FixtureTransactionTests(DumpDataAssertMixin, TransactionTestCase):
@skipUnlessDBFeature('supports_forward_references')
def test_format_discovery(self):
# Load fixture 1 again, using format discovery
- management.call_command('loaddata', 'fixture1', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture1', verbosity=0)
self.assertQuerysetEqual(Article.objects.all(), [
'',
'',
@@ -386,7 +384,7 @@ def test_format_discovery(self):
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]')
# Load fixture 4 (compressed), using format discovery
- management.call_command('loaddata', 'fixture4', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture4', verbosity=0)
self.assertQuerysetEqual(Article.objects.all(), [
'',
'',
diff --git a/tests/fixtures_model_package/tests.py b/tests/fixtures_model_package/tests.py
index dbcc721d8fce..4b00e6dfb9aa 100644
--- a/tests/fixtures_model_package/tests.py
+++ b/tests/fixtures_model_package/tests.py
@@ -60,7 +60,6 @@ def test_flush(self):
'flush',
verbosity=0,
interactive=False,
- commit=False,
load_initial_data=False
)
self.assertQuerysetEqual(Book.objects.all(), [])
@@ -83,7 +82,7 @@ def test_initial_data(self):
def test_loaddata(self):
"Fixtures can load data into models defined in packages"
# Load fixture 1. Single JSON file, with two objects
- management.call_command("loaddata", "fixture1.json", verbosity=0, commit=False)
+ management.call_command("loaddata", "fixture1.json", verbosity=0)
self.assertQuerysetEqual(
Article.objects.all(), [
"Time to reform copyright",
@@ -94,7 +93,7 @@ def test_loaddata(self):
# Load fixture 2. JSON file imported by default. Overwrites some
# existing objects
- management.call_command("loaddata", "fixture2.json", verbosity=0, commit=False)
+ management.call_command("loaddata", "fixture2.json", verbosity=0)
self.assertQuerysetEqual(
Article.objects.all(), [
"Django conquers world!",
@@ -107,7 +106,7 @@ def test_loaddata(self):
# Load a fixture that doesn't exist
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
- management.call_command("loaddata", "unknown.json", verbosity=0, commit=False)
+ management.call_command("loaddata", "unknown.json", verbosity=0)
self.assertEqual(len(w), 1)
self.assertTrue(w[0].message, "No fixture named 'unknown' found.")
diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py
index 0f6ac659765a..a9d67ec9a239 100644
--- a/tests/fixtures_regress/tests.py
+++ b/tests/fixtures_regress/tests.py
@@ -45,7 +45,6 @@ def test_duplicate_pk(self):
'loaddata',
'sequence',
verbosity=0,
- commit=False
)
# Create a new animal. Without a sequence reset, this new object
@@ -84,7 +83,6 @@ def test_loaddata_not_found_fields_ignore(self):
'sequence_extra',
ignore=True,
verbosity=0,
- commit=False
)
self.assertEqual(Animal.specimens.all()[0].name, 'Lion')
@@ -98,7 +96,6 @@ def test_loaddata_not_found_fields_ignore_xml(self):
'sequence_extra_xml',
ignore=True,
verbosity=0,
- commit=False
)
self.assertEqual(Animal.specimens.all()[0].name, 'Wolf')
@@ -113,7 +110,6 @@ def test_pretty_print_xml(self):
'loaddata',
'pretty.xml',
verbosity=0,
- commit=False
)
self.assertEqual(Stuff.objects.all()[0].name, None)
self.assertEqual(Stuff.objects.all()[0].owner, None)
@@ -129,7 +125,6 @@ def test_pretty_print_xml_empty_strings(self):
'loaddata',
'pretty.xml',
verbosity=0,
- commit=False
)
self.assertEqual(Stuff.objects.all()[0].name, '')
self.assertEqual(Stuff.objects.all()[0].owner, None)
@@ -152,7 +147,6 @@ def test_absolute_path(self):
'loaddata',
load_absolute_path,
verbosity=0,
- commit=False
)
self.assertEqual(Absolute.load_count, 1)
@@ -168,7 +162,6 @@ def test_unknown_format(self):
'loaddata',
'bad_fixture1.unkn',
verbosity=0,
- commit=False,
)
@override_settings(SERIALIZATION_MODULES={'unkn': 'unexistent.path'})
@@ -182,7 +175,6 @@ def test_unimportable_serializer(self):
'loaddata',
'bad_fixture1.unkn',
verbosity=0,
- commit=False,
)
def test_invalid_data(self):
@@ -197,7 +189,6 @@ def test_invalid_data(self):
'loaddata',
'bad_fixture2.xml',
verbosity=0,
- commit=False,
)
def test_invalid_data_no_ext(self):
@@ -212,7 +203,6 @@ def test_invalid_data_no_ext(self):
'loaddata',
'bad_fixture2',
verbosity=0,
- commit=False,
)
def test_empty(self):
@@ -226,7 +216,6 @@ def test_empty(self):
'loaddata',
'empty',
verbosity=0,
- commit=False,
)
def test_error_message(self):
@@ -240,7 +229,6 @@ def test_error_message(self):
'bad_fixture2',
'animal',
verbosity=0,
- commit=False,
)
def test_pg_sequence_resetting_checks(self):
@@ -253,7 +241,6 @@ def test_pg_sequence_resetting_checks(self):
'loaddata',
'model-inheritance.json',
verbosity=0,
- commit=False
)
self.assertEqual(Parent.objects.all()[0].id, 1)
self.assertEqual(Child.objects.all()[0].id, 1)
@@ -270,7 +257,6 @@ def test_close_connection_after_loaddata(self):
'loaddata',
'big-fixture.json',
verbosity=0,
- commit=False
)
articles = Article.objects.exclude(id=9)
self.assertEqual(
@@ -297,7 +283,6 @@ def test_field_value_coerce(self):
'loaddata',
'animal.xml',
verbosity=0,
- commit=False,
)
self.assertEqual(
self.pre_save_checks,
@@ -319,13 +304,11 @@ def test_dumpdata_uses_default_manager(self):
'loaddata',
'animal.xml',
verbosity=0,
- commit=False,
)
management.call_command(
'loaddata',
'sequence.json',
verbosity=0,
- commit=False,
)
animal = Animal(
name='Platypus',
@@ -390,7 +373,6 @@ def test_loaddata_works_when_fixture_has_forward_refs(self):
'loaddata',
'forward_ref.json',
verbosity=0,
- commit=False
)
self.assertEqual(Book.objects.all()[0].id, 1)
self.assertEqual(Person.objects.all()[0].id, 4)
@@ -405,7 +387,6 @@ def test_loaddata_raises_error_when_fixture_has_invalid_foreign_key(self):
'loaddata',
'forward_ref_bad_data.json',
verbosity=0,
- commit=False,
)
_cur_dir = os.path.dirname(os.path.abspath(upath(__file__)))
@@ -422,7 +403,6 @@ def test_loaddata_forward_refs_split_fixtures(self):
'forward_ref_1.json',
'forward_ref_2.json',
verbosity=0,
- commit=False
)
self.assertEqual(Book.objects.all()[0].id, 1)
self.assertEqual(Person.objects.all()[0].id, 4)
@@ -437,7 +417,6 @@ def test_loaddata_no_fixture_specified(self):
management.call_command(
'loaddata',
verbosity=0,
- commit=False,
)
def test_loaddata_not_existant_fixture_file(self):
@@ -447,7 +426,6 @@ def test_loaddata_not_existant_fixture_file(self):
'loaddata',
'this_fixture_doesnt_exist',
verbosity=2,
- commit=False,
stdout=stdout_output,
)
self.assertTrue("No fixture 'this_fixture_doesnt_exist' in" in
@@ -465,13 +443,11 @@ def test_nk_deserialize(self):
'loaddata',
'model-inheritance.json',
verbosity=0,
- commit=False
)
management.call_command(
'loaddata',
'nk-inheritance.json',
verbosity=0,
- commit=False
)
self.assertEqual(
NKChild.objects.get(pk=1).data,
@@ -492,19 +468,16 @@ def test_nk_deserialize_xml(self):
'loaddata',
'model-inheritance.json',
verbosity=0,
- commit=False
)
management.call_command(
'loaddata',
'nk-inheritance.json',
verbosity=0,
- commit=False
)
management.call_command(
'loaddata',
'nk-inheritance2.xml',
verbosity=0,
- commit=False
)
self.assertEqual(
NKChild.objects.get(pk=2).data,
@@ -524,7 +497,6 @@ def test_nk_on_serialize(self):
'loaddata',
'forward_ref_lookup.json',
verbosity=0,
- commit=False
)
stdout = StringIO()
@@ -662,19 +634,16 @@ def test_normal_pk(self):
'loaddata',
'non_natural_1.json',
verbosity=0,
- commit=False
)
management.call_command(
'loaddata',
'forward_ref_lookup.json',
verbosity=0,
- commit=False
)
management.call_command(
'loaddata',
'non_natural_2.xml',
verbosity=0,
- commit=False
)
books = Book.objects.all()
self.assertEqual(
@@ -696,7 +665,6 @@ def ticket_11101(self):
'loaddata',
'thingy.json',
verbosity=0,
- commit=False
)
self.assertEqual(Thingy.objects.count(), 1)
transaction.rollback()
diff --git a/tests/proxy_models/tests.py b/tests/proxy_models/tests.py
index 77d2ba9a7429..5cc5ef54789e 100644
--- a/tests/proxy_models/tests.py
+++ b/tests/proxy_models/tests.py
@@ -358,7 +358,7 @@ def test_proxy_bug(self):
)
def test_proxy_load_from_fixture(self):
- management.call_command('loaddata', 'mypeople.json', verbosity=0, commit=False)
+ management.call_command('loaddata', 'mypeople.json', verbosity=0)
p = MyPerson.objects.get(pk=100)
self.assertEqual(p.name, 'Elvis Presley')
From 02976a46c926880e78eb64424a9c8aa28d8be48a Mon Sep 17 00:00:00 2001
From: Aymeric Augustin
Date: Sun, 30 Jun 2013 15:57:00 +0200
Subject: [PATCH 013/944] [1.6.x] Introduced getters for connection.autocommit
and .needs_rollback.
They ensure that the attributes aren't accessed in conditions where they
don't contain a valid value.
Fixed #20666.
Backport of dd9c6bc359a799fcbed647055b596239956a472a from master.
---
django/db/backends/__init__.py | 34 +++++++++++++++++++++++-----------
django/db/transaction.py | 12 ++++--------
2 files changed, 27 insertions(+), 19 deletions(-)
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 9abb9a9637e7..a1fd923faf7e 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -204,7 +204,7 @@ def _savepoint_commit(self, sid):
def _savepoint_allowed(self):
# Savepoints cannot be created outside a transaction
- return self.features.uses_savepoints and not self.autocommit
+ return self.features.uses_savepoints and not self.get_autocommit()
##### Generic savepoint management methods #####
@@ -279,15 +279,13 @@ def enter_transaction_management(self, managed=True, forced=False):
"""
self.validate_no_atomic_block()
- self.ensure_connection()
-
self.transaction_state.append(managed)
if not managed and self.is_dirty() and not forced:
self.commit()
self.set_clean()
- if managed == self.autocommit:
+ if managed == self.get_autocommit():
self.set_autocommit(not managed)
def leave_transaction_management(self):
@@ -298,8 +296,6 @@ def leave_transaction_management(self):
"""
self.validate_no_atomic_block()
- self.ensure_connection()
-
if self.transaction_state:
del self.transaction_state[-1]
else:
@@ -313,14 +309,21 @@ def leave_transaction_management(self):
if self._dirty:
self.rollback()
- if managed == self.autocommit:
+ if managed == self.get_autocommit():
self.set_autocommit(not managed)
raise TransactionManagementError(
"Transaction managed block ended with pending COMMIT/ROLLBACK")
- if managed == self.autocommit:
+ if managed == self.get_autocommit():
self.set_autocommit(not managed)
+ def get_autocommit(self):
+ """
+ Check the autocommit state.
+ """
+ self.ensure_connection()
+ return self.autocommit
+
def set_autocommit(self, autocommit):
"""
Enable or disable autocommit.
@@ -330,13 +333,22 @@ def set_autocommit(self, autocommit):
self._set_autocommit(autocommit)
self.autocommit = autocommit
+ def get_rollback(self):
+ """
+ Get the "needs rollback" flag -- for *advanced use* only.
+ """
+ if not self.in_atomic_block:
+ raise TransactionManagementError(
+ "The rollback flag doesn't work outside of an 'atomic' block.")
+ return self.needs_rollback
+
def set_rollback(self, rollback):
"""
Set or unset the "needs rollback" flag -- for *advanced use* only.
"""
if not self.in_atomic_block:
raise TransactionManagementError(
- "needs_rollback doesn't work outside of an 'atomic' block.")
+ "The rollback flag doesn't work outside of an 'atomic' block.")
self.needs_rollback = rollback
def validate_no_atomic_block(self):
@@ -370,7 +382,7 @@ def set_dirty(self):
to decide in a managed block of code to decide whether there are open
changes waiting for commit.
"""
- if not self.autocommit:
+ if not self.get_autocommit():
self._dirty = True
def set_clean(self):
@@ -436,7 +448,7 @@ def close_if_unusable_or_obsolete(self):
if self.connection is not None:
# If the application didn't restore the original autocommit setting,
# don't take chances, drop the connection.
- if self.autocommit != self.settings_dict['AUTOCOMMIT']:
+ if self.get_autocommit() != self.settings_dict['AUTOCOMMIT']:
self.close()
return
diff --git a/django/db/transaction.py b/django/db/transaction.py
index 95b9ae165e29..5f60e6807f8c 100644
--- a/django/db/transaction.py
+++ b/django/db/transaction.py
@@ -123,7 +123,7 @@ def get_autocommit(using=None):
"""
Get the autocommit status of the connection.
"""
- return get_connection(using).autocommit
+ return get_connection(using).get_autocommit()
def set_autocommit(autocommit, using=None):
"""
@@ -175,7 +175,7 @@ def get_rollback(using=None):
"""
Gets the "needs rollback" flag -- for *advanced use* only.
"""
- return get_connection(using).needs_rollback
+ return get_connection(using).get_rollback()
def set_rollback(rollback, using=None):
"""
@@ -229,15 +229,11 @@ def __init__(self, using, savepoint):
def __enter__(self):
connection = get_connection(self.using)
- # Ensure we have a connection to the database before testing
- # autocommit status.
- connection.ensure_connection()
-
if not connection.in_atomic_block:
# Reset state when entering an outermost atomic block.
connection.commit_on_exit = True
connection.needs_rollback = False
- if not connection.autocommit:
+ if not connection.get_autocommit():
# Some database adapters (namely sqlite3) don't handle
# transactions and savepoints properly when autocommit is off.
# Turning autocommit back on isn't an option; it would trigger
@@ -500,7 +496,7 @@ def commit_on_success_unless_managed(using=None, savepoint=False):
legacy behavior.
"""
connection = get_connection(using)
- if connection.autocommit or connection.in_atomic_block:
+ if connection.get_autocommit() or connection.in_atomic_block:
return atomic(using, savepoint)
else:
def entering(using):
From 3493f18d7850236dcb6292ebb1b949d6aeed7a9c Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sun, 30 Jun 2013 14:48:15 -0400
Subject: [PATCH 014/944] [1.6.x] Fixed #20667 - Removed discussion of DEBUG
from tutorial.
Backport of 0d642aac86 from master.
---
docs/howto/deployment/checklist.txt | 15 ++++++++++
docs/intro/tutorial03.txt | 45 -----------------------------
docs/intro/whatsnext.txt | 5 ++++
docs/topics/http/views.txt | 14 ++++-----
4 files changed, 27 insertions(+), 52 deletions(-)
diff --git a/docs/howto/deployment/checklist.txt b/docs/howto/deployment/checklist.txt
index 1a235673e869..4ec0c266f898 100644
--- a/docs/howto/deployment/checklist.txt
+++ b/docs/howto/deployment/checklist.txt
@@ -206,6 +206,21 @@ See :doc:`/howto/error-reporting` for details on error reporting by email.
.. _Sentry: http://sentry.readthedocs.org/en/latest/
+Customize the default error views
+---------------------------------
+
+Django includes default views and templates for several HTTP error codes. You
+may want to override the default templates by creating the following templates
+in your root template directory: ``404.html``, ``500.html``, ``403.html``, and
+``400.html``. The default views should suffice for 99% of Web applications, but
+if you desire to customize them, see these instructions which also contain
+details about the default templates:
+
+* :ref:`http_not_found_view`
+* :ref:`http_internal_server_error_view`
+* :ref:`http_forbidden_view`
+* :ref:`http_bad_request_view`
+
Miscellaneous
=============
diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt
index 91409848cf24..777b8ef23bee 100644
--- a/docs/intro/tutorial03.txt
+++ b/docs/intro/tutorial03.txt
@@ -454,51 +454,6 @@ just as :func:`~django.shortcuts.get_object_or_404` -- except using
:meth:`~django.db.models.query.QuerySet.get`. It raises
:exc:`~django.http.Http404` if the list is empty.
-Write a 404 (page not found) view
-=================================
-
-When you raise :exc:`~django.http.Http404` from within a view, Django
-will load a special view devoted to handling 404 errors. It finds it
-by looking for the variable ``handler404`` in your root URLconf (and
-only in your root URLconf; setting ``handler404`` anywhere else will
-have no effect), which is a string in Python dotted syntax -- the same
-format the normal URLconf callbacks use. A 404 view itself has nothing
-special: It's just a normal view.
-
-You normally won't have to bother with writing 404 views. If you don't set
-``handler404``, the built-in view :func:`django.views.defaults.page_not_found`
-is used by default. Optionally, you can create a ``404.html`` template
-in the root of your template directory. The default 404 view will then use that
-template for all 404 errors when :setting:`DEBUG` is set to ``False`` (in your
-settings module). If you do create the template, add at least some dummy
-content like "Page not found".
-
-.. warning::
-
- If :setting:`DEBUG` is set to ``False``, all responses will be
- "Bad Request (400)" unless you specify the proper :setting:`ALLOWED_HOSTS`
- as well (something like ``['localhost', '127.0.0.1']`` for
- local development).
-
-A couple more things to note about 404 views:
-
-* If :setting:`DEBUG` is set to ``True`` (in your settings module) then your
- 404 view will never be used (and thus the ``404.html`` template will never
- be rendered) because the traceback will be displayed instead.
-
-* The 404 view is also called if Django doesn't find a match after checking
- every regular expression in the URLconf.
-
-Write a 500 (server error) view
-===============================
-
-Similarly, your root URLconf may define a ``handler500``, which points
-to a view to call in case of server errors. Server errors happen when
-you have runtime errors in view code.
-
-Likewise, you should create a ``500.html`` template at the root of your
-template directory and add some content like "Something went wrong".
-
Use the template system
=======================
diff --git a/docs/intro/whatsnext.txt b/docs/intro/whatsnext.txt
index a677bc9efd17..638d219afe85 100644
--- a/docs/intro/whatsnext.txt
+++ b/docs/intro/whatsnext.txt
@@ -66,6 +66,11 @@ different needs:
where you'll turn to find the details of a particular function or
whathaveyou.
+* If you are interested in deploying a project for public use, our docs have
+ :doc:`several guides` for various deployment
+ setups as well as a :doc:`deployment checklist`
+ for some things you'll need to think about.
+
* Finally, there's some "specialized" documentation not usually relevant to
most developers. This includes the :doc:`release notes ` and
:doc:`internals documentation ` for those who want to add
diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt
index 5c27c9c95852..ebe5302ef8f1 100644
--- a/docs/topics/http/views.txt
+++ b/docs/topics/http/views.txt
@@ -140,18 +140,18 @@ The 404 (page not found) view
.. function:: django.views.defaults.page_not_found(request, template_name='404.html')
-When you raise an ``Http404`` exception, Django loads a special view devoted
-to handling 404 errors. By default, it's the view
-``django.views.defaults.page_not_found``, which either produces a very simple
-"Not Found" message or loads and renders the template ``404.html`` if you
-created it in your root template directory.
+When you raise :exc:`~django.http.Http404` from within a view, Django loads a
+special view devoted to handling 404 errors. By default, it's the view
+:func:`django.views.defaults.page_not_found`, which either produces a very
+simple "Not Found" message or loads and renders the template ``404.html`` if
+you created it in your root template directory.
The default 404 view will pass one variable to the template: ``request_path``,
which is the URL that resulted in the error.
The ``page_not_found`` view should suffice for 99% of Web applications, but if
-you want to override it, you can specify ``handler404`` in your URLconf, like
-so::
+you want to override it, you can specify ``handler404`` in your root URLconf
+(setting ``handler404`` anywhere else will have no effect), like so::
handler404 = 'mysite.views.my_custom_404_view'
From c1d8f3b2078db05bcc66b664c1d17b7799342bd5 Mon Sep 17 00:00:00 2001
From: Aymeric Augustin
Date: Mon, 1 Jul 2013 11:44:59 +0200
Subject: [PATCH 015/944] Updated FAQ on Python versions to explain 2 vs 3.
Required the latest version for each Python series to minimize
bookkeeping in the future.
---
docs/faq/install.txt | 49 ++++++++++++++++++++++----------------------
1 file changed, 24 insertions(+), 25 deletions(-)
diff --git a/docs/faq/install.txt b/docs/faq/install.txt
index d221f93d0226..2c7c35299bbf 100644
--- a/docs/faq/install.txt
+++ b/docs/faq/install.txt
@@ -38,22 +38,6 @@ PostgreSQL fans, and MySQL_, `SQLite 3`_, and Oracle_ are also supported.
.. _`SQLite 3`: http://www.sqlite.org/
.. _Oracle: http://www.oracle.com/
-Do I lose anything by using Python 2.6 versus newer Python versions, such as Python 2.7?
-----------------------------------------------------------------------------------------
-
-Not in the core framework. Currently, Django itself officially supports
-Python 2.6 (2.6.5 or higher), 2.7, 3.2.3 or higher. However, newer versions of
-Python are often faster, have more features, and are better supported. If you
-use a newer version of Python you will also have access to some APIs that
-aren't available under older versions of Python.
-
-Third-party applications for use with Django are, of course, free to set their
-own version requirements.
-
-All else being equal, we recommend that you use the latest 2.7 or 3.x release.
-This will let you take advantage of the numerous improvements and optimizations
-to the Python language since version 2.6.
-
What Python version can I use with Django?
------------------------------------------
@@ -65,20 +49,35 @@ 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 2.6.5, 2.7 and 3.2.3, 3.3 (experimental)
-**1.6** **2.6.5, 2.7** and **3.2.3, 3.3**
-*1.7 (future)* *2.7, 3.3 (to be confirmed)*
+1.5 2.6, 2.7 and 3.2, 3.3 (experimental)
+**1.6** **2.6, 2.7** and **3.2, 3.3**
+*1.7 (future)* *2.7* and *3.2, 3.3*
============== ===============
-Can I use Django with Python 3?
--------------------------------
+For a given series of Python versions, only the latest release is officially
+supported. For instance, at the time of writing (July 1st, 2013), the latest
+release in the 2.7 series is 2.7.5.
-Yes, you can!
-
-Django 1.5 introduced experimental support for Python 3.2.3 and above.
+What Python version should I use with Django?
+---------------------------------------------
As of Django 1.6, Python 3 support is considered stable and you can safely use
-it in production. See also :doc:`/topics/python3`.
+it in production. See also :doc:`/topics/python3`. However, the community is
+still in the process of migrating third-party packages and applications to
+Python 3.
+
+If you're starting a new project, and the dependencies you plan to use work on
+Python 3, you should use Python 3. If they don't, consider contributing to the
+porting efforts, or stick to Python 2.
+
+Since newer versions of Python are often faster, have more features, and are
+better supported, all else being equal, we recommend that you use the latest
+2.x.y or 3.x.y release.
+
+You don't lose anything in Django by using an older release, but you don't take
+advantage of the improvements and optimizations in newer Python releases.
+Third-party applications for use with Django are, of course, free to set their
+own version requirements.
Will Django run under shared hosting (like TextDrive or Dreamhost)?
-------------------------------------------------------------------
From 0de21a6a7a6c651382bae50f13468316e40471b5 Mon Sep 17 00:00:00 2001
From: Aymeric Augustin
Date: Mon, 1 Jul 2013 11:52:00 +0200
Subject: [PATCH 016/944] Stopped branding Python 3 support as experimental.
---
docs/intro/install.txt | 5 ++---
docs/topics/install.txt | 6 ++----
2 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/docs/intro/install.txt b/docs/intro/install.txt
index 3cbc8d88ab26..40375e5eca3d 100644
--- a/docs/intro/install.txt
+++ b/docs/intro/install.txt
@@ -9,9 +9,8 @@ that'll work while you walk through the introduction.
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. All these versions of Python include a lightweight database
+Being a Python Web framework, Django requires Python. It works with Python 2.6,
+2.7, 3.2 or 3.3. All these versions of Python include a lightweight database
called SQLite_ so you won't need to set up a database just yet.
.. _sqlite: http://sqlite.org/
diff --git a/docs/topics/install.txt b/docs/topics/install.txt
index 1c99dc5d5ad8..505d51a846db 100644
--- a/docs/topics/install.txt
+++ b/docs/topics/install.txt
@@ -7,10 +7,8 @@ This document will get you up and running with Django.
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 from 3.2.3 to 3.3.
+Being a Python Web framework, Django requires Python. It works with Python 2.6,
+2.7, 3.2 or 3.3.
Get Python at http://www.python.org. If you're running Linux or Mac OS X, you
probably already have it installed.
From e03a88ba217006fbd7618e3f836c2f6210638aaf Mon Sep 17 00:00:00 2001
From: Baptiste Mispelon
Date: Mon, 1 Jul 2013 14:05:49 +0200
Subject: [PATCH 017/944] [1.6.x] Fixed #20659 -- Fixed PublisherDetail in CBV
topic documentation.
Backport of 88de53d4a86548016f245a1413b856aa334bc737 from master.
---
docs/topics/class-based-views/mixins.txt | 25 ++++++++++++------------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt
index 84d741723369..df1a5505a5b3 100644
--- a/docs/topics/class-based-views/mixins.txt
+++ b/docs/topics/class-based-views/mixins.txt
@@ -286,18 +286,18 @@ One way to do this is to combine :class:`ListView` with
for the paginated list of books can hang off the publisher found as the single
object. In order to do this, we need to have two different querysets:
-``Publisher`` queryset for use in
- :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()`
- We'll set the ``model`` attribute on the view and rely on the default
- implementation of ``get_object()`` to fetch the correct ``Publisher``
- object.
-
``Book`` queryset for use by :class:`~django.views.generic.list.ListView`
- The default implementation of ``get_queryset()`` uses the ``model`` attribute
- to construct the queryset. This conflicts with our use of this attribute
- for ``get_object()`` so we'll override that method and have it return
- the queryset of ``Book`` objects linked to the ``Publisher`` we're looking
- at.
+ Since we have access to the ``Publisher`` whose books we want to list, we
+ simply override ``get_queryset()`` and use the ``Publisher``'s
+ :ref:`reverse foreign key manager`.
+
+``Publisher`` queryset for use in :meth:`~django.views.generic.detail.SingleObjectMixin.get_object()`
+ We'll rely on the default implementation of ``get_object()`` to fetch the
+ correct ``Publisher`` object.
+ However, we need to explicitly pass a ``queryset`` argument because
+ otherwise the default implementation of ``get_object()`` would call
+ ``get_queryset()`` which we have overridden to return ``Book`` objects
+ instead of ``Publisher`` ones.
.. note::
@@ -317,12 +317,11 @@ Now we can write a new ``PublisherDetail``::
from books.models import Publisher
class PublisherDetail(SingleObjectMixin, ListView):
- model = Publisher # for SingleObjectMixin.get_object
paginate_by = 2
template_name = "books/publisher_detail.html"
def get(self, request, *args, **kwargs):
- self.object = self.get_object()
+ self.object = self.get_object(queryset=Publisher.objects.all())
return super(PublisherDetail, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
From 3c51962cabc9537221b86c667aac5ffaa1469660 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Mon, 1 Jul 2013 09:19:55 -0400
Subject: [PATCH 018/944] [1.6.x] Updated tests for deprecation of
Option.get_(add|change|delete)_permission.
refs #20642.
Backport of a6a905c619 from master.
---
django/contrib/admin/util.py | 3 ++-
tests/admin_views/tests.py | 15 ++++++++-------
2 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
index a53cd367b462..dd9047c42824 100644
--- a/django/contrib/admin/util.py
+++ b/django/contrib/admin/util.py
@@ -3,6 +3,7 @@
import datetime
import decimal
+from django.contrib.auth import get_permission_codename
from django.db import models
from django.db.models.constants import LOOKUP_SEP
from django.db.models.deletion import Collector
@@ -119,7 +120,7 @@ def format_callback(obj):
opts.model_name),
None, (quote(obj._get_pk_val()),))
p = '%s.%s' % (opts.app_label,
- opts.get_delete_permission())
+ get_permission_codename('delete', opts))
if not user.has_perm(p):
perms_needed.add(opts.verbose_name)
# Display a link to the admin page.
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 235fe0cb54b9..1d63163771f5 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -15,6 +15,7 @@
from django.core.urlresolvers import reverse
# Register auth models with the admin.
from django.contrib import admin
+from django.contrib.auth import get_permission_codename
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
from django.contrib.admin.models import LogEntry, DELETION
from django.contrib.admin.sites import LOGIN_FORM_KEY
@@ -854,20 +855,20 @@ def setUp(self):
# User who can add Articles
add_user = User.objects.get(username='adduser')
add_user.user_permissions.add(get_perm(Article,
- opts.get_add_permission()))
+ get_permission_codename('add', opts)))
# User who can change Articles
change_user = User.objects.get(username='changeuser')
change_user.user_permissions.add(get_perm(Article,
- opts.get_change_permission()))
+ get_permission_codename('change', opts)))
# User who can delete Articles
delete_user = User.objects.get(username='deleteuser')
delete_user.user_permissions.add(get_perm(Article,
- opts.get_delete_permission()))
+ get_permission_codename('delete', opts)))
delete_user.user_permissions.add(get_perm(Section,
- Section._meta.get_delete_permission()))
+ get_permission_codename('delete', Section._meta)))
# login POST dicts
self.super_login = {
@@ -1210,7 +1211,7 @@ def testConditionallyShowAddSectionLink(self):
# Allow the add user to add sections too. Now they can see the "add
# section" link.
add_user = User.objects.get(username='adduser')
- perm = get_perm(Section, Section._meta.get_add_permission())
+ perm = get_perm(Section, get_permission_codename('add', Section._meta))
add_user.user_permissions.add(perm)
response = self.client.get(url)
self.assertContains(response, add_link_text)
@@ -1315,7 +1316,7 @@ def setUp(self):
# User who can change Reports
change_user = User.objects.get(username='changeuser')
change_user.user_permissions.add(get_perm(Report,
- opts.get_change_permission()))
+ get_permission_codename('change', opts)))
# login POST dict
self.changeuser_login = {
@@ -1372,7 +1373,7 @@ def test_perms_needed(self):
self.client.logout()
delete_user = User.objects.get(username='deleteuser')
delete_user.user_permissions.add(get_perm(Plot,
- Plot._meta.get_delete_permission()))
+ get_permission_codename('delete', Plot._meta)))
self.assertTrue(self.client.login(username='deleteuser',
password='secret'))
From 4c1029971e810cde32ee4cd489627239b9b1b6ed Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Mon, 1 Jul 2013 09:36:31 -0400
Subject: [PATCH 019/944] [1.6.x] Fixed a couple form/formset deprecation
warnings in tests.
Backport of a521d10322 from master.
---
tests/model_forms/tests.py | 2 ++
tests/model_formsets/tests.py | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py
index 39be8247985f..19f0216ec73a 100644
--- a/tests/model_forms/tests.py
+++ b/tests/model_forms/tests.py
@@ -252,10 +252,12 @@ class Meta:
fields = '__all__'
widgets = {'status': forms.CheckboxSelectMultiple}
+
class CustomErrorMessageForm(forms.ModelForm):
name1 = forms.CharField(error_messages={'invalid': 'Form custom error message.'})
class Meta:
+ fields = '__all__'
model = CustomErrorMessage
diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py
index 43509c471ff1..09a3ba177885 100644
--- a/tests/model_formsets/tests.py
+++ b/tests/model_formsets/tests.py
@@ -399,7 +399,7 @@ def __init__(self, *args, **kwargs):
super(BaseAuthorFormSet, self).__init__(*args, **kwargs)
self.queryset = Author.objects.filter(name__startswith='Charles')
- AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet)
+ AuthorFormSet = modelformset_factory(Author, fields='__all__', formset=BaseAuthorFormSet)
formset = AuthorFormSet()
self.assertEqual(len(formset.get_queryset()), 1)
From 5ecdf0eb9ccd47c102deb873a242ed40d1cf45cd Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Tue, 2 Jul 2013 14:14:56 -0400
Subject: [PATCH 020/944] [1.6.x] A couple more semicolon -> colon fixes; refs
#18134.
Backport of 3632d289de from master.
---
docs/ref/forms/api.txt | 2 +-
docs/releases/1.6.txt | 8 ++++----
docs/topics/forms/index.txt | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index 3c17827800bf..aa19719a68a5 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -661,7 +661,7 @@ additional attributes for the ``
{% endif %}
- {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
+ {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
{{ inline_admin_form.fk_field.field }}
{% spaceless %}
{% for fieldset in inline_admin_form %}
diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py
index 2f88248ca4a7..62f9e04e5bb4 100644
--- a/tests/admin_inlines/admin.py
+++ b/tests/admin_inlines/admin.py
@@ -12,8 +12,26 @@ class BookInline(admin.TabularInline):
model = Author.books.through
+class NonAutoPKBookTabularInline(admin.TabularInline):
+ model = NonAutoPKBook
+
+
+class NonAutoPKBookStackedInline(admin.StackedInline):
+ model = NonAutoPKBook
+
+
+class EditablePKBookTabularInline(admin.TabularInline):
+ model = EditablePKBook
+
+
+class EditablePKBookStackedInline(admin.StackedInline):
+ model = EditablePKBook
+
+
class AuthorAdmin(admin.ModelAdmin):
- inlines = [BookInline]
+ inlines = [BookInline,
+ NonAutoPKBookTabularInline, NonAutoPKBookStackedInline,
+ EditablePKBookTabularInline, EditablePKBookStackedInline]
class InnerInline(admin.StackedInline):
diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py
index d4ba0ab6bc13..dde5cf79e308 100644
--- a/tests/admin_inlines/models.py
+++ b/tests/admin_inlines/models.py
@@ -3,6 +3,7 @@
"""
from __future__ import unicode_literals
+import random
from django.db import models
from django.contrib.contenttypes.models import ContentType
@@ -48,6 +49,25 @@ class Author(models.Model):
books = models.ManyToManyField(Book)
+class NonAutoPKBook(models.Model):
+ rand_pk = models.IntegerField(primary_key=True, editable=False)
+ author = models.ForeignKey(Author)
+ title = models.CharField(max_length=50)
+
+ def save(self, *args, **kwargs):
+ while not self.rand_pk:
+ test_pk = random.randint(1, 99999)
+ if not NonAutoPKBook.objects.filter(rand_pk=test_pk).exists():
+ self.rand_pk = test_pk
+ super(NonAutoPKBook, self).save(*args, **kwargs)
+
+
+class EditablePKBook(models.Model):
+ manual_pk = models.IntegerField(primary_key=True)
+ author = models.ForeignKey(Author)
+ title = models.CharField(max_length=50)
+
+
class Holder(models.Model):
dummy = models.IntegerField()
diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py
index 41dc0e32dea5..465b224d4f08 100644
--- a/tests/admin_inlines/tests.py
+++ b/tests/admin_inlines/tests.py
@@ -211,6 +211,24 @@ def test_custom_get_extra_form(self):
self.assertContains(response, max_forms_input % 2)
self.assertContains(response, total_forms_hidden)
+ def test_inline_nonauto_noneditable_pk(self):
+ response = self.client.get('/admin/admin_inlines/author/add/')
+ self.assertContains(response,
+ '',
+ html=True)
+ self.assertContains(response,
+ '',
+ html=True)
+
+ def test_inline_editable_pk(self):
+ response = self.client.get('/admin/admin_inlines/author/add/')
+ self.assertContains(response,
+ '',
+ html=True, count=1)
+ self.assertContains(response,
+ '',
+ html=True, count=1)
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class TestInlineMedia(TestCase):
From 48516d3b853e99600c60fed26b5c12ed3fc4a4d8 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Sat, 20 Jul 2013 18:00:23 +0200
Subject: [PATCH 077/944] [1.6.x] Fixed an email validation regression
Thanks Vincent Wagelaar for the report.
Backport of 11b7b9ad from master.
---
django/core/validators.py | 2 +-
tests/validators/tests.py | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/django/core/validators.py b/django/core/validators.py
index 200d28fe02d6..aa417ed099e5 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -87,7 +87,7 @@ class EmailValidator(object):
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"$)', # quoted-string
re.IGNORECASE)
domain_regex = re.compile(
- r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?$)' # domain
+ r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,})\.?$' # domain
# literal form, ipv4 address (SMTP 4.1.3)
r'|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$',
re.IGNORECASE)
diff --git a/tests/validators/tests.py b/tests/validators/tests.py
index a1555d8e9168..8d21ddea7941 100644
--- a/tests/validators/tests.py
+++ b/tests/validators/tests.py
@@ -46,6 +46,7 @@
(validate_email, 'example@-invalid.com', ValidationError),
(validate_email, 'example@inv-.alid-.com', ValidationError),
(validate_email, 'example@inv-.-alid.com', ValidationError),
+ (validate_email, 'test@example.com\n\n tags required with Google Maps JavaScript."
- return format_html('%s\n ', self.api_script, mark_safe(self.js))
+ return format_html('{0}\n ',
+ self.api_script, mark_safe(self.js))
@property
def style(self):
diff --git a/django/contrib/gis/tests/geoadmin/tests.py b/django/contrib/gis/tests/geoadmin/tests.py
index 15874758be3c..1bb54f2744f1 100644
--- a/django/contrib/gis/tests/geoadmin/tests.py
+++ b/django/contrib/gis/tests/geoadmin/tests.py
@@ -1,8 +1,9 @@
from __future__ import absolute_import
-from django.test import TestCase
from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
+from django.test import TestCase
+from django.test.utils import override_settings
from django.utils.unittest import skipUnless
if HAS_GEOS and HAS_SPATIAL_DB:
@@ -11,6 +12,8 @@
from .models import City
+GOOGLE_MAPS_API_KEY = 'XXXX'
+
@skipUnless(HAS_GEOS and HAS_SPATIAL_DB, "Geos and spatial db are required.")
class GeoAdminTest(TestCase):
@@ -38,7 +41,9 @@ def test_olmap_WMS_rendering(self):
result)
def test_olwidget_has_changed(self):
- """ Check that changes are accurately noticed by OpenLayersWidget. """
+ """
+ Check that changes are accurately noticed by OpenLayersWidget.
+ """
geoadmin = admin.site._registry[City]
form = geoadmin.get_changelist_form(None)()
has_changed = form.fields['point']._has_changed
@@ -54,3 +59,15 @@ def test_olwidget_has_changed(self):
self.assertFalse(has_changed(initial, data_same))
self.assertFalse(has_changed(initial, data_almost_same))
self.assertTrue(has_changed(initial, data_changed))
+
+ @override_settings(GOOGLE_MAPS_API_KEY=GOOGLE_MAPS_API_KEY)
+ def test_google_map_scripts(self):
+ """
+ Testing GoogleMap.scripts() output. See #20773.
+ """
+ from django.contrib.gis.maps.google.gmap import GoogleMap
+
+ google_map = GoogleMap()
+ scripts = google_map.scripts
+ self.assertIn(GOOGLE_MAPS_API_KEY, scripts)
+ self.assertIn("new GMap2", scripts)
From 43f1d51b4ba3ab2c0223928904e1d24c0571da8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?=
Date: Tue, 23 Jul 2013 15:06:02 +0300
Subject: [PATCH 080/944] [1.6.x] Minor change to get_extra_descriptor_filter()
Refs #20611. Backpatch of 6b4967e88368934dbbb1f289c790ab813fa59c72.
---
django/db/models/fields/related.py | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 0367d24fe895..650bbdfe73e0 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -297,10 +297,15 @@ def __get__(self, instance, instance_type=None):
params = dict(
(rh_field.attname, getattr(instance, lh_field.attname))
for lh_field, rh_field in self.field.related_fields)
- params.update(self.field.get_extra_descriptor_filter(instance))
qs = self.get_queryset(instance=instance)
+ extra_filter = self.field.get_extra_descriptor_filter(instance)
+ if isinstance(extra_filter, dict):
+ params.update(extra_filter)
+ qs = qs.filter(**params)
+ else:
+ qs = qs.filter(extra_filter, **params)
# Assuming the database enforces foreign keys, this won't fail.
- rel_obj = qs.get(**params)
+ rel_obj = qs.get()
if not self.field.rel.multiple:
setattr(rel_obj, self.field.related.get_cache_name(), instance)
setattr(instance, self.cache_name, rel_obj)
@@ -1003,10 +1008,11 @@ def get_extra_descriptor_filter(self, instance):
user does 'instance.fieldname', that is the extra filter is used in
the descriptor of the field.
- The filter should be something usable in .filter(**kwargs) call, and
- will be ANDed together with the joining columns condition.
+ The filter should be either a dict usable in .filter(**kwargs) call or
+ a Q-object. The condition will be ANDed together with the relation's
+ joining columns.
- A parallel method is get_extra_relation_restriction() which is used in
+ A parallel method is get_extra_restriction() which is used in
JOIN and subquery conditions.
"""
return {}
From ad898453b71d231402684c9e165bd4df9a1cfb76 Mon Sep 17 00:00:00 2001
From: Kirill Fomichev
Date: Wed, 24 Oct 2012 21:11:41 +0600
Subject: [PATCH 081/944] [1.6.x] Fixed #19019 -- Fixed UserAdmin to log
password change.
Thanks Tuttle for the report.
Backport of 33242fe015 from master
---
django/contrib/auth/admin.py | 2 +
django/contrib/auth/forms.py | 8 +++
django/contrib/auth/tests/test_views.py | 66 +++++++++++++++++++++++--
3 files changed, 72 insertions(+), 4 deletions(-)
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
index 5a1db686138e..5e424de34619 100644
--- a/django/contrib/auth/admin.py
+++ b/django/contrib/auth/admin.py
@@ -130,6 +130,8 @@ def user_change_password(self, request, id, form_url=''):
form = self.change_password_form(user, request.POST)
if form.is_valid():
form.save()
+ change_message = self.construct_change_message(request, form, None)
+ self.log_change(request, request.user, change_message)
msg = ugettext('Password changed successfully.')
messages.success(request, msg)
return HttpResponseRedirect('..')
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index 43f5303b63c8..eabb9da0b901 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -356,3 +356,11 @@ def save(self, commit=True):
if commit:
self.user.save()
return self.user
+
+ def _get_changed_data(self):
+ data = super(AdminPasswordChangeForm, self).changed_data
+ for name in self.fields.keys():
+ if name not in data:
+ return []
+ return ['password']
+ changed_data = property(_get_changed_data)
diff --git a/django/contrib/auth/tests/test_views.py b/django/contrib/auth/tests/test_views.py
index ba06a6af4d40..d5f0c40a57c3 100644
--- a/django/contrib/auth/tests/test_views.py
+++ b/django/contrib/auth/tests/test_views.py
@@ -8,6 +8,7 @@
from django.conf import global_settings, settings
from django.contrib.sites.models import Site, RequestSite
+from django.contrib.admin.models import LogEntry
from django.contrib.auth.models import User
from django.core import mail
from django.core.urlresolvers import reverse, NoReverseMatch
@@ -54,6 +55,11 @@ def login(self, password='password'):
self.assertTrue(SESSION_KEY in self.client.session)
return response
+ def logout(self):
+ response = self.client.get('/admin/logout/')
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(SESSION_KEY not in self.client.session)
+
def assertFormError(self, response, error):
"""Assert that error is found in response.context['form'] errors"""
form_errors = list(itertools.chain(*response.context['form'].errors.values()))
@@ -690,18 +696,70 @@ def test_security_check(self, password='password'):
self.confirm_logged_out()
@skipIfCustomUser
+@override_settings(
+ PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
+)
class ChangelistTests(AuthViewsTestCase):
urls = 'django.contrib.auth.tests.urls_admin'
- # #20078 - users shouldn't be allowed to guess password hashes via
- # repeated password__startswith queries.
- def test_changelist_disallows_password_lookups(self):
- # Make me a superuser before loging in.
+ def setUp(self):
+ # Make me a superuser before logging in.
User.objects.filter(username='testclient').update(is_staff=True, is_superuser=True)
self.login()
+ self.admin = User.objects.get(pk=1)
+
+ def get_user_data(self, user):
+ return {
+ 'username': user.username,
+ 'password': user.password,
+ 'email': user.email,
+ 'is_active': user.is_active,
+ 'is_staff': user.is_staff,
+ 'is_superuser': user.is_superuser,
+ 'last_login_0': user.last_login.strftime('%Y-%m-%d'),
+ 'last_login_1': user.last_login.strftime('%H:%M:%S'),
+ 'initial-last_login_0': user.last_login.strftime('%Y-%m-%d'),
+ 'initial-last_login_1': user.last_login.strftime('%H:%M:%S'),
+ 'date_joined_0': user.date_joined.strftime('%Y-%m-%d'),
+ 'date_joined_1': user.date_joined.strftime('%H:%M:%S'),
+ 'initial-date_joined_0': user.date_joined.strftime('%Y-%m-%d'),
+ 'initial-date_joined_1': user.date_joined.strftime('%H:%M:%S'),
+ 'first_name': user.first_name,
+ 'last_name': user.last_name,
+ }
+ # #20078 - users shouldn't be allowed to guess password hashes via
+ # repeated password__startswith queries.
+ def test_changelist_disallows_password_lookups(self):
# A lookup that tries to filter on password isn't OK
with patch_logger('django.security.DisallowedModelAdminLookup', 'error') as logger_calls:
response = self.client.get('/admin/auth/user/?password__startswith=sha1$')
self.assertEqual(response.status_code, 400)
self.assertEqual(len(logger_calls), 1)
+
+ def test_user_change_email(self):
+ data = self.get_user_data(self.admin)
+ data['email'] = 'new_' + data['email']
+ response = self.client.post('/admin/auth/user/%s/' % self.admin.pk, data)
+ self.assertRedirects(response, '/admin/auth/user/')
+ row = LogEntry.objects.latest('id')
+ self.assertEqual(row.change_message, 'Changed email.')
+
+ def test_user_not_change(self):
+ response = self.client.post('/admin/auth/user/%s/' % self.admin.pk,
+ self.get_user_data(self.admin)
+ )
+ self.assertRedirects(response, '/admin/auth/user/')
+ row = LogEntry.objects.latest('id')
+ self.assertEqual(row.change_message, 'No fields changed.')
+
+ def test_user_change_password(self):
+ response = self.client.post('/admin/auth/user/%s/password/' % self.admin.pk, {
+ 'password1': 'password1',
+ 'password2': 'password1',
+ })
+ self.assertRedirects(response, '/admin/auth/user/%s/' % self.admin.pk)
+ row = LogEntry.objects.latest('id')
+ self.assertEqual(row.change_message, 'Changed password.')
+ self.logout()
+ self.login(password='password1')
From 4525eab0779a2946063288224dcebb61ba382976 Mon Sep 17 00:00:00 2001
From: Aymeric Augustin
Date: Tue, 23 Jul 2013 15:41:09 +0200
Subject: [PATCH 082/944] [1.6.x] Fixed #20760 -- Reduced timing variation in
ModelBackend.
Thanks jpaglier and erikr.
Backport of 5dbca13f3baa2e1bafd77e84a80ad6d8a074712e from master.
---
django/contrib/auth/backends.py | 4 ++-
.../contrib/auth/tests/test_auth_backends.py | 25 ++++++++++++++++++-
2 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py
index 6b31f72b03c8..cb79291c17ae 100644
--- a/django/contrib/auth/backends.py
+++ b/django/contrib/auth/backends.py
@@ -17,7 +17,9 @@ def authenticate(self, username=None, password=None, **kwargs):
if user.check_password(password):
return user
except UserModel.DoesNotExist:
- return None
+ # Run the default password hasher once to reduce the timing
+ # difference between an existing and a non-existing user (#20760).
+ UserModel().set_password(password)
def get_group_permissions(self, user_obj, obj=None):
"""
diff --git a/django/contrib/auth/tests/test_auth_backends.py b/django/contrib/auth/tests/test_auth_backends.py
index fc5a80e8dda4..b48df91cfb22 100644
--- a/django/contrib/auth/tests/test_auth_backends.py
+++ b/django/contrib/auth/tests/test_auth_backends.py
@@ -12,6 +12,17 @@
from django.http import HttpRequest
from django.test import TestCase
from django.test.utils import override_settings
+from django.contrib.auth.hashers import MD5PasswordHasher
+
+
+class CountingMD5PasswordHasher(MD5PasswordHasher):
+ """Hasher that counts how many times it computes a hash."""
+
+ calls = 0
+
+ def encode(self, *args, **kwargs):
+ type(self).calls += 1
+ return super(CountingMD5PasswordHasher, self).encode(*args, **kwargs)
class BaseModelBackendTest(object):
@@ -107,10 +118,22 @@ def test_has_no_object_perm(self):
self.assertEqual(user.get_all_permissions(), set(['auth.test']))
def test_get_all_superuser_permissions(self):
- "A superuser has all permissions. Refs #14795"
+ """A superuser has all permissions. Refs #14795."""
user = self.UserModel._default_manager.get(pk=self.superuser.pk)
self.assertEqual(len(user.get_all_permissions()), len(Permission.objects.all()))
+ @override_settings(PASSWORD_HASHERS=('django.contrib.auth.tests.test_auth_backends.CountingMD5PasswordHasher',))
+ def test_authentication_timing(self):
+ """Hasher is run once regardless of whether the user exists. Refs #20760."""
+ CountingMD5PasswordHasher.calls = 0
+ username = getattr(self.user, self.UserModel.USERNAME_FIELD)
+ authenticate(username=username, password='test')
+ self.assertEqual(CountingMD5PasswordHasher.calls, 1)
+
+ CountingMD5PasswordHasher.calls = 0
+ authenticate(username='no_such_user', password='test')
+ self.assertEqual(CountingMD5PasswordHasher.calls, 1)
+
@skipIfCustomUser
class ModelBackendTest(BaseModelBackendTest, TestCase):
From 7295a8262f4f963ba7336337e2cd175be3db6f8c Mon Sep 17 00:00:00 2001
From: ersran9
Date: Tue, 23 Jul 2013 23:02:09 +0530
Subject: [PATCH 083/944] [1.6.x] Fixed #20791 -- Reworded ForeignKey default
error message
Backport of 311c1d2848 from master.
---
django/db/models/fields/related.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 650bbdfe73e0..b288a822982b 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -1125,7 +1125,7 @@ def contribute_to_related_class(self, cls, related):
class ForeignKey(ForeignObject):
empty_strings_allowed = False
default_error_messages = {
- 'invalid': _('Model %(model)s with pk %(pk)r does not exist.')
+ 'invalid': _('%(model)s instance with pk %(pk)r does not exist.')
}
description = _("Foreign Key (type determined by related field)")
From 45d5a4e7d22bb8541dcb8e8dc7d1e951758ac0ad Mon Sep 17 00:00:00 2001
From: Dominic Rodger
Date: Tue, 23 Jul 2013 21:58:43 +0100
Subject: [PATCH 084/944] [1.6.x] Fixed #20794 -- Documented changes to
validate_email
4e2e8f39d changed the way validate_email behaves for foo@localhost
email addresses, but wasn't listed in the release notes.
Backport of c928725b9 from master.
---
docs/releases/1.6.txt | 3 +++
1 file changed, 3 insertions(+)
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index 1419f0915c4e..071a80286cad 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -797,6 +797,9 @@ Miscellaneous
of the admin views. You should update your custom templates if they use the
previous parameter name.
+* :meth:`~django.core.validators.validate_email` now accepts email addresses
+ with ``localhost`` as the domain.
+
Features deprecated in 1.6
==========================
From cd46463eb170b4888931966a576188ddd17bfeff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jon=20L=C3=B8nne?=
Date: Wed, 24 Jul 2013 13:14:32 +0200
Subject: [PATCH 085/944] [1.6.x] Fixed typo in Custom management commands
documentation.
Backport of 5a5d594717 from master
---
docs/howto/custom-management-commands.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt
index 34e68d3700bf..04ab9bae1b79 100644
--- a/docs/howto/custom-management-commands.txt
+++ b/docs/howto/custom-management-commands.txt
@@ -245,7 +245,7 @@ All attributes can be set in your derived class and can be used in
Make sure you know what you are doing if you decide to change the value of
this option in your custom command if it creates database content that
is locale-sensitive and such content shouldn't contain any translations (like
- it happens e.g. with django.contrim.auth permissions) as making the locale
+ it happens e.g. with django.contrib.auth permissions) as making the locale
differ from the de facto default 'en-us' might cause unintended effects. See
the `Management commands and locales`_ section above for further details.
From 76047498719836c7c33c4610afb21b0d000e1393 Mon Sep 17 00:00:00 2001
From: Brenton Cleeland
Date: Thu, 25 Jul 2013 20:57:49 +1000
Subject: [PATCH 086/944] [1.6.x] Fixed #20792 -- Corrected
DISALLOWED_USER_AGENTS docs.
Thanks simonb for the report.
Backport of dab52d99fc from master
---
docs/ref/middleware.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt
index 4898bab63655..d011f054ac00 100644
--- a/docs/ref/middleware.txt
+++ b/docs/ref/middleware.txt
@@ -37,7 +37,7 @@ defines. See the :doc:`cache documentation `.
Adds a few conveniences for perfectionists:
* Forbids access to user agents in the :setting:`DISALLOWED_USER_AGENTS`
- setting, which should be a list of strings.
+ setting, which should be a list of compiled regular expression objects.
* Performs URL rewriting based on the :setting:`APPEND_SLASH` and
:setting:`PREPEND_WWW` settings.
From d439f85bbfd9f86dd91e70498ba536394690d1ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?=
Date: Thu, 25 Jul 2013 16:25:23 +0300
Subject: [PATCH 087/944] [1.6.x] Fixed ._meta.pk_index() virtual field failure
Backport of 92476e880c from master
---
django/db/models/options.py | 5 +++--
tests/foreign_object/models.py | 3 +++
tests/foreign_object/tests.py | 18 +++++++++++++++++-
3 files changed, 23 insertions(+), 3 deletions(-)
diff --git a/django/db/models/options.py b/django/db/models/options.py
index ad25de4a3e0d..ace8f3723456 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -204,9 +204,10 @@ def setup_pk(self, field):
def pk_index(self):
"""
- Returns the index of the primary key field in the self.fields list.
+ Returns the index of the primary key field in the self.concrete_fields
+ list.
"""
- return self.fields.index(self.pk)
+ return self.concrete_fields.index(self.pk)
def setup_proxy(self, target):
"""
diff --git a/tests/foreign_object/models.py b/tests/foreign_object/models.py
index eee8091a159d..4c58fd15bd9e 100644
--- a/tests/foreign_object/models.py
+++ b/tests/foreign_object/models.py
@@ -140,6 +140,9 @@ def __str__(self):
except ArticleTranslation.DoesNotExist:
return '[No translation found]'
+class NewsArticle(Article):
+ pass
+
class ArticleTranslation(models.Model):
article = models.ForeignKey(Article)
lang = models.CharField(max_length='2')
diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py
index 670fc94dc5b7..4a7cc3e61359 100644
--- a/tests/foreign_object/tests.py
+++ b/tests/foreign_object/tests.py
@@ -1,7 +1,9 @@
import datetime
from operator import attrgetter
-from .models import Country, Person, Group, Membership, Friendship, Article, ArticleTranslation, ArticleTag, ArticleIdea
+from .models import (
+ Country, Person, Group, Membership, Friendship, Article,
+ ArticleTranslation, ArticleTag, ArticleIdea, NewsArticle)
from django.test import TestCase
from django.utils.translation import activate
from django.core.exceptions import FieldError
@@ -339,6 +341,20 @@ def test_many_to_many_related_query_name(self):
with self.assertRaises(FieldError):
Article.objects.filter(ideas__name="idea1")
+ def test_inheritance(self):
+ activate("fi")
+ na = NewsArticle.objects.create(pub_date=datetime.date.today())
+ ArticleTranslation.objects.create(
+ article=na, lang="fi", title="foo", body="bar")
+ self.assertQuerysetEqual(
+ NewsArticle.objects.select_related('active_translation'),
+ [na], lambda x: x
+ )
+ with self.assertNumQueries(1):
+ self.assertEqual(
+ NewsArticle.objects.select_related(
+ 'active_translation')[0].active_translation.title,
+ "foo")
class FormsTests(TestCase):
# ForeignObjects should not have any form fields, currently the user needs
From ec6928be3430e5113a888da43a9b386f85add40b Mon Sep 17 00:00:00 2001
From: mark hellewell
Date: Thu, 25 Jul 2013 22:48:22 +1000
Subject: [PATCH 088/944] [1.6.x] Fixed #18315 -- Documented QueryDict.popitem
and QueryDict.pop
Thanks gcbirzan for the report.
Backport of 8c9240222f from master
---
docs/ref/request-response.txt | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt
index 7ca46dfe7905..ebe4119b3523 100644
--- a/docs/ref/request-response.txt
+++ b/docs/ref/request-response.txt
@@ -495,6 +495,26 @@ In addition, ``QueryDict`` has the following methods:
>>> q.lists()
[(u'a', [u'1', u'2', u'3'])]
+.. method:: QueryDict.pop(key)
+
+ Returns a list of values for the given key and removes them from the
+ dictionary. Raises ``KeyError`` if the key does not exist. For example::
+
+ >>> q = QueryDict('a=1&a=2&a=3', mutable=True)
+ >>> q.pop('a')
+ [u'1', u'2', u'3']
+
+.. method:: QueryDict.popitem()
+
+ Removes an arbitrary member of the dictionary (since there's no concept
+ of ordering), and returns a two value tuple containing the key and a list
+ of all values for the key. Raises ``KeyError`` when called on an empty
+ dictionary. For example::
+
+ >>> q = QueryDict('a=1&a=2&a=3', mutable=True)
+ >>> q.popitem()
+ (u'a', [u'1', u'2', u'3'])
+
.. method:: QueryDict.dict()
Returns ``dict`` representation of ``QueryDict``. For every (key, list)
From 9abbad491ac78fd061fe4b0dd96065fbbe4625fc Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 25 Jul 2013 12:31:53 -0400
Subject: [PATCH 089/944] [1.6.x] Fixed #20679 -- Corrected
CachedFilesMixin.post_process docstring.
Thanks bmispelon for the report.
Backport of 9b88dd3809 from master
---
django/contrib/staticfiles/storage.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py
index d085cf723f98..cd4ca516d5dc 100644
--- a/django/contrib/staticfiles/storage.py
+++ b/django/contrib/staticfiles/storage.py
@@ -202,7 +202,7 @@ def converter(matchobj):
def post_process(self, paths, dry_run=False, **options):
"""
- Post process the given list of files (called from collectstatic).
+ Post process the given SortedDict of files (called from collectstatic).
Processing is actually two separate operations:
From 68c01e15a5e46b385f5cbacc6f74a1dffabcdd55 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 25 Jul 2013 13:03:15 -0400
Subject: [PATCH 090/944] [1.6.x] Fixed #20769 -- Added "Python compatibility"
section to the 1.6 release notes.
Backport of bddb4a6818 from master
---
docs/releases/1.6.txt | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index 071a80286cad..cbc77c3fc450 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -32,6 +32,16 @@ deprecation process for some features`_.
.. _`backwards incompatible changes`: `Backwards incompatible changes in 1.6`_
.. _`begun the deprecation process for some features`: `Features deprecated in 1.6`_
+Python compatibility
+====================
+
+Django 1.6, like Django 1.5, requires Python 2.6.5 or above. Python 3 is also
+officially supported. We **highly recommend** the latest minor release for each
+supported Python series (2.6.X, 2.7.X, 3.2.X, and 3.3.X).
+
+Django 1.6 will be the final release series to support Python 2.6; beginning
+with Django 1.7, the minimum supported Python version will be 2.7.
+
What's new in Django 1.6
========================
From efdf7442bbd81eb4bc460e98e3b5c1825eaef546 Mon Sep 17 00:00:00 2001
From: Aymeric Augustin
Date: Thu, 25 Jul 2013 20:16:02 +0200
Subject: [PATCH 091/944] [1.6.x] Added versionadded directive missing from
b7bd708.
Backport of 5ed7ec9 from master.
---
docs/ref/class-based-views/base.txt | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/ref/class-based-views/base.txt b/docs/ref/class-based-views/base.txt
index 4ad017c34228..24058311a4c2 100644
--- a/docs/ref/class-based-views/base.txt
+++ b/docs/ref/class-based-views/base.txt
@@ -222,6 +222,8 @@ RedirectView
.. attribute:: pattern_name
+ .. versionadded:: 1.6
+
The name of the URL pattern to redirect to. Reversing will be done
using the same args and kwargs as are passed in for this view.
From 7f892cedbad13b444151a1c163a328d968ee6b44 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?=
Date: Fri, 26 Jul 2013 13:02:32 +0300
Subject: [PATCH 092/944] [1.6.x] Fixed related model lookup regression
It has been possible to use models of wrong type in related field
lookups. For example pigs__in=[a_duck] has worked. Changes to
ForeignObject broke that.
It might be a good idea to restrict the model types usable in lookups.
This should be done intentionally, not accidentally and without any
consideration for deprecation path.
Backpatch of 7cca8d56d28e321ffc395c92f82d97adaa0dcf94 from master.
---
django/db/models/fields/related.py | 2 +-
tests/queries/tests.py | 16 ++++++++++++++++
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index b288a822982b..5c13205fbbef 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -1060,7 +1060,7 @@ def get_normalized_value(value):
value_list = []
for source in sources:
# Account for one-to-one relations when sent a different model
- while not isinstance(value, source.model):
+ while not isinstance(value, source.model) and source.rel:
source = source.rel.to._meta.get_field(source.rel.field_name)
value_list.append(getattr(value, source.attname))
return tuple(value_list)
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index 352e87a63456..ff6c0bb7a1d7 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -2927,3 +2927,19 @@ def test_ticket_18785(self):
).order_by()
self.assertEqual(1, str(qs.query).count('INNER JOIN'))
self.assertEqual(0, str(qs.query).count('OUTER JOIN'))
+
+class RelatedLookupTypeTests(TestCase):
+ def test_wrong_type_lookup(self):
+ oa = ObjectA.objects.create(name="oa")
+ wrong_type = Order.objects.create(id=oa.pk)
+ ob = ObjectB.objects.create(name="ob", objecta=oa, num=1)
+ # Currently Django doesn't care if the object is of correct
+ # type, it will just use the objecta's related fields attribute
+ # (id) for model lookup. Making things more restrictive could
+ # be a good idea...
+ self.assertQuerysetEqual(
+ ObjectB.objects.filter(objecta=wrong_type),
+ [ob], lambda x: x)
+ self.assertQuerysetEqual(
+ ObjectB.objects.filter(objecta__in=[wrong_type]),
+ [ob], lambda x: x)
From 5cc1ea4773f628f93dd2db9d353dc6b980e4a3ab Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 26 Jul 2013 07:22:30 -0400
Subject: [PATCH 093/944] [1.6.x] Updated contrib.admin to use Email/URLInputs;
refs #16630
Backport of 2a979d2a7b from master
---
django/contrib/admin/options.py | 1 +
django/contrib/admin/widgets.py | 9 ++++++++-
tests/admin_widgets/models.py | 1 +
tests/admin_widgets/tests.py | 13 ++++++++-----
4 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 5b58e370a173..131c0ad88602 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -69,6 +69,7 @@ class IncorrectLookupParameters(Exception):
models.CharField: {'widget': widgets.AdminTextInputWidget},
models.ImageField: {'widget': widgets.AdminFileWidget},
models.FileField: {'widget': widgets.AdminFileWidget},
+ models.EmailField: {'widget': widgets.AdminEmailInputWidget},
}
csrf_protect_m = method_decorator(csrf_protect)
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index 4b79401dbc1d..eeae846320d7 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -285,7 +285,14 @@ def __init__(self, attrs=None):
final_attrs.update(attrs)
super(AdminTextInputWidget, self).__init__(attrs=final_attrs)
-class AdminURLFieldWidget(forms.TextInput):
+class AdminEmailInputWidget(forms.EmailInput):
+ def __init__(self, attrs=None):
+ final_attrs = {'class': 'vTextField'}
+ if attrs is not None:
+ final_attrs.update(attrs)
+ super(AdminEmailInputWidget, self).__init__(attrs=final_attrs)
+
+class AdminURLFieldWidget(forms.URLInput):
def __init__(self, attrs=None):
final_attrs = {'class': 'vURLField'}
if attrs is not None:
diff --git a/tests/admin_widgets/models.py b/tests/admin_widgets/models.py
index ae19d58cc473..2c9bc5b32a14 100644
--- a/tests/admin_widgets/models.py
+++ b/tests/admin_widgets/models.py
@@ -13,6 +13,7 @@ class Member(models.Model):
name = models.CharField(max_length=100)
birthdate = models.DateTimeField(blank=True, null=True)
gender = models.CharField(max_length=1, blank=True, choices=[('M','Male'), ('F', 'Female')])
+ email = models.EmailField(blank=True)
def __str__(self):
return self.name
diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
index 4823883f42c6..b4d6da034bb7 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -83,6 +83,9 @@ def testIntegerField(self):
def testCharField(self):
self.assertFormfield(models.Member, 'name', widgets.AdminTextInputWidget)
+ def testEmailField(self):
+ self.assertFormfield(models.Member, 'email', widgets.AdminEmailInputWidget)
+
def testFileField(self):
self.assertFormfield(models.Album, 'cover_art', widgets.AdminFileWidget)
@@ -300,29 +303,29 @@ def test_render(self):
w = widgets.AdminURLFieldWidget()
self.assertHTMLEqual(
conditional_escape(w.render('test', '')),
- ''
+ ''
)
self.assertHTMLEqual(
conditional_escape(w.render('test', 'http://example.com')),
- '
'
)
From dbbd2b1272e5a3ce7ab4b39b9a74f4ea725b104f Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 26 Jul 2013 14:43:46 -0400
Subject: [PATCH 094/944] [1.6.x] Fixed #20805 -- Removed an extra colon beside
checkboxes in the admin.
Thanks CollinAnderson for the report.
Backport of 8676318d2d from master
---
django/contrib/admin/helpers.py | 8 +++++---
django/forms/forms.py | 9 ++++++---
docs/ref/forms/api.txt | 15 ++++++++++++++-
docs/releases/1.6.txt | 4 +++-
tests/admin_util/tests.py | 4 ++--
tests/forms_tests/tests/test_forms.py | 10 ++++++++++
6 files changed, 40 insertions(+), 10 deletions(-)
diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py
index 3ffb85e6c635..b6d5bde932d4 100644
--- a/django/contrib/admin/helpers.py
+++ b/django/contrib/admin/helpers.py
@@ -125,14 +125,16 @@ def label_tag(self):
contents = conditional_escape(force_text(self.field.label))
if self.is_checkbox:
classes.append('vCheckboxLabel')
- else:
- contents += ':'
+
if self.field.field.required:
classes.append('required')
if not self.is_first:
classes.append('inline')
attrs = {'class': ' '.join(classes)} if classes else {}
- return self.field.label_tag(contents=mark_safe(contents), attrs=attrs)
+ # checkboxes should not have a label suffix as the checkbox appears
+ # to the left of the label.
+ return self.field.label_tag(contents=mark_safe(contents), attrs=attrs,
+ label_suffix='' if self.is_checkbox else None)
def errors(self):
return mark_safe(self.field.errors.as_ul())
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 6c062bed0f04..10ae1248da1f 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -509,20 +509,23 @@ def value(self):
)
return self.field.prepare_value(data)
- def label_tag(self, contents=None, attrs=None):
+ def label_tag(self, contents=None, attrs=None, label_suffix=None):
"""
Wraps the given contents in a , if the field has an ID attribute.
contents should be 'mark_safe'd to avoid HTML escaping. If contents
aren't given, uses the field's HTML-escaped label.
If attrs are given, they're used as HTML attributes on the tag.
+
+ label_suffix allows overriding the form's label_suffix.
"""
contents = contents or self.label
# Only add the suffix if the label does not end in punctuation.
# Translators: If found as last label character, these punctuation
# characters will prevent the default label_suffix to be appended to the label
- if self.form.label_suffix and contents and contents[-1] not in _(':?.!'):
- contents = format_html('{0}{1}', contents, self.form.label_suffix)
+ label_suffix = label_suffix if label_suffix is not None else self.form.label_suffix
+ if label_suffix and contents and contents[-1] not in _(':?.!'):
+ contents = format_html('{0}{1}', contents, label_suffix)
widget = self.field.widget
id_ = widget.attrs.get('id') or self.auto_id
if id_:
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index 7c1601d3ea4b..780cb5d4f717 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -527,6 +527,11 @@ Note that the label suffix is added only if the last character of the
label isn't a punctuation character (in English, those are ``.``, ``!``, ``?``
or ``:``).
+.. versionadded:: 1.6
+
+You can also customize the ``label_suffix`` on a per-field basis using the
+``label_suffix`` parameter to :meth:`~django.forms.BoundField.label_tag`.
+
Notes on field ordering
~~~~~~~~~~~~~~~~~~~~~~~
@@ -653,7 +658,7 @@ when printed::
>>> str(f['subject'].errors)
''
-.. method:: BoundField.label_tag(contents=None, attrs=None)
+.. method:: BoundField.label_tag(contents=None, attrs=None, label_suffix=None)
To separately render the label tag of a form field, you can call its
``label_tag`` method::
@@ -671,6 +676,14 @@ additional attributes for the ```` tag.
The label now includes the form's :attr:`~django.forms.Form.label_suffix`
(a colon, by default).
+.. versionadded:: 1.6
+
+ The optional ``label_suffix`` parameter allows you to override the form's
+ :attr:`~django.forms.Form.label_suffix`. For example, you can use an empty
+ string to hide the label on selected fields. If you need to do this in a
+ template, you could write a custom filter to allow passing parameters to
+ ``label_tag``.
+
.. method:: BoundField.css_classes()
When you use Django's rendering shortcuts, CSS classes are used to
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index cbc77c3fc450..ab70be7cfe1e 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -660,7 +660,9 @@ will render something like:
My Field:
If you want to keep the current behavior of rendering ``label_tag`` without
-the ``label_suffix``, instantiate the form ``label_suffix=''``.
+the ``label_suffix``, instantiate the form ``label_suffix=''``. You can also
+customize the ``label_suffix`` on a per-field basis using the new
+``label_suffix`` parameter on :meth:`~django.forms.BoundField.label_tag`.
Admin views ``_changelist_filters`` GET parameter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/admin_util/tests.py b/tests/admin_util/tests.py
index 637f6432619b..4a9a203f5039 100644
--- a/tests/admin_util/tests.py
+++ b/tests/admin_util/tests.py
@@ -301,7 +301,7 @@ class MyForm(forms.Form):
self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
'text:')
self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
- 'cb:')
+ 'cb')
# normal strings needs to be escaped
class MyForm(forms.Form):
@@ -312,7 +312,7 @@ class MyForm(forms.Form):
self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
'&text:')
self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
- '&cb:')
+ '&cb')
def test_flatten_fieldsets(self):
"""
diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
index 633fde5026a8..c77181273c04 100644
--- a/tests/forms_tests/tests/test_forms.py
+++ b/tests/forms_tests/tests/test_forms.py
@@ -1870,3 +1870,13 @@ class SomeForm(Form):
boundfield = SomeForm()['field']
self.assertHTMLEqual(boundfield.label_tag(), '')
+
+ def test_label_tag_override(self):
+ """
+ BoundField label_suffix (if provided) overrides Form label_suffix
+ """
+ class SomeForm(Form):
+ field = CharField()
+ boundfield = SomeForm(label_suffix='!')['field']
+
+ self.assertHTMLEqual(boundfield.label_tag(label_suffix='$'), 'Field$')
From 081a27c3585a109b0e8b62e43f57273c69890bd3 Mon Sep 17 00:00:00 2001
From: Florian Apolloner
Date: Sun, 28 Jul 2013 10:05:39 +0200
Subject: [PATCH 095/944] [1.6.x] Simplified smart_urlquote and added some
basic tests.
Backport of b70c371fc1f18ea0c43b503122df3f311afc7105 from master.
---
django/utils/html.py | 15 ++++++---------
tests/defaultfilters/tests.py | 7 ++++---
tests/utils_tests/test_html.py | 11 +++++++++++
3 files changed, 21 insertions(+), 12 deletions(-)
diff --git a/django/utils/html.py b/django/utils/html.py
index 4893b6b18a75..89e790d96f85 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -4,13 +4,13 @@
import re
try:
- from urllib.parse import quote, urlsplit, urlunsplit
+ from urllib.parse import quote, unquote, urlsplit, urlunsplit
except ImportError: # Python 2
- from urllib import quote
+ from urllib import quote, unquote
from urlparse import urlsplit, urlunsplit
from django.utils.safestring import SafeData, mark_safe
-from django.utils.encoding import force_bytes, force_text
+from django.utils.encoding import force_text, force_str
from django.utils.functional import allow_lazy
from django.utils import six
from django.utils.text import normalize_newlines
@@ -26,7 +26,6 @@
DOTS = ['·', '*', '\u2022', '', '•', '•']
unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)')
-unquoted_percents_re = re.compile(r'%(?![0-9A-Fa-f]{2})')
word_split_re = re.compile(r'(\s+)')
simple_url_re = re.compile(r'^https?://\[?\w', re.IGNORECASE)
simple_url_2_re = re.compile(r'^www\.|^(?!http)\w[^@]+\.(com|edu|gov|int|mil|net|org)$', re.IGNORECASE)
@@ -185,11 +184,9 @@ def smart_urlquote(url):
# invalid IPv6 URL (normally square brackets in hostname part).
pass
- # An URL is considered unquoted if it contains no % characters or
- # contains a % not followed by two hexadecimal digits. See #9655.
- if '%' not in url or unquoted_percents_re.search(url):
- # See http://bugs.python.org/issue2637
- url = quote(force_bytes(url), safe=b'!*\'();:@&=+$,/?#[]~')
+ url = unquote(force_str(url))
+ # See http://bugs.python.org/issue2637
+ url = quote(url, safe=b'!*\'();:@&=+$,/?#[]~')
return force_text(url)
diff --git a/tests/defaultfilters/tests.py b/tests/defaultfilters/tests.py
index d0009c6e667d..56b32ae6231f 100644
--- a/tests/defaultfilters/tests.py
+++ b/tests/defaultfilters/tests.py
@@ -248,9 +248,10 @@ def test_urlize(self):
'https://google.com')
# Check urlize doesn't overquote already quoted urls - see #9655
- self.assertEqual(urlize('http://hi.baidu.com/%D6%D8%D0%C2%BF'),
- ''
- 'http://hi.baidu.com/%D6%D8%D0%C2%BF')
+ # The teststring is the urlquoted version of 'http://hi.baidu.com/重新开始'
+ self.assertEqual(urlize('http://hi.baidu.com/%E9%87%8D%E6%96%B0%E5%BC%80%E5%A7%8B'),
+ ''
+ 'http://hi.baidu.com/%E9%87%8D%E6%96%B0%E5%BC%80%E5%A7%8B')
self.assertEqual(urlize('www.mystore.com/30%OffCoupons!'),
''
'www.mystore.com/30%OffCoupons!')
diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
index b973f1c64f96..ba8f29e3ae6a 100644
--- a/tests/utils_tests/test_html.py
+++ b/tests/utils_tests/test_html.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from datetime import datetime
@@ -181,3 +182,13 @@ def test_remove_tags(self):
)
for value, tags, output in items:
self.assertEqual(f(value, tags), output)
+
+ def test_smart_urlquote(self):
+ quote = html.smart_urlquote
+ # Ensure that IDNs are properly quoted
+ self.assertEqual(quote('http://öäü.com/'), 'http://xn--4ca9at.com/')
+ self.assertEqual(quote('http://öäü.com/öäü/'), 'http://xn--4ca9at.com/%C3%B6%C3%A4%C3%BC/')
+ # Ensure that everything unsafe is quoted, !*'();:@&=+$,/?#[]~ is considered safe as per RFC
+ self.assertEqual(quote('http://example.com/path/öäü/'), 'http://example.com/path/%C3%B6%C3%A4%C3%BC/')
+ self.assertEqual(quote('http://example.com/%C3%B6/ä/'), 'http://example.com/%C3%B6/%C3%A4/')
+ self.assertEqual(quote('http://example.com/?x=1&y=2'), 'http://example.com/?x=1&y=2')
From 192154fb17f257e74e45a28be4babc15c217ea06 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sun, 28 Jul 2013 14:09:29 -0400
Subject: [PATCH 096/944] [1.6.x] Fixed ReST typo in
topics/class-based-views/mixins.txt
Backport of 0b35a2cce3 from master
---
docs/topics/class-based-views/mixins.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt
index df1a5505a5b3..96b5e9f49888 100644
--- a/docs/topics/class-based-views/mixins.txt
+++ b/docs/topics/class-based-views/mixins.txt
@@ -345,7 +345,7 @@ The ``paginate_by`` is deliberately small in the example so you don't
have to create lots of books to see the pagination working! Here's the
template you'd want to use:
-.. code-block: html+django
+.. code-block:: html+django
{% extends "base.html" %}
From c456a277255e1b63ba4dcf6ed894e37ac4f05b63 Mon Sep 17 00:00:00 2001
From: Jannis Leidel
Date: Fri, 26 Jul 2013 11:48:24 +0200
Subject: [PATCH 097/944] [1.6.x] Fixed #20774 -- Mention the new
django-localflavor app as a replacement for the contrib app. Backport from
master.
---
docs/topics/localflavor.txt | 80 +++++++++----------------------------
1 file changed, 18 insertions(+), 62 deletions(-)
diff --git a/docs/topics/localflavor.txt b/docs/topics/localflavor.txt
index 8bc8b2f9e34e..724805b1470e 100644
--- a/docs/topics/localflavor.txt
+++ b/docs/topics/localflavor.txt
@@ -7,8 +7,9 @@ assorted pieces of code that are useful for particular countries or cultures.
This code is now distributed separately from Django, for easier maintenance
and to trim the size of Django's codebase.
-The localflavor packages are named ``django-localflavor-*``, where the asterisk
-is an `ISO 3166 country code`_. For example: ``django-localflavor-us`` is the
+The new localflavor package is named ``django-localflavor``, with a main
+module called ``localflavor`` and many subpackages using an
+`ISO 3166 country code`_. For example: ``localflavor.us`` is the
localflavor package for the U.S.A.
Most of these ``localflavor`` add-ons are country-specific fields for the
@@ -22,7 +23,7 @@ For example, here's how you can create a form with a field representing a
French telephone number::
from django import forms
- from django_localflavor_fr.forms import FRPhoneNumberField
+ from localflavor.fr.forms import FRPhoneNumberField
class MyForm(forms.Form):
my_french_phone_no = FRPhoneNumberField()
@@ -37,75 +38,30 @@ file.
Supported countries
===================
-The following countries have django-localflavor- packages.
-
-* Argentina: https://github.com/django/django-localflavor-ar
-* Australia: https://github.com/django/django-localflavor-au
-* Austria: https://github.com/django/django-localflavor-at
-* Belgium: https://github.com/django/django-localflavor-be
-* Brazil: https://github.com/django/django-localflavor-br
-* Canada: https://github.com/django/django-localflavor-ca
-* Chile: https://github.com/django/django-localflavor-cl
-* China: https://github.com/django/django-localflavor-cn
-* Colombia: https://github.com/django/django-localflavor-co
-* Croatia: https://github.com/django/django-localflavor-hr
-* Czech Republic: https://github.com/django/django-localflavor-cz
-* Ecuador: https://github.com/django/django-localflavor-ec
-* Finland: https://github.com/django/django-localflavor-fi
-* France: https://github.com/django/django-localflavor-fr
-* Germany: https://github.com/django/django-localflavor-de
-* Greece: https://github.com/spapas/django-localflavor-gr
-* Hong Kong: https://github.com/django/django-localflavor-hk
-* Iceland: https://github.com/django/django-localflavor-is
-* India: https://github.com/django/django-localflavor-in
-* Indonesia: https://github.com/django/django-localflavor-id
-* Ireland: https://github.com/django/django-localflavor-ie
-* Israel: https://github.com/django/django-localflavor-il
-* Italy: https://github.com/django/django-localflavor-it
-* Japan: https://github.com/django/django-localflavor-jp
-* Kuwait: https://github.com/django/django-localflavor-kw
-* Lithuania: https://github.com/simukis/django-localflavor-lt
-* Macedonia: https://github.com/django/django-localflavor-mk
-* Mexico: https://github.com/django/django-localflavor-mx
-* The Netherlands: https://github.com/django/django-localflavor-nl
-* Norway: https://github.com/django/django-localflavor-no
-* Peru: https://github.com/django/django-localflavor-pe
-* Poland: https://github.com/django/django-localflavor-pl
-* Portugal: https://github.com/django/django-localflavor-pt
-* Paraguay: https://github.com/django/django-localflavor-py
-* Romania: https://github.com/django/django-localflavor-ro
-* Russia: https://github.com/django/django-localflavor-ru
-* Slovakia: https://github.com/django/django-localflavor-sk
-* Slovenia: https://github.com/django/django-localflavor-si
-* South Africa: https://github.com/django/django-localflavor-za
-* Spain: https://github.com/django/django-localflavor-es
-* Sweden: https://github.com/django/django-localflavor-se
-* Switzerland: https://github.com/django/django-localflavor-ch
-* Turkey: https://github.com/django/django-localflavor-tr
-* United Kingdom: https://github.com/django/django-localflavor-gb
-* United States of America: https://github.com/django/django-localflavor-us
-* Uruguay: https://github.com/django/django-localflavor-uy
+See the official documentation for more information:
+
+ https://django-localflavor.readthedocs.org/
Internationalization of localflavors
====================================
-To activate translations for a ``localflavor`` application, you must include
-the application's name (e.g. ``django_localflavor_jp``) in the
-:setting:`INSTALLED_APPS` setting, so the internationalization system can find
-the catalog, as explained in :ref:`how-django-discovers-translations`.
+To activate translations for the ``localflavor`` application, you must include
+the application's name in the :setting:`INSTALLED_APPS` setting, so the
+internationalization system can find the catalog, as explained in
+:ref:`how-django-discovers-translations`.
.. _localflavor-how-to-migrate:
How to migrate
==============
-If you've used the old ``django.contrib.localflavor`` package, follow these two
-easy steps to update your code:
+If you've used the old ``django.contrib.localflavor`` package or one of the
+temporary ``django-localflavor-*`` releases, follow these two easy steps to
+update your code:
-1. Install the appropriate third-party ``django-localflavor-*`` package(s).
- Go to https://github.com/django/ and find the package for your country.
+1. Install the third-party ``django-localflavor`` package from PyPI.
-2. Change your app's import statements to reference the new packages.
+2. Change your app's import statements to reference the new package.
For example, change this::
@@ -113,9 +69,9 @@ easy steps to update your code:
...to this::
- from django_localflavor_fr.forms import FRPhoneNumberField
+ from localflavor.fr.forms import FRPhoneNumberField
-The code in the new packages is the same (it was copied directly from Django),
+The code in the new package is the same (it was copied directly from Django),
so you don't have to worry about backwards compatibility in terms of
functionality. Only the imports have changed.
From 5f8bf4368d79324f3fa076e1d7e810f53ddf4801 Mon Sep 17 00:00:00 2001
From: minusf
Date: Sun, 28 Jul 2013 19:58:19 +0900
Subject: [PATCH 098/944] [1.6.x] Allowed overriding variables in
docs/Makefile.
Backport of c694e6220e from master
---
docs/Makefile | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/Makefile b/docs/Makefile
index a2c926c7eadd..21b8a9cbe9e1 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -2,11 +2,11 @@
#
# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-BUILDDIR = _build
-LANGUAGE =
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+PAPER ?=
+BUILDDIR ?= _build
+LANGUAGE ?=
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
From 518faa19c1f9212cbbbfaca6ef605fd02acd3abb Mon Sep 17 00:00:00 2001
From: minusf
Date: Sun, 28 Jul 2013 19:58:19 +0900
Subject: [PATCH 099/944] [1.6.x] Misc doc cleanups.
Backport of 70c080fcdb from master
---
docs/ref/forms/widgets.txt | 7 +++----
docs/topics/templates.txt | 4 +++-
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt
index 5b66776cfcf5..080d1fea862f 100644
--- a/docs/ref/forms/widgets.txt
+++ b/docs/ref/forms/widgets.txt
@@ -52,8 +52,7 @@ widget on the field. In the following example, the
:attr:`~django.forms.extras.widgets.SelectDateWidget.years` attribute is set
for a :class:`~django.forms.extras.widgets.SelectDateWidget`::
- from django.forms.fields import DateField, ChoiceField, MultipleChoiceField
- from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
+ from django import forms
from django.forms.extras.widgets import SelectDateWidget
BIRTH_YEAR_CHOICES = ('1980', '1981', '1982')
@@ -62,9 +61,9 @@ for a :class:`~django.forms.extras.widgets.SelectDateWidget`::
('black', 'Black'))
class SimpleForm(forms.Form):
- birth_year = DateField(widget=SelectDateWidget(years=BIRTH_YEAR_CHOICES))
+ birth_year = forms.DateField(widget=SelectDateWidget(years=BIRTH_YEAR_CHOICES))
favorite_colors = forms.MultipleChoiceField(required=False,
- widget=CheckboxSelectMultiple, choices=FAVORITE_COLORS_CHOICES)
+ widget=forms.CheckboxSelectMultiple, choices=FAVORITE_COLORS_CHOICES)
See the :ref:`built-in widgets` for more information about which widgets
are available and which arguments they accept.
diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt
index 58a3ee9870b4..c75b83f158ae 100644
--- a/docs/topics/templates.txt
+++ b/docs/topics/templates.txt
@@ -45,7 +45,9 @@ A template contains **variables**, which get replaced with values when the
template is evaluated, and **tags**, which control the logic of the template.
Below is a minimal template that illustrates a few basics. Each element will be
-explained later in this document.::
+explained later in this document.
+
+.. code-block:: html+django
{% extends "base_generic.html" %}
From e82846a9aa38bcc3db7e1820d11c7fa9a5f79449 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Mon, 29 Jul 2013 18:41:08 -0400
Subject: [PATCH 100/944] [1.6.x] Fixed #20823 -- Typo in
docs/ref/forms/validation.txt
Backport of 05ea5a2139 from master
---
docs/ref/forms/validation.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt
index 8ab1c26831ae..87f9741c1bd8 100644
--- a/docs/ref/forms/validation.txt
+++ b/docs/ref/forms/validation.txt
@@ -171,7 +171,7 @@ following guidelines:
Putting it all together::
- raise ValidationErrror(
+ raise ValidationError(
_('Invalid value: %(value)s'),
code='invalid',
params={'value': '42'},
From 17e632929cdf9575f538c1f98379adac8698c288 Mon Sep 17 00:00:00 2001
From: Shai Berger
Date: Tue, 30 Jul 2013 03:21:06 +0300
Subject: [PATCH 101/944] [1.6.x] Fixed #20785 -- Corrected exception caught
for Oracle LIKE operator detection
The code that tests to see which LIKE expressions to use now runs
using non-error-wrapped cursor, so cx_Oracle exceptions need to be caught
rather than Django DatabaseErrors.
Thanks Trac user ludo for report and initial patch.
---
django/db/backends/oracle/base.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 470ea779c3cc..6c510f34b55b 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -576,7 +576,7 @@ def init_connection_state(self):
cursor.execute("SELECT 1 FROM DUAL WHERE DUMMY %s"
% self._standard_operators['contains'],
['X'])
- except utils.DatabaseError:
+ except DatabaseError:
self.operators = self._likec_operators
else:
self.operators = self._standard_operators
From 88e4a3a3d932997aabebba772217f954df2fd65b Mon Sep 17 00:00:00 2001
From: Aymeric Augustin
Date: Tue, 30 Jul 2013 16:14:53 +0200
Subject: [PATCH 102/944] [1.6.x] Fixed a test that could fail depending on
PASSWORD_HASHERS.
Thanks Claude. Refs #20760.
Backport of 5b47a9c5a0dcb513dc5ff68b617b3aa374c90f3b from master.
---
django/contrib/auth/tests/test_auth_backends.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/django/contrib/auth/tests/test_auth_backends.py b/django/contrib/auth/tests/test_auth_backends.py
index b48df91cfb22..4e83d786cf41 100644
--- a/django/contrib/auth/tests/test_auth_backends.py
+++ b/django/contrib/auth/tests/test_auth_backends.py
@@ -125,6 +125,10 @@ def test_get_all_superuser_permissions(self):
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.tests.test_auth_backends.CountingMD5PasswordHasher',))
def test_authentication_timing(self):
"""Hasher is run once regardless of whether the user exists. Refs #20760."""
+ # Re-set the password, because this tests overrides PASSWORD_HASHERS
+ self.user.set_password('test')
+ self.user.save()
+
CountingMD5PasswordHasher.calls = 0
username = getattr(self.user, self.UserModel.USERNAME_FIELD)
authenticate(username=username, password='test')
From bf132bcb8d741a161cd9ff61073ac40e7e873b59 Mon Sep 17 00:00:00 2001
From: MinRK
Date: Fri, 26 Jul 2013 14:32:56 -0700
Subject: [PATCH 103/944] [1.6.x] Added support for IPython.start_ipython in
shell
IPython 1.0 introduces an actual stable public API function
for starting a normal (non-embedded) IPython session.
This is an official public API, which is promised to survive implementation changes.
---
django/core/management/commands/shell.py | 40 +++++++++++++++---------
1 file changed, 26 insertions(+), 14 deletions(-)
diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py
index 851d4e3cfbe5..00a6602c0bb7 100644
--- a/django/core/management/commands/shell.py
+++ b/django/core/management/commands/shell.py
@@ -19,23 +19,35 @@ class Command(NoArgsCommand):
help = "Runs a Python interactive interpreter. Tries to use IPython or bpython, if one of them is available."
requires_model_validation = False
+ def _ipython_pre_011(self):
+ """Start IPython pre-0.11"""
+ from IPython.Shell import IPShell
+ shell = IPShell(argv=[])
+ shell.mainloop()
+
+ def _ipython_pre_100(self):
+ """Start IPython pre-1.0.0"""
+ from IPython.frontend.terminal.ipapp import TerminalIPythonApp
+ app = TerminalIPythonApp.instance()
+ app.initialize(argv=[])
+ app.start()
+
+ def _ipython(self):
+ """Start IPython >= 1.0"""
+ from IPython import start_ipython
+ start_ipython(argv=[])
+
def ipython(self):
- try:
- from IPython.frontend.terminal.ipapp import TerminalIPythonApp
- app = TerminalIPythonApp.instance()
- app.initialize(argv=[])
- app.start()
- except ImportError:
- # IPython < 0.11
- # Explicitly pass an empty list as arguments, because otherwise
- # IPython would use sys.argv from this script.
+ """Start any version of IPython"""
+ for ip in (self._ipython, self._ipython_pre_100, self._ipython_pre_011):
try:
- from IPython.Shell import IPShell
- shell = IPShell(argv=[])
- shell.mainloop()
+ ip()
except ImportError:
- # IPython not found at all, raise ImportError
- raise
+ pass
+ else:
+ return
+ # no IPython, raise ImportError
+ raise ImportError("No IPython")
def bpython(self):
import bpython
From badca4716fee99372ae545eaf6d5521db11348c1 Mon Sep 17 00:00:00 2001
From: Baptiste Mispelon
Date: Sat, 20 Apr 2013 05:20:01 +0200
Subject: [PATCH 104/944] [1.6.x] Fixed #10491 -- Allowed passing lazy objects
to HttpResponseRedirect.
Thanks liangent for the report.
Backport of 3c45fb8589 from master
---
django/http/response.py | 4 ++--
tests/httpwrappers/tests.py | 14 +++++++++++++-
2 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/django/http/response.py b/django/http/response.py
index 784f21174eee..8d8db2c8b4bd 100644
--- a/django/http/response.py
+++ b/django/http/response.py
@@ -15,7 +15,7 @@
from django.core.exceptions import DisallowedRedirect
from django.http.cookie import SimpleCookie
from django.utils import six, timezone
-from django.utils.encoding import force_bytes, iri_to_uri
+from django.utils.encoding import force_bytes, force_text, iri_to_uri
from django.utils.http import cookie_date
from django.utils.six.moves import map
@@ -454,7 +454,7 @@ class HttpResponseRedirectBase(HttpResponse):
allowed_schemes = ['http', 'https', 'ftp']
def __init__(self, redirect_to, *args, **kwargs):
- parsed = urlparse(redirect_to)
+ parsed = urlparse(force_text(redirect_to))
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
raise DisallowedRedirect("Unsafe redirect to URL with protocol '%s'" % parsed.scheme)
super(HttpResponseRedirectBase, self).__init__(*args, **kwargs)
diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py
index 194232e92fac..c4c7996aa5bf 100644
--- a/tests/httpwrappers/tests.py
+++ b/tests/httpwrappers/tests.py
@@ -15,11 +15,14 @@
SimpleCookie, BadHeaderError,
parse_cookie)
from django.test import TestCase
-from django.utils.encoding import smart_str
+from django.utils.encoding import smart_str, force_text
+from django.utils.functional import lazy
from django.utils._os import upath
from django.utils import six
from django.utils import unittest
+lazystr = lazy(force_text, six.text_type)
+
class QueryDictTests(unittest.TestCase):
def test_missing_key(self):
@@ -379,6 +382,10 @@ def test_iterator_isnt_rewound(self):
self.assertEqual(list(i), [b'abc'])
self.assertEqual(list(i), [])
+ def test_lazy_content(self):
+ r = HttpResponse(lazystr('helloworld'))
+ self.assertEqual(r.content, b'helloworld')
+
def test_file_interface(self):
r = HttpResponse()
r.write(b"hello")
@@ -415,6 +422,11 @@ def test_redirect(self):
# Test that url attribute is right
self.assertEqual(response.url, response['Location'])
+ def test_redirect_lazy(self):
+ """Make sure HttpResponseRedirect works with lazy strings."""
+ r = HttpResponseRedirect(lazystr('/redirected/'))
+ self.assertEqual(r.url, '/redirected/')
+
def test_not_modified(self):
response = HttpResponseNotModified()
self.assertEqual(response.status_code, 304)
From bc617fd42c660c87f5fb6ba43c7b78449c4997d7 Mon Sep 17 00:00:00 2001
From: SusanTan
Date: Tue, 30 Jul 2013 02:11:15 -0700
Subject: [PATCH 105/944] [1.6.x] Fixed #20779 -- Documented
AdminSite.app_index_template; refs #8498.
Thanks CollinAnderson for the report.
Backport of 7de35a9ef3 from master
---
docs/ref/contrib/admin/index.txt | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index e5e94288055a..662dd1eda6f7 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -2162,6 +2162,10 @@ Templates can override or extend base admin templates as described in
Path to a custom template that will be used by the admin site main index
view.
+.. attribute:: AdminSite.app_index_template
+
+ Path to a custom template that will be used by the admin site app index view.
+
.. attribute:: AdminSite.login_template
Path to a custom template that will be used by the admin site login view.
From d32471f8c276f19d69af6ba2531e42dac1e8d3e5 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Wed, 31 Jul 2013 09:24:29 -0400
Subject: [PATCH 106/944] [1.6.x] Added a bugfix in docutils 0.11 -- docs will
now build properly.
Backport of a3a59a3197 from master
---
docs/_ext/djangodocs.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py
index 833232a0c374..e7e316f58b0a 100644
--- a/docs/_ext/djangodocs.py
+++ b/docs/_ext/djangodocs.py
@@ -92,9 +92,15 @@ class DjangoHTMLTranslator(SmartyPantsHTMLTranslator):
# Don't use border=1, which docutils does by default.
def visit_table(self, node):
+ self.context.append(self.compact_p)
+ self.compact_p = True
self._table_row_index = 0 # Needed by Sphinx
self.body.append(self.starttag(node, 'table', CLASS='docutils'))
+ def depart_table(self, node):
+ self.compact_p = self.context.pop()
+ self.body.append('\n')
+
# ? Really?
def visit_desc_parameterlist(self, node):
self.body.append('(')
From b3e54f4a01dd410a87bc3d0669cb7b36aa74e7a8 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 1 Aug 2013 10:27:30 -0400
Subject: [PATCH 107/944] [1.6.x] Removed unused model option "admin"
Backport of 5df84b268d from master
---
django/db/models/options.py | 1 -
docs/ref/models/options.txt | 5 ++---
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/django/db/models/options.py b/django/db/models/options.py
index ace8f3723456..b37965ca4f9c 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -41,7 +41,6 @@ def __init__(self, meta, app_label=None):
self.get_latest_by = None
self.order_with_respect_to = None
self.db_tablespace = settings.DEFAULT_TABLESPACE
- self.admin = None
self.meta = meta
self.pk = None
self.has_auto_field, self.auto_field = False, None
diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
index 90099d13a3f3..6944796ef1d4 100644
--- a/docs/ref/models/options.txt
+++ b/docs/ref/models/options.txt
@@ -222,9 +222,8 @@ Django quotes column and table names behind the scenes.
.. attribute:: Options.permissions
Extra permissions to enter into the permissions table when creating this object.
- Add, delete and change permissions are automatically created for each object
- that has ``admin`` set. This example specifies an extra permission,
- ``can_deliver_pizzas``::
+ Add, delete and change permissions are automatically created for each
+ model. This example specifies an extra permission, ``can_deliver_pizzas``::
permissions = (("can_deliver_pizzas", "Can deliver pizzas"),)
From 196cc875b26f266e8f8dfd5be9daa0b8f246b9cd Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 1 Aug 2013 14:09:47 -0400
Subject: [PATCH 108/944] [1.6.x] Fixed #17519 -- Fixed missing SQL constraints
to proxy models.
Thanks thibaultj for the report, jenh for the patch,
and charettes for the tests.
Backport of aa830009de from master
---
django/db/backends/creation.py | 2 +-
tests/backends/models.py | 7 ++++++
tests/backends/tests.py | 39 +++++++++++++++++++++++++---------
3 files changed, 37 insertions(+), 11 deletions(-)
diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
index c9e5c83ade26..bae439b41967 100644
--- a/django/db/backends/creation.py
+++ b/django/db/backends/creation.py
@@ -146,7 +146,7 @@ def sql_for_pending_references(self, model, style, pending_references):
Returns any ALTER TABLE statements to add constraints after the fact.
"""
opts = model._meta
- if not opts.managed or opts.proxy or opts.swapped:
+ if not opts.managed or opts.swapped:
return []
qn = self.connection.ops.quote_name
final_output = []
diff --git a/tests/backends/models.py b/tests/backends/models.py
index 4f03ddeacce2..1508af435404 100644
--- a/tests/backends/models.py
+++ b/tests/backends/models.py
@@ -68,11 +68,18 @@ def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
+class ReporterProxy(Reporter):
+ class Meta:
+ proxy = True
+
+
@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter)
+ reporter_proxy = models.ForeignKey(ReporterProxy, null=True,
+ related_name='reporter_proxy')
def __str__(self):
return self.headline
diff --git a/tests/backends/tests.py b/tests/backends/tests.py
index b196133a684b..b018d2a2106b 100644
--- a/tests/backends/tests.py
+++ b/tests/backends/tests.py
@@ -613,12 +613,19 @@ def test_integrity_checks_on_creation(self):
Try to create a model instance that violates a FK constraint. If it
fails it should fail with IntegrityError.
"""
- a = models.Article(headline="This is a test", pub_date=datetime.datetime(2005, 7, 27), reporter_id=30)
+ a1 = models.Article(headline="This is a test", pub_date=datetime.datetime(2005, 7, 27), reporter_id=30)
try:
- a.save()
+ a1.save()
except IntegrityError:
- return
- self.skipTest("This backend does not support integrity checks.")
+ pass
+ else:
+ self.skipTest("This backend does not support integrity checks.")
+ # Now that we know this backend supports integrity checks we make sure
+ # constraints are also enforced for proxy models. Refs #17519
+ a2 = models.Article(headline='This is another test', reporter=self.r,
+ pub_date=datetime.datetime(2012, 8, 3),
+ reporter_proxy_id=30)
+ self.assertRaises(IntegrityError, a2.save)
def test_integrity_checks_on_update(self):
"""
@@ -627,14 +634,26 @@ def test_integrity_checks_on_update(self):
"""
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
- # Retrive it from the DB
- a = models.Article.objects.get(headline="Test article")
- a.reporter_id = 30
+ # Retrieve it from the DB
+ a1 = models.Article.objects.get(headline="Test article")
+ a1.reporter_id = 30
try:
- a.save()
+ a1.save()
except IntegrityError:
- return
- self.skipTest("This backend does not support integrity checks.")
+ pass
+ else:
+ self.skipTest("This backend does not support integrity checks.")
+ # Now that we know this backend supports integrity checks we make sure
+ # constraints are also enforced for proxy models. Refs #17519
+ # Create another article
+ r_proxy = models.ReporterProxy.objects.get(pk=self.r.pk)
+ models.Article.objects.create(headline='Another article',
+ pub_date=datetime.datetime(1988, 5, 15),
+ reporter=self.r, reporter_proxy=r_proxy)
+ # Retreive the second article from the DB
+ a2 = models.Article.objects.get(headline='Another article')
+ a2.reporter_proxy_id = 30
+ self.assertRaises(IntegrityError, a2.save)
def test_disable_constraint_checks_manually(self):
"""
From 6e4fd816c48b6ffd06fb7ae2c8ef2009bb0c6c25 Mon Sep 17 00:00:00 2001
From: Aleksandra Sendecka
Date: Sun, 24 Feb 2013 14:57:29 +0100
Subject: [PATCH 109/944] [1.6.x] Fixed #18777 -- Localized form fields with
as_text/as_hidden
Thanks croldan for the report.
Backport of 893d8de6f5 from master
---
django/forms/fields.py | 3 +++
tests/i18n/tests.py | 33 +++++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+)
diff --git a/django/forms/fields.py b/django/forms/fields.py
index a794c02e9fcb..76ee9b91d39a 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -118,6 +118,8 @@ def __init__(self, required=True, widget=None, label=None, initial=None,
super(Field, self).__init__()
def prepare_value(self, value):
+ if self.widget.is_localized:
+ value = formats.localize_input(value)
return value
def to_python(self, value):
@@ -460,6 +462,7 @@ class DateTimeField(BaseTemporalField):
}
def prepare_value(self, value):
+ value = super(DateTimeField, self).prepare_value(value)
if isinstance(value, datetime.datetime):
value = to_current_timezone(value)
return value
diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py
index 019ed88cdb6f..bd61943cc197 100644
--- a/tests/i18n/tests.py
+++ b/tests/i18n/tests.py
@@ -774,6 +774,39 @@ def test_localize_templatetag_and_filter(self):
self.assertEqual(template2.render(context), output2)
self.assertEqual(template3.render(context), output3)
+ def test_localized_as_text_as_hidden_input(self):
+ """
+ Tests if form input with 'as_hidden' or 'as_text' is correctly localized. Ticket #18777
+ """
+ self.maxDiff = 1200
+
+ with translation.override('de-at', deactivate=True):
+ template = Template('{% load l10n %}{{ form.date_added }}; {{ form.cents_paid }}')
+ template_as_text = Template('{% load l10n %}{{ form.date_added.as_text }}; {{ form.cents_paid.as_text }}')
+ template_as_hidden = Template('{% load l10n %}{{ form.date_added.as_hidden }}; {{ form.cents_paid.as_hidden }}')
+ form = CompanyForm({
+ 'name': 'acme',
+ 'date_added': datetime.datetime(2009, 12, 31, 6, 0, 0),
+ 'cents_paid': decimal.Decimal('59.47'),
+ 'products_delivered': 12000,
+ })
+ context = Context({'form': form })
+ self.assertTrue(form.is_valid())
+
+ self.assertHTMLEqual(
+ template.render(context),
+ '; '
+ )
+ self.assertHTMLEqual(
+ template_as_text.render(context),
+ '; '
+ )
+ self.assertHTMLEqual(
+ template_as_hidden.render(context),
+ '; '
+ )
+
+
class MiscTests(TransRealMixin, TestCase):
def setUp(self):
From e710f6dc56731b52f84864e7cb199dc4a8f881e7 Mon Sep 17 00:00:00 2001
From: Dominic Rodger
Date: Thu, 1 Aug 2013 22:27:11 +0100
Subject: [PATCH 110/944] [1.6.x] Fixed #20786 -- Cleaned up
docs/ref/exceptions.txt
Thanks Daniele Procida for the suggestion and edits.
Backport of 920b242e30 from master
---
docs/ref/exceptions.txt | 48 ++++++++++++++++++++++++++---------------
1 file changed, 31 insertions(+), 17 deletions(-)
diff --git a/docs/ref/exceptions.txt b/docs/ref/exceptions.txt
index b15bbea8fa27..bee8559ab07b 100644
--- a/docs/ref/exceptions.txt
+++ b/docs/ref/exceptions.txt
@@ -6,24 +6,29 @@ Django Exceptions
Django raises some Django specific exceptions as well as many standard
Python exceptions.
-Django-specific Exceptions
-==========================
+Django Core Exceptions
+======================
.. module:: django.core.exceptions
- :synopsis: Django specific exceptions
+ :synopsis: Django core exceptions
+
+Django core exception classes are defined in :mod:`django.core.exceptions`.
ObjectDoesNotExist and DoesNotExist
-----------------------------------
.. exception:: DoesNotExist
-.. exception:: ObjectDoesNotExist
- The :exc:`DoesNotExist` exception is raised when an object is not found
- for the given parameters of a query.
+ The ``DoesNotExist`` exception is raised when an object is not found for
+ the given parameters of a query. Django provides a ``DoesNotExist``
+ exception as an attribute of each model class to identify the class of
+ object that could not be found and to allow you to catch a particular model
+ class with ``try/except``.
+
+.. exception:: ObjectDoesNotExist
- :exc:`ObjectDoesNotExist` is defined in :mod:`django.core.exceptions`.
- :exc:`DoesNotExist` is a subclass of the base :exc:`ObjectDoesNotExist`
- exception that is provided on every model class as a way of
- identifying the specific type of object that could not be found.
+ The base class for ``DoesNotExist`` exceptions; a ``try/except`` for
+ ``ObjectDoesNotExist`` will catch ``DoesNotExist`` exceptions for all
+ models.
See :meth:`~django.db.models.query.QuerySet.get()` for further information
on :exc:`ObjectDoesNotExist` and :exc:`DoesNotExist`.
@@ -121,6 +126,11 @@ ValidationError
.. currentmodule:: django.core.urlresolvers
+URL Resolver exceptions
+=======================
+
+URL Resolver exceptions are defined in :mod:`django.core.urlresolvers`.
+
NoReverseMatch
--------------
.. exception:: NoReverseMatch
@@ -134,9 +144,10 @@ NoReverseMatch
Database Exceptions
===================
+Database exceptions are provided in :mod:`django.db`.
+
Django wraps the standard database exceptions so that your Django code has a
-guaranteed common implementation of these classes. These database exceptions
-are provided in :mod:`django.db`.
+guaranteed common implementation of these classes.
.. exception:: Error
.. exception:: InterfaceError
@@ -160,34 +171,37 @@ to Python 3.)
.. versionchanged:: 1.6
- Previous version of Django only wrapped ``DatabaseError`` and
+ Previous versions of Django only wrapped ``DatabaseError`` and
``IntegrityError``, and did not provide ``__cause__``.
.. exception:: models.ProtectedError
Raised to prevent deletion of referenced objects when using
-:attr:`django.db.models.PROTECT`. Subclass of :exc:`IntegrityError`.
+:attr:`django.db.models.PROTECT`. :exc:`models.ProtectedError` is a subclass
+of :exc:`IntegrityError`.
.. currentmodule:: django.http
Http Exceptions
===============
+Http exceptions are provided in :mod:`django.http`.
+
.. exception:: UnreadablePostError
The :exc:`UnreadablePostError` is raised when a user cancels an upload.
- It is available from :mod:`django.http`.
.. currentmodule:: django.db.transaction
Transaction Exceptions
======================
+Transaction exceptions are defined in :mod:`django.db.transaction`.
+
.. exception:: TransactionManagementError
The :exc:`TransactionManagementError` is raised for any and all problems
- related to database transactions. It is available from
- :mod:`django.db.transaction`.
+ related to database transactions.
Python Exceptions
=================
From f942554cf3f5da7efe3e66832fd58b9bdd05f2e5 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Sat, 6 Jul 2013 14:26:28 +0200
Subject: [PATCH 111/944] [1.6.x] Tweaked proj string regex in gis tests
Backport of 4367c637d6 from master
---
django/contrib/gis/tests/test_spatialrefsys.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/contrib/gis/tests/test_spatialrefsys.py b/django/contrib/gis/tests/test_spatialrefsys.py
index f33883e9f1fc..a493838323c4 100644
--- a/django/contrib/gis/tests/test_spatialrefsys.py
+++ b/django/contrib/gis/tests/test_spatialrefsys.py
@@ -22,7 +22,7 @@
'auth_srid' : 32140,
'srtext' : 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980"',
'proj4_re' : r'\+proj=lcc \+lat_1=30.28333333333333 \+lat_2=28.38333333333333 \+lat_0=27.83333333333333 '
- r'\+lon_0=-99 \+x_0=600000 \+y_0=4000000 \+ellps=GRS80 '
+ r'\+lon_0=-99 \+x_0=600000 \+y_0=4000000 (\+ellps=GRS80 )?'
r'(\+datum=NAD83 |\+towgs84=0,0,0,0,0,0,0)?\+units=m \+no_defs ',
'spheroid' : 'GRS 1980', 'name' : 'NAD83 / Texas South Central',
'geographic' : False, 'projected' : True, 'spatialite' : False,
From b0bde218847692096b08d27aa37eba1745cacca8 Mon Sep 17 00:00:00 2001
From: Harm Geerts
Date: Wed, 31 Jul 2013 23:03:22 +0200
Subject: [PATCH 112/944] [1.6.x] Fixed #20838 -- Fixed Geodjango spatialrefsys
test failure with postgis-2.0.3
Backport of fd0d486467 from master
---
django/contrib/gis/tests/test_spatialrefsys.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/contrib/gis/tests/test_spatialrefsys.py b/django/contrib/gis/tests/test_spatialrefsys.py
index a493838323c4..e8bb5b55447c 100644
--- a/django/contrib/gis/tests/test_spatialrefsys.py
+++ b/django/contrib/gis/tests/test_spatialrefsys.py
@@ -23,7 +23,7 @@
'srtext' : 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980"',
'proj4_re' : r'\+proj=lcc \+lat_1=30.28333333333333 \+lat_2=28.38333333333333 \+lat_0=27.83333333333333 '
r'\+lon_0=-99 \+x_0=600000 \+y_0=4000000 (\+ellps=GRS80 )?'
- r'(\+datum=NAD83 |\+towgs84=0,0,0,0,0,0,0)?\+units=m \+no_defs ',
+ r'(\+datum=NAD83 |\+towgs84=0,0,0,0,0,0,0 )?\+units=m \+no_defs ',
'spheroid' : 'GRS 1980', 'name' : 'NAD83 / Texas South Central',
'geographic' : False, 'projected' : True, 'spatialite' : False,
'ellipsoid' : (6378137.0, 6356752.31414, 298.257222101), # From proj's "cs2cs -le" and Wikipedia (semi-minor only)
From 4f8fb199948eab417961a8df66e5c41354d9fd0d Mon Sep 17 00:00:00 2001
From: Loic Bistuer
Date: Wed, 31 Jul 2013 12:50:39 +0700
Subject: [PATCH 113/944] [1.6.x] Fixed #18681 --
GenericInlineModelAdmin.get_formset() no longer bypasses get_fieldsets().
Refs 23e1b59 which already fixed this issue for ModelAdmin and InlineModelAdmin.
Backport of a0ed2f9260 from master
---
django/contrib/contenttypes/generic.py | 6 +++---
tests/generic_inline_admin/tests.py | 20 ++++++++++++++++++++
2 files changed, 23 insertions(+), 3 deletions(-)
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index e14aa5ecee92..c1db206515a3 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -465,10 +465,10 @@ class GenericInlineModelAdmin(InlineModelAdmin):
formset = BaseGenericInlineFormSet
def get_formset(self, request, obj=None, **kwargs):
- if self.declared_fieldsets:
- fields = flatten_fieldsets(self.declared_fieldsets)
+ if 'fields' in kwargs:
+ fields = kwargs.pop('fields')
else:
- fields = None
+ fields = flatten_fieldsets(self.get_fieldsets(request, obj))
if self.exclude is None:
exclude = []
else:
diff --git a/tests/generic_inline_admin/tests.py b/tests/generic_inline_admin/tests.py
index ac2c19118368..efa40ca3d9a2 100644
--- a/tests/generic_inline_admin/tests.py
+++ b/tests/generic_inline_admin/tests.py
@@ -325,3 +325,23 @@ class EpisodeAdmin(admin.ModelAdmin):
self.assertEqual(
list(list(ma.get_formsets(request))[0]().forms[0].fields),
['description', 'keywords', 'id', 'DELETE'])
+
+ def test_get_fieldsets(self):
+ # Test that get_fieldsets is called when figuring out form fields.
+ # Refs #18681.
+ class MediaForm(ModelForm):
+ class Meta:
+ model = Media
+ fields = '__all__'
+
+ class MediaInline(GenericTabularInline):
+ form = MediaForm
+ model = Media
+ can_delete = False
+
+ def get_fieldsets(self, request, obj=None):
+ return [(None, {'fields': ['url', 'description']})]
+
+ ma = MediaInline(Media, self.site)
+ form = ma.get_formset(None).form
+ self.assertEqual(form._meta.fields, ['url', 'description'])
From 4e7745cc1c89a0c464b752e6da7c1fe7bce948d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Dlouh=C3=BD?=
Date: Thu, 1 Aug 2013 09:51:01 +0200
Subject: [PATCH 114/944] [1.6.x] Fixed LogEntry.get_admin_url() for
non-existent models.
Regression introduced by [369b6fa]; refs #18169.
Backport of 1b47508ac8 from master
---
django/contrib/admin/models.py | 7 +++++--
tests/admin_views/tests.py | 11 +++++++++++
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py
index f3be6530bf46..dc282b7e57f2 100644
--- a/django/contrib/admin/models.py
+++ b/django/contrib/admin/models.py
@@ -4,7 +4,7 @@
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin.util import quote
-from django.core.urlresolvers import reverse
+from django.core.urlresolvers import reverse, NoReverseMatch
from django.utils.translation import ugettext, ugettext_lazy as _
from django.utils.encoding import smart_text
from django.utils.encoding import python_2_unicode_compatible
@@ -74,5 +74,8 @@ def get_admin_url(self):
"""
if self.content_type and self.object_id:
url_name = 'admin:%s_%s_change' % (self.content_type.app_label, self.content_type.model)
- return reverse(url_name, args=(quote(self.object_id),))
+ try:
+ return reverse(url_name, args=(quote(self.object_id),))
+ except NoReverseMatch:
+ pass
return None
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 9374dfff2479..3c276199ff36 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -1525,6 +1525,17 @@ def test_recentactions_without_content_type(self):
self.assertEqual(counted_presence_before - 1,
counted_presence_after)
+ def test_logentry_get_admin_url(self):
+ "LogEntry.get_admin_url returns a URL to edit the entry's object or None for non-existent (possibly deleted) models"
+ log_entry_name = "Model with string primary key" # capitalized in Recent Actions
+ logentry = LogEntry.objects.get(content_type__name__iexact=log_entry_name)
+ model = "modelwithstringprimarykey"
+ desired_admin_url = "/test_admin/admin/admin_views/%s/%s/" % (model, escape(iri_to_uri(urlquote(quote(self.pk)))))
+ self.assertEqual(logentry.get_admin_url(), desired_admin_url)
+
+ logentry.content_type.model = "non-existent"
+ self.assertEqual(logentry.get_admin_url(), None)
+
def test_deleteconfirmation_link(self):
"The link from the delete confirmation page referring back to the changeform of the object should be quoted"
response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/delete/' % quote(self.pk))
From 97254154ab43d7973fba09ccd7ab548866f83e03 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 2 Aug 2013 14:46:17 -0400
Subject: [PATCH 115/944] [1.6.x] Fixed #18923 -- Corrected usage of
sensitive_post_parameters in contrib.auth
Thanks Collin Anderson for the report.
Backport of 425d076d0c from master
---
django/contrib/auth/admin.py | 5 +++--
django/views/decorators/debug.py | 6 ++++++
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
index 5e424de34619..033e3cf6e183 100644
--- a/django/contrib/auth/admin.py
+++ b/django/contrib/auth/admin.py
@@ -17,6 +17,7 @@
from django.views.decorators.debug import sensitive_post_parameters
csrf_protect_m = method_decorator(csrf_protect)
+sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())
class GroupAdmin(admin.ModelAdmin):
@@ -90,7 +91,7 @@ def lookup_allowed(self, lookup, value):
return False
return super(UserAdmin, self).lookup_allowed(lookup, value)
- @sensitive_post_parameters()
+ @sensitive_post_parameters_m
@csrf_protect_m
@transaction.atomic
def add_view(self, request, form_url='', extra_context=None):
@@ -121,7 +122,7 @@ def add_view(self, request, form_url='', extra_context=None):
return super(UserAdmin, self).add_view(request, form_url,
extra_context)
- @sensitive_post_parameters()
+ @sensitive_post_parameters_m
def user_change_password(self, request, id, form_url=''):
if not self.has_change_permission(request):
raise PermissionDenied
diff --git a/django/views/decorators/debug.py b/django/views/decorators/debug.py
index 78ae6b144255..a611981e791b 100644
--- a/django/views/decorators/debug.py
+++ b/django/views/decorators/debug.py
@@ -1,5 +1,7 @@
import functools
+from django.http import HttpRequest
+
def sensitive_variables(*variables):
"""
@@ -62,6 +64,10 @@ def my_view(request)
def decorator(view):
@functools.wraps(view)
def sensitive_post_parameters_wrapper(request, *args, **kwargs):
+ assert isinstance(request, HttpRequest), (
+ "sensitive_post_parameters didn't receive an HttpRequest. If you "
+ "are decorating a classmethod, be sure to use @method_decorator."
+ )
if parameters:
request.sensitive_post_parameters = parameters
else:
From ccef8b2aa26201ec1f3c4d3be6e69f52edf51144 Mon Sep 17 00:00:00 2001
From: Alasdair Nicol
Date: Sat, 3 Aug 2013 20:42:02 +0100
Subject: [PATCH 116/944] [1.6.x] Added missing request argument to example in
URL dispatcher docs
Backport of a0c58113b9 from master
---
docs/topics/http/urls.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt
index 8a3f24030748..a9f488737d42 100644
--- a/docs/topics/http/urls.txt
+++ b/docs/topics/http/urls.txt
@@ -412,7 +412,7 @@ For example::
)
In this example, for a request to ``/blog/2005/``, Django will call
-``blog.views.year_archive(year='2005', foo='bar')``.
+``blog.views.year_archive(request, year='2005', foo='bar')``.
This technique is used in the
:doc:`syndication framework ` to pass metadata and
From 2eac9899856d0e19c8e2df02256f2e8c4bdb5344 Mon Sep 17 00:00:00 2001
From: Aymeric Augustin
Date: Sun, 4 Aug 2013 11:01:01 +0200
Subject: [PATCH 117/944] [1.6.x] Fixed #20822 -- Set content type of default
error pages to 'text/html'.
Thanks Jimmy Song for the patch.
Backport of 7843775 from master.
---
django/views/defaults.py | 11 +++++++----
tests/view_tests/tests/test_defaults.py | 20 +++++++++++++++++++-
tests/view_tests/urls.py | 4 +++-
tests/view_tests/views.py | 11 +++++++----
4 files changed, 36 insertions(+), 10 deletions(-)
diff --git a/django/views/defaults.py b/django/views/defaults.py
index c8a62fc7532d..d531f2237b6f 100644
--- a/django/views/defaults.py
+++ b/django/views/defaults.py
@@ -21,11 +21,14 @@ def page_not_found(request, template_name='404.html'):
"""
try:
template = loader.get_template(template_name)
+ content_type = None # Django will use DEFAULT_CONTENT_TYPE
except TemplateDoesNotExist:
template = Template(
'
Not Found
'
'
The requested URL {{ request_path }} was not found on this server.
', content_type='text/html')
return http.HttpResponseForbidden(template.render(RequestContext(request)))
diff --git a/tests/view_tests/tests/test_defaults.py b/tests/view_tests/tests/test_defaults.py
index 5efd338d345c..df891add7ea0 100644
--- a/tests/view_tests/tests/test_defaults.py
+++ b/tests/view_tests/tests/test_defaults.py
@@ -2,7 +2,8 @@
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
-from django.test.utils import setup_test_template_loader, restore_template_loaders
+from django.test.utils import (setup_test_template_loader,
+ restore_template_loaders, override_settings)
from ..models import Author, Article, UrlArticle
@@ -59,3 +60,20 @@ def test_get_absolute_url_attributes(self):
article = UrlArticle.objects.get(pk=1)
self.assertTrue(getattr(article.get_absolute_url, 'purge', False),
'The attributes of the original get_absolute_url must be added.')
+
+ @override_settings(DEFAULT_CONTENT_TYPE="text/xml")
+ def test_default_content_type_is_text_html(self):
+ """
+ Content-Type of the default error responses is text/html. Refs #20822.
+ """
+ response = self.client.get('/views/raises400/')
+ self.assertEqual(response['Content-Type'], 'text/html')
+
+ response = self.client.get('/views/raises403/')
+ self.assertEqual(response['Content-Type'], 'text/html')
+
+ response = self.client.get('/views/non_existing_url/')
+ self.assertEqual(response['Content-Type'], 'text/html')
+
+ response = self.client.get('/views/server_error/')
+ self.assertEqual(response['Content-Type'], 'text/html')
diff --git a/tests/view_tests/urls.py b/tests/view_tests/urls.py
index d792e47ddfb0..b0c06a1e5606 100644
--- a/tests/view_tests/urls.py
+++ b/tests/view_tests/urls.py
@@ -47,8 +47,10 @@
# a view that raises an exception for the debug view
(r'raises/$', views.raises),
- (r'raises404/$', views.raises404),
+
+ (r'raises400/$', views.raises400),
(r'raises403/$', views.raises403),
+ (r'raises404/$', views.raises404),
# i18n views
(r'^i18n/', include('django.conf.urls.i18n')),
diff --git a/tests/view_tests/views.py b/tests/view_tests/views.py
index 1cfafa4333d2..5fcf1703d497 100644
--- a/tests/view_tests/views.py
+++ b/tests/view_tests/views.py
@@ -2,7 +2,7 @@
import sys
-from django.core.exceptions import PermissionDenied
+from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.core.urlresolvers import get_resolver
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, render
@@ -31,13 +31,16 @@ def callable():
except Exception:
return technical_500_response(request, *sys.exc_info())
-def raises404(request):
- resolver = get_resolver(None)
- resolver.resolve('')
+def raises400(request):
+ raise SuspiciousOperation
def raises403(request):
raise PermissionDenied
+def raises404(request):
+ resolver = get_resolver(None)
+ resolver.resolve('')
+
def redirect(request):
"""
Forces an HTTP redirect.
From 01a96042740788aee1c371eb191113f2700d3ccc Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sun, 4 Aug 2013 05:57:11 -0400
Subject: [PATCH 118/944] [1.6.x] Fixed #20858 -- Removed erroneous import in
tutorial 2.
Thanks AtomicSpark.
Backport of b278f7478d from master
---
docs/intro/tutorial02.txt | 6 ------
1 file changed, 6 deletions(-)
diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt
index dd3e86d8ae86..c5c5f8f28850 100644
--- a/docs/intro/tutorial02.txt
+++ b/docs/intro/tutorial02.txt
@@ -352,12 +352,6 @@ representation of the output.
You can improve that by giving that method (in :file:`polls/models.py`) a few
attributes, as follows::
- import datetime
- from django.utils import timezone
- from django.db import models
-
- from polls.models import Poll
-
class Poll(models.Model):
# ...
def was_published_recently(self):
From 9053c6da5fcfe98c05527591f9fd785223eb354a Mon Sep 17 00:00:00 2001
From: Loic Bistuer
Date: Sun, 4 Aug 2013 17:18:17 +0700
Subject: [PATCH 119/944] [1.6.x] Moved get_search_results from BaseModelAdmin
to ModelAdmin.
Refs #15961.
Backport of 470a9bb22d from master.
---
django/contrib/admin/options.py | 62 ++++++++++++++++++---------------
1 file changed, 33 insertions(+), 29 deletions(-)
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 131c0ad88602..b2f90ca4d9f5 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -264,34 +264,6 @@ def get_prepopulated_fields(self, request, obj=None):
"""
return self.prepopulated_fields
- def get_search_results(self, request, queryset, search_term):
- # Apply keyword searches.
- def construct_search(field_name):
- if field_name.startswith('^'):
- return "%s__istartswith" % field_name[1:]
- elif field_name.startswith('='):
- return "%s__iexact" % field_name[1:]
- elif field_name.startswith('@'):
- return "%s__search" % field_name[1:]
- else:
- return "%s__icontains" % field_name
-
- use_distinct = False
- if self.search_fields and search_term:
- orm_lookups = [construct_search(str(search_field))
- for search_field in self.search_fields]
- for bit in search_term.split():
- or_queries = [models.Q(**{orm_lookup: bit})
- for orm_lookup in orm_lookups]
- queryset = queryset.filter(reduce(operator.or_, or_queries))
- if not use_distinct:
- for search_spec in orm_lookups:
- if lookup_needs_distinct(self.opts, search_spec):
- use_distinct = True
- break
-
- return queryset, use_distinct
-
def get_queryset(self, request):
"""
Returns a QuerySet of all model instances that can be edited by the
@@ -767,11 +739,43 @@ def get_list_filter(self, request):
"""
return self.list_filter
+ def get_search_results(self, request, queryset, search_term):
+ """
+ Returns a tuple containing a queryset to implement the search,
+ and a boolean indicating if the results may contain duplicates.
+ """
+ # Apply keyword searches.
+ def construct_search(field_name):
+ if field_name.startswith('^'):
+ return "%s__istartswith" % field_name[1:]
+ elif field_name.startswith('='):
+ return "%s__iexact" % field_name[1:]
+ elif field_name.startswith('@'):
+ return "%s__search" % field_name[1:]
+ else:
+ return "%s__icontains" % field_name
+
+ use_distinct = False
+ search_fields = self.get_search_fields(request)
+ if search_fields and search_term:
+ orm_lookups = [construct_search(str(search_field))
+ for search_field in search_fields]
+ for bit in search_term.split():
+ or_queries = [models.Q(**{orm_lookup: bit})
+ for orm_lookup in orm_lookups]
+ queryset = queryset.filter(reduce(operator.or_, or_queries))
+ if not use_distinct:
+ for search_spec in orm_lookups:
+ if lookup_needs_distinct(self.opts, search_spec):
+ use_distinct = True
+ break
+
+ return queryset, use_distinct
+
def get_preserved_filters(self, request):
"""
Returns the preserved filters querystring.
"""
-
match = request.resolver_match
if self.preserve_filters and match:
opts = self.model._meta
From 90bdb42702327a4ef74e74d2e123da0731a90c4c Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sun, 4 Aug 2013 07:30:30 -0400
Subject: [PATCH 120/944] [1.6.x] Fixed backport error in previous commit; refs
#15961
---
django/contrib/admin/options.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index b2f90ca4d9f5..8c88506a15a7 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -756,10 +756,9 @@ def construct_search(field_name):
return "%s__icontains" % field_name
use_distinct = False
- search_fields = self.get_search_fields(request)
- if search_fields and search_term:
+ if self.search_fields and search_term:
orm_lookups = [construct_search(str(search_field))
- for search_field in search_fields]
+ for search_field in self.search_fields]
for bit in search_term.split():
or_queries = [models.Q(**{orm_lookup: bit})
for orm_lookup in orm_lookups]
From 77293f9354774e2631087935f1550c674093c433 Mon Sep 17 00:00:00 2001
From: Justin Michalicek
Date: Fri, 2 Aug 2013 14:24:26 -0400
Subject: [PATCH 121/944] [1.6.x] Fixed #20855 -- Added documentation of
current_app and extra_context params to django.contrib.auth views
refs #5298 and refs #8342
Backport of 61ecb5f48a from master
---
docs/topics/auth/default.txt | 101 +++++++++++++++++++++++++++++++----
1 file changed, 92 insertions(+), 9 deletions(-)
diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt
index b7f679bf2856..e7dd5a964f38 100644
--- a/docs/topics/auth/default.txt
+++ b/docs/topics/auth/default.txt
@@ -561,13 +561,33 @@ Most built-in authentication views provide a URL name for easier reference. See
patterns.
-.. function:: login(request, [template_name, redirect_field_name, authentication_form])
+.. function:: login(request, [template_name, redirect_field_name, authentication_form, current_app, extra_context])
**URL name:** ``login``
See :doc:`the URL documentation ` for details on using
named URL patterns.
+ **Optional arguments:**
+
+ * ``template_name``: The name of a template to display for the view used to
+ log the user in. Defaults to :file:`registration/login.html`.
+
+ * ``redirect_field_name``: The name of a ``GET`` field containing the
+ URL to redirect to after login. Overrides ``next`` if the given
+ ``GET`` parameter is passed.
+
+ * ``authentication_form``: A callable (typically just a form class) to
+ use for authentication. Defaults to
+ :class:`~django.contrib.auth.forms.AuthenticationForm`.
+
+ * ``current_app``: A hint indicating which application contains the current
+ view. See the :ref:`namespaced URL resolution strategy
+ ` for more information.
+
+ * ``extra_context``: A dictionary of context data that will be added to the
+ default context data passed to the template.
+
Here's what ``django.contrib.auth.views.login`` does:
* If called via ``GET``, it displays a login form that POSTs to the
@@ -657,7 +677,7 @@ patterns.
.. _site framework docs: ../sites/
-.. function:: logout(request, [next_page, template_name, redirect_field_name])
+.. function:: logout(request, [next_page, template_name, redirect_field_name, current_app, extra_context])
Logs a user out.
@@ -675,6 +695,13 @@ patterns.
URL to redirect to after log out. Overrides ``next_page`` if the given
``GET`` parameter is passed.
+ * ``current_app``: A hint indicating which application contains the current
+ view. See the :ref:`namespaced URL resolution strategy
+ ` for more information.
+
+ * ``extra_context``: A dictionary of context data that will be added to the
+ default context data passed to the template.
+
**Template context:**
* ``title``: The string "Logged out", localized.
@@ -691,7 +718,14 @@ patterns.
:attr:`request.META['SERVER_NAME'] `.
For more on sites, see :doc:`/ref/contrib/sites`.
-.. function:: logout_then_login(request[, login_url])
+ * ``current_app``: A hint indicating which application contains the current
+ view. See the :ref:`namespaced URL resolution strategy
+ ` for more information.
+
+ * ``extra_context``: A dictionary of context data that will be added to the
+ default context data passed to the template.
+
+.. function:: logout_then_login(request[, login_url, current_app, extra_context])
Logs a user out, then redirects to the login page.
@@ -702,7 +736,14 @@ patterns.
* ``login_url``: The URL of the login page to redirect to.
Defaults to :setting:`settings.LOGIN_URL ` if not supplied.
-.. function:: password_change(request[, template_name, post_change_redirect, password_change_form])
+ * ``current_app``: A hint indicating which application contains the current
+ view. See the :ref:`namespaced URL resolution strategy
+ ` for more information.
+
+ * ``extra_context``: A dictionary of context data that will be added to the
+ default context data passed to the template.
+
+.. function:: password_change(request[, template_name, post_change_redirect, password_change_form, current_app, extra_context])
Allows a user to change their password.
@@ -722,11 +763,18 @@ patterns.
actually changing the user's password. Defaults to
:class:`~django.contrib.auth.forms.PasswordChangeForm`.
+ * ``current_app``: A hint indicating which application contains the current
+ view. See the :ref:`namespaced URL resolution strategy
+ ` for more information.
+
+ * ``extra_context``: A dictionary of context data that will be added to the
+ default context data passed to the template.
+
**Template context:**
* ``form``: The password change form (see ``password_change_form`` above).
-.. function:: password_change_done(request[, template_name])
+.. function:: password_change_done(request[, template_name, current_app, extra_context])
The page shown after a user has changed their password.
@@ -738,7 +786,14 @@ patterns.
Defaults to :file:`registration/password_change_done.html` if not
supplied.
-.. function:: password_reset(request[, is_admin_site, template_name, email_template_name, password_reset_form, token_generator, post_reset_redirect, from_email])
+ * ``current_app``: A hint indicating which application contains the current
+ view. See the :ref:`namespaced URL resolution strategy
+ ` for more information.
+
+ * ``extra_context``: A dictionary of context data that will be added to the
+ default context data passed to the template.
+
+.. function:: password_reset(request[, is_admin_site, template_name, email_template_name, password_reset_form, token_generator, post_reset_redirect, from_email, current_app, extra_context])
Allows a user to reset their password by generating a one-time use link
that can be used to reset the password, and sending that link to the
@@ -794,6 +849,13 @@ patterns.
* ``from_email``: A valid email address. By default Django uses
the :setting:`DEFAULT_FROM_EMAIL`.
+ * ``current_app``: A hint indicating which application contains the current
+ view. See the :ref:`namespaced URL resolution strategy
+ ` for more information.
+
+ * ``extra_context``: A dictionary of context data that will be added to the
+ default context data passed to the template.
+
**Template context:**
* ``form``: The form (see ``password_reset_form`` above) for resetting
@@ -838,7 +900,7 @@ patterns.
single line plain text string.
-.. function:: password_reset_done(request[, template_name])
+.. function:: password_reset_done(request[, template_name, current_app, extra_context])
The page shown after a user has been emailed a link to reset their
password. This view is called by default if the :func:`password_reset` view
@@ -852,7 +914,14 @@ patterns.
Defaults to :file:`registration/password_reset_done.html` if not
supplied.
-.. function:: password_reset_confirm(request[, uidb64, token, template_name, token_generator, set_password_form, post_reset_redirect])
+ * ``current_app``: A hint indicating which application contains the current
+ view. See the :ref:`namespaced URL resolution strategy
+ ` for more information.
+
+ * ``extra_context``: A dictionary of context data that will be added to the
+ default context data passed to the template.
+
+.. function:: password_reset_confirm(request[, uidb64, token, template_name, token_generator, set_password_form, post_reset_redirect, current_app, extra_context])
Presents a form for entering a new password.
@@ -883,6 +952,13 @@ patterns.
* ``post_reset_redirect``: URL to redirect after the password reset
done. Defaults to ``None``.
+ * ``current_app``: A hint indicating which application contains the current
+ view. See the :ref:`namespaced URL resolution strategy
+ ` for more information.
+
+ * ``extra_context``: A dictionary of context data that will be added to the
+ default context data passed to the template.
+
**Template context:**
* ``form``: The form (see ``set_password_form`` above) for setting the
@@ -891,7 +967,7 @@ patterns.
* ``validlink``: Boolean, True if the link (combination of ``uidb64`` and
``token``) is valid or unused yet.
-.. function:: password_reset_complete(request[,template_name])
+.. function:: password_reset_complete(request[,template_name, current_app, extra_context])
Presents a view which informs the user that the password has been
successfully changed.
@@ -903,6 +979,13 @@ patterns.
* ``template_name``: The full name of a template to display the view.
Defaults to :file:`registration/password_reset_complete.html`.
+ * ``current_app``: A hint indicating which application contains the current
+ view. See the :ref:`namespaced URL resolution strategy
+ ` for more information.
+
+ * ``extra_context``: A dictionary of context data that will be added to the
+ default context data passed to the template.
+
Helper functions
----------------
From 74205c4a3c587a9eedceef1b98cd4b0d529a7510 Mon Sep 17 00:00:00 2001
From: Daniele Procida
Date: Fri, 2 Aug 2013 14:26:19 +0100
Subject: [PATCH 122/944] [1.6.x] Fixed #20842 and #20845 - Added a note on
order_by() and improved prefetch_related() docs.
Backport of e8183a8193 from master
---
docs/ref/models/querysets.txt | 101 ++++++++++++++++++++++------------
1 file changed, 66 insertions(+), 35 deletions(-)
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index f9d5d001f830..fec059a1be46 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -110,7 +110,7 @@ described here.
.. admonition:: You can't share pickles between versions
- Pickles of QuerySets are only valid for the version of Django that
+ Pickles of ``QuerySets`` are only valid for the version of Django that
was used to generate them. If you generate a pickle using Django
version N, there is no guarantee that pickle will be readable with
Django version N+1. Pickles should not be used as part of a long-term
@@ -300,14 +300,30 @@ Be cautious when ordering by fields in related models if you are also using
:meth:`distinct()`. See the note in :meth:`distinct` for an explanation of how
related model ordering can change the expected results.
-It is permissible to specify a multi-valued field to order the results by (for
-example, a :class:`~django.db.models.ManyToManyField` field). Normally
-this won't be a sensible thing to do and it's really an advanced usage
-feature. However, if you know that your queryset's filtering or available data
-implies that there will only be one ordering piece of data for each of the main
-items you are selecting, the ordering may well be exactly what you want to do.
-Use ordering on multi-valued fields with care and make sure the results are
-what you expect.
+.. note::
+ It is permissible to specify a multi-valued field to order the results by
+ (for example, a :class:`~django.db.models.ManyToManyField` field, or the
+ reverse relation of a :class:`~django.db.models.ForeignKey` field).
+
+ Consider this case::
+
+ class Event(Model):
+ parent = models.ForeignKey('self', related_name='children')
+ date = models.DateField()
+
+ Event.objects.order_by('children__date')
+
+ Here, there could potentially be multiple ordering data for each ``Event``;
+ each ``Event`` with multiple ``children`` will be returned multiple times
+ into the new ``QuerySet`` that ``order_by()`` creates. In other words,
+ using ``order_by()`` on the ``QuerySet`` could return more items than you
+ were working on to begin with - which is probably neither expected nor
+ useful.
+
+ Thus, take care when using multi-valued field to order the results. **If**
+ you can be sure that there will only be one ordering piece of data for each
+ of the items you're ordering, this approach should not present problems. If
+ not, make sure the results are what you expect.
There's no way to specify whether ordering should be case sensitive. With
respect to case-sensitivity, Django will order results however your database
@@ -388,7 +404,7 @@ field names, the database will only compare the specified field names.
.. note::
When you specify field names, you *must* provide an ``order_by()`` in the
- QuerySet, and the fields in ``order_by()`` must start with the fields in
+ ``QuerySet``, and the fields in ``order_by()`` must start with the fields in
``distinct()``, in the same order.
For example, ``SELECT DISTINCT ON (a)`` gives you the first row for each
@@ -805,8 +821,8 @@ stop the deluge of database queries that is caused by accessing related objects,
but the strategy is quite different.
``select_related`` works by creating a SQL join and including the fields of the
-related object in the SELECT statement. For this reason, ``select_related`` gets
-the related objects in the same database query. However, to avoid the much
+related object in the ``SELECT`` statement. For this reason, ``select_related``
+gets the related objects in the same database query. However, to avoid the much
larger result set that would result from joining across a 'many' relationship,
``select_related`` is limited to single-valued relationships - foreign key and
one-to-one.
@@ -835,39 +851,54 @@ For example, suppose you have these models::
return u"%s (%s)" % (self.name, u", ".join([topping.name
for topping in self.toppings.all()]))
-and run this code::
+and run::
>>> Pizza.objects.all()
[u"Hawaiian (ham, pineapple)", u"Seafood (prawns, smoked salmon)"...
-The problem with this code is that it will run a query on the Toppings table for
-**every** item in the Pizza ``QuerySet``. Using ``prefetch_related``, this can
-be reduced to two:
+The problem with this is that every time ``Pizza.__unicode__()`` asks for
+``self.toppings.all()`` it has to query the database, so
+``Pizza.objects.all()`` will run a query on the Toppings table for **every**
+item in the Pizza ``QuerySet``.
+
+We can reduce to just two queries using ``prefetch_related``:
>>> Pizza.objects.all().prefetch_related('toppings')
-All the relevant toppings will be fetched in a single query, and used to make
-``QuerySets`` that have a pre-filled cache of the relevant results. These
-``QuerySets`` are then used in the ``self.toppings.all()`` calls.
+This implies a ``self.toppings.all()`` for each ``Pizza``; now each time
+``self.toppings.all()`` is called, instead of having to go to the database for
+the items, it will find them in a prefetched ``QuerySet`` cache that was
+populated in a single query.
+
+That is, all the relevant toppings will have been fetched in a single query,
+and used to make ``QuerySets`` that have a pre-filled cache of the relevant
+results; these ``QuerySets`` are then used in the ``self.toppings.all()`` calls.
+
+The additional queries in ``prefetch_related()`` are executed after the
+``QuerySet`` has begun to be evaluated and the primary query has been executed.
-The additional queries are executed after the QuerySet has begun to be evaluated
-and the primary query has been executed. Note that the result cache of the
-primary QuerySet and all specified related objects will then be fully loaded
-into memory, which is often avoided in other cases - even after a query has been
-executed in the database, QuerySet normally tries to make uses of chunking
-between the database to avoid loading all objects into memory before you need
-them.
+Note that the result cache of the primary ``QuerySet`` and all specified related
+objects will then be fully loaded into memory. This changes the typical
+behavior of ``QuerySets``, which normally try to avoid loading all objects into
+memory before they are needed, even after a query has been executed in the
+database.
+
+.. note::
-Also remember that, as always with QuerySets, any subsequent chained methods
-which imply a different database query will ignore previously cached results,
-and retrieve data using a fresh database query. So, if you write the following:
+ Remember that, as always with ``QuerySets``, any subsequent chained methods
+ which imply a different database query will ignore previously cached
+ results, and retrieve data using a fresh database query. So, if you write
+ the following:
- >>> pizzas = Pizza.objects.prefetch_related('toppings')
- >>> [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]
+ >>> pizzas = Pizza.objects.prefetch_related('toppings')
+ >>> [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]
-...then the fact that ``pizza.toppings.all()`` has been prefetched will not help
-you - in fact it hurts performance, since you have done a database query that
-you haven't used. So use this feature with caution!
+ ...then the fact that ``pizza.toppings.all()`` has been prefetched will not
+ help you. The ``prefetch_related('toppings')`` implied
+ ``pizza.toppings.all()``, but ``pizza.toppings.filter()`` is a new and
+ different query. The prefetched cache can't help here; in fact it hurts
+ performance, since you have done a database query that you haven't used. So
+ use this feature with caution!
You can also use the normal join syntax to do related fields of related
fields. Suppose we have an additional model to the example above::
@@ -920,7 +951,7 @@ additional queries on the ``ContentType`` table if the relevant rows have not
already been fetched.
``prefetch_related`` in most cases will be implemented using a SQL query that
-uses the 'IN' operator. This means that for a large QuerySet a large 'IN' clause
+uses the 'IN' operator. This means that for a large ``QuerySet`` a large 'IN' clause
could be generated, which, depending on the database, might have performance
problems of its own when it comes to parsing or executing the SQL query. Always
profile for your use case!
From 7a5f7991939b41c63acf06b664d8fe3686eb8ecf Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sun, 4 Aug 2013 14:45:29 -0400
Subject: [PATCH 123/944] [1.6.x] Fixed #20860 -- Removed references to defunct
chicagocrime.org
Backport of 1593a86494 from master
---
docs/ref/contrib/syndication.txt | 26 ++++++++++++--------------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/docs/ref/contrib/syndication.txt b/docs/ref/contrib/syndication.txt
index 80a7afb35fb3..f17b9d823749 100644
--- a/docs/ref/contrib/syndication.txt
+++ b/docs/ref/contrib/syndication.txt
@@ -49,17 +49,17 @@ are views which can be used in your :doc:`URLconf `.
A simple example
----------------
-This simple example, taken from `chicagocrime.org`_, describes a feed of the
-latest five news items::
+This simple example, taken from a hypothetical police beat news site describes
+a feed of the latest five news items::
from django.contrib.syndication.views import Feed
from django.core.urlresolvers import reverse
- from chicagocrime.models import NewsItem
+ from policebeat.models import NewsItem
class LatestEntriesFeed(Feed):
- title = "Chicagocrime.org site news"
+ title = "Police beat site news"
link = "/sitenews/"
- description = "Updates on changes and additions to chicagocrime.org."
+ description = "Updates on changes and additions to police beat central."
def items(self):
return NewsItem.objects.order_by('-pub_date')[:5]
@@ -199,22 +199,20 @@ into those elements.
are responsible for doing all necessary URL quoting and conversion to
ASCII inside the method itself.
-.. _chicagocrime.org: http://www.chicagocrime.org/
-
A complex example
-----------------
The framework also supports more complex feeds, via arguments.
-For example, `chicagocrime.org`_ offers an RSS feed of recent crimes for every
-police beat in Chicago. It'd be silly to create a separate
+For example, a website could offer an RSS feed of recent crimes for every
+police beat in a city. It'd be silly to create a separate
:class:`~django.contrib.syndication.views.Feed` class for each police beat; that
would violate the :ref:`DRY principle ` and would couple data to
programming logic. Instead, the syndication framework lets you access the
arguments passed from your :doc:`URLconf ` so feeds can output
items based on information in the feed's URL.
-On chicagocrime.org, the police-beat feeds are accessible via URLs like this:
+The police beat feeds could be accessible via URLs like this:
* :file:`/beats/613/rss/` -- Returns recent crimes for beat 613.
* :file:`/beats/1424/rss/` -- Returns recent crimes for beat 1424.
@@ -238,7 +236,7 @@ Here's the code for these beat-specific feeds::
return get_object_or_404(Beat, pk=beat_id)
def title(self, obj):
- return "Chicagocrime.org: Crimes for beat %s" % obj.beat
+ return "Police beat central: Crimes for beat %s" % obj.beat
def link(self, obj):
return obj.get_absolute_url()
@@ -339,13 +337,13 @@ URLconf to add the extra versions.
Here's a full example::
from django.contrib.syndication.views import Feed
- from chicagocrime.models import NewsItem
+ from policebeat.models import NewsItem
from django.utils.feedgenerator import Atom1Feed
class RssSiteNewsFeed(Feed):
- title = "Chicagocrime.org site news"
+ title = "Police beat site news"
link = "/sitenews/"
- description = "Updates on changes and additions to chicagocrime.org."
+ description = "Updates on changes and additions to police beat central."
def items(self):
return NewsItem.objects.order_by('-pub_date')[:5]
From 59bf42f79e6f2fce2fc9128a86643cdb80b4b49a Mon Sep 17 00:00:00 2001
From: Julien Phalip
Date: Sun, 4 Aug 2013 17:18:10 -0700
Subject: [PATCH 124/944] [1.6.x] Added a note to the 1.6 release about the new
`--keep-pot` option for `makemessages`. Refs #17008. Backport of
28d3b33c04cc2 from master.
---
docs/releases/1.6.txt | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index ab70be7cfe1e..b545cbcd64f9 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -812,6 +812,10 @@ Miscellaneous
* :meth:`~django.core.validators.validate_email` now accepts email addresses
with ``localhost`` as the domain.
+* The :djadminopt:`--keep-pot` option was added to :djadmin:`makemessages`
+ to prevent django from deleting the temporary .pot file it generates before
+ creating the .po file.
+
Features deprecated in 1.6
==========================
From 7cc91eae7c4efc0108e51a28c472559c77778eaf Mon Sep 17 00:00:00 2001
From: Julien Phalip
Date: Sun, 4 Aug 2013 17:29:55 -0700
Subject: [PATCH 125/944] [1.6.x] Fixed a small formatting issue. Backport
0b071ba7df7394b9 of from master.
---
docs/ref/django-admin.txt | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index 3f70ea3c1db3..9e3d6bc4382e 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -265,8 +265,8 @@ prompts.
The :djadminopt:`--database` option may be used to specify the database
to flush.
---no-initial-data
-~~~~~~~~~~~~~~~~~
+``--no-initial-data``
+~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 1.5
@@ -1154,8 +1154,8 @@ prompts.
The :djadminopt:`--database` option can be used to specify the database to
synchronize.
---no-initial-data
-~~~~~~~~~~~~~~~~~
+``--no-initial-data``
+~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 1.5
From 06f484dcf9984f2a2223f20422e130844c246800 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Mon, 5 Aug 2013 08:14:27 -0400
Subject: [PATCH 126/944] [1.6.x] Fixed #20862 -- Updated startproject
MIDDLEWARE_CLASSES in docs.
Thanks Keryn Knight.
Backport of 26c4bd38ac from master
---
docs/topics/http/middleware.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt
index 503d4322e049..6bb7ccb8f8be 100644
--- a/docs/topics/http/middleware.txt
+++ b/docs/topics/http/middleware.txt
@@ -28,11 +28,12 @@ here's the default value created by :djadmin:`django-admin.py startproject
`::
MIDDLEWARE_CLASSES = (
- 'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
A Django installation doesn't require any middleware —
From ea7bef318fd42411e3db057e888367ef52fb23c3 Mon Sep 17 00:00:00 2001
From: Jimmy Song
Date: Sun, 4 Aug 2013 19:40:16 +0000
Subject: [PATCH 127/944] [1.6.x] Fixed #20859 - Clarified Model.clean()
example.
Backport of 94d7fed775 from master
---
AUTHORS | 1 +
docs/ref/models/instances.txt | 22 +++++++++++++---------
2 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/AUTHORS b/AUTHORS
index ba1f4036e99a..6b232deb7054 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -540,6 +540,7 @@ answer newbie questions, and generally made Django that much better:
smurf@smurf.noris.de
Vsevolod Solovyov
George Song
+ Jimmy Song
sopel
Leo Soto
Wiliam Alves de Souza
diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt
index f06866d9a17c..c9c01d56796e 100644
--- a/docs/ref/models/instances.txt
+++ b/docs/ref/models/instances.txt
@@ -140,15 +140,19 @@ attributes on your model if desired. For instance, you could use it to
automatically provide a value for a field, or to do validation that requires
access to more than a single field::
- def clean(self):
- import datetime
- from django.core.exceptions import ValidationError
- # Don't allow draft entries to have a pub_date.
- if self.status == 'draft' and self.pub_date is not None:
- raise ValidationError('Draft entries may not have a publication date.')
- # Set the pub_date for published items if it hasn't been set already.
- if self.status == 'published' and self.pub_date is None:
- self.pub_date = datetime.date.today()
+ import datetime
+ from django.core.exceptions import ValidationError
+ from django.db import models
+
+ class Article(models.Model):
+ ...
+ def clean(self):
+ # Don't allow draft entries to have a pub_date.
+ if self.status == 'draft' and self.pub_date is not None:
+ raise ValidationError('Draft entries may not have a publication date.')
+ # Set the pub_date for published items if it hasn't been set already.
+ if self.status == 'published' and self.pub_date is None:
+ self.pub_date = datetime.date.today()
Any :exc:`~django.core.exceptions.ValidationError` exceptions raised by
``Model.clean()`` will be stored in a special key error dictionary key,
From 891cdf120a0751b71eb534e14f814f574904ec44 Mon Sep 17 00:00:00 2001
From: Tim Heap
Date: Mon, 5 Aug 2013 10:01:49 -0400
Subject: [PATCH 128/944] [1.6.x] Fixed #20850 -- Added
MultiWidget.needs_multipart_form
Backport of 75c87e2d38 from master
---
django/forms/widgets.py | 5 +++++
tests/forms_tests/tests/test_widgets.py | 8 ++++++++
2 files changed, 13 insertions(+)
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index 38d1b99b0db6..d74710bcbacc 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -838,6 +838,11 @@ def __deepcopy__(self, memo):
obj.widgets = copy.deepcopy(self.widgets)
return obj
+ @property
+ def needs_multipart_form(self):
+ return any(w.needs_multipart_form for w in self.widgets)
+
+
class SplitDateTimeWidget(MultiWidget):
"""
A Widget that splits datetime input into two boxes.
diff --git a/tests/forms_tests/tests/test_widgets.py b/tests/forms_tests/tests/test_widgets.py
index 4c566dc8e433..06ee2d6c53ce 100644
--- a/tests/forms_tests/tests/test_widgets.py
+++ b/tests/forms_tests/tests/test_widgets.py
@@ -849,6 +849,14 @@ def format_output(self, rendered_widgets):
w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})), attrs={'id': 'bar'})
self.assertHTMLEqual(w.render('name', ['john', 'lennon']), ' ')
+ # Test needs_multipart_form=True if any widget needs it
+ w = MyMultiWidget(widgets=(TextInput(), FileInput()))
+ self.assertTrue(w.needs_multipart_form)
+
+ # Test needs_multipart_form=False if no widget needs it
+ w = MyMultiWidget(widgets=(TextInput(), TextInput()))
+ self.assertFalse(w.needs_multipart_form)
+
def test_splitdatetime(self):
w = SplitDateTimeWidget()
self.assertHTMLEqual(w.render('date', ''), '')
From 363b81c59cc0252f605de7d30433934b4662cfb3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?=
Date: Tue, 6 Aug 2013 09:58:07 +0300
Subject: [PATCH 129/944] [1.6.x] Fixed ordering related test failure
Also PEP8 + python_2_unicode_compatible cleanup done.
Backport of 263b873599 from master
---
tests/foreign_object/models.py | 31 ++++++++++++++++++++-----------
tests/foreign_object/tests.py | 20 +++++++++++++-------
2 files changed, 33 insertions(+), 18 deletions(-)
diff --git a/tests/foreign_object/models.py b/tests/foreign_object/models.py
index 4c58fd15bd9e..d8c3bc10d4ff 100644
--- a/tests/foreign_object/models.py
+++ b/tests/foreign_object/models.py
@@ -5,14 +5,15 @@
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import get_language
+@python_2_unicode_compatible
class Country(models.Model):
# Table Column Fields
name = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.name
-
+@python_2_unicode_compatible
class Person(models.Model):
# Table Column Fields
name = models.CharField(max_length=128)
@@ -26,9 +27,10 @@ class Person(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Group(models.Model):
# Table Column Fields
name = models.CharField(max_length=128)
@@ -38,10 +40,11 @@ class Group(models.Model):
class Meta:
ordering = ('name',)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Membership(models.Model):
# Table Column Fields
membership_country = models.ForeignKey(Country)
@@ -51,17 +54,19 @@ class Membership(models.Model):
group_id = models.IntegerField()
# Relation Fields
- person = models.ForeignObject(Person,
+ person = models.ForeignObject(
+ Person,
from_fields=['membership_country', 'person_id'],
to_fields=['person_country_id', 'id'])
- group = models.ForeignObject(Group,
+ group = models.ForeignObject(
+ Group,
from_fields=['membership_country', 'group_id'],
to_fields=['group_country', 'id'])
class Meta:
ordering = ('date_joined', 'invite_reason')
- def __unicode__(self):
+ def __str__(self):
return "%s is a member of %s" % (self.person.name, self.group.name)
@@ -73,17 +78,20 @@ class Friendship(models.Model):
to_friend_id = models.IntegerField()
# Relation Fields
- from_friend = models.ForeignObject(Person,
+ from_friend = models.ForeignObject(
+ Person,
from_fields=['from_friend_country', 'from_friend_id'],
to_fields=['person_country_id', 'id'],
related_name='from_friend')
- to_friend_country = models.ForeignObject(Country,
+ to_friend_country = models.ForeignObject(
+ Country,
from_fields=['to_friend_country_id'],
to_fields=['id'],
related_name='to_friend_country')
- to_friend = models.ForeignObject(Person,
+ to_friend = models.ForeignObject(
+ Person,
from_fields=['to_friend_country_id', 'to_friend_id'],
to_fields=['person_country_id', 'id'],
related_name='to_friend')
@@ -159,5 +167,6 @@ class ArticleTag(models.Model):
name = models.CharField(max_length=255)
class ArticleIdea(models.Model):
- articles = models.ManyToManyField(Article, related_name="ideas", related_query_name="idea_things")
+ articles = models.ManyToManyField(Article, related_name="ideas",
+ related_query_name="idea_things")
name = models.CharField(max_length=255)
diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py
index 4a7cc3e61359..cd81cc68a24a 100644
--- a/tests/foreign_object/tests.py
+++ b/tests/foreign_object/tests.py
@@ -9,6 +9,9 @@
from django.core.exceptions import FieldError
from django import forms
+# Note that these tests are testing internal implementation details.
+# ForeignObject is not part of public API.
+
class MultiColumnFKTests(TestCase):
def setUp(self):
# Creating countries
@@ -142,9 +145,9 @@ def test_select_related_foreignkey_forward_works(self):
Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat)
with self.assertNumQueries(1):
- people = [m.person for m in Membership.objects.select_related('person')]
+ people = [m.person for m in Membership.objects.select_related('person').order_by('pk')]
- normal_people = [m.person for m in Membership.objects.all()]
+ normal_people = [m.person for m in Membership.objects.all().order_by('pk')]
self.assertEqual(people, normal_people)
def test_prefetch_foreignkey_forward_works(self):
@@ -152,19 +155,22 @@ def test_prefetch_foreignkey_forward_works(self):
Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat)
with self.assertNumQueries(2):
- people = [m.person for m in Membership.objects.prefetch_related('person')]
+ people = [
+ m.person for m in Membership.objects.prefetch_related('person').order_by('pk')]
- normal_people = [m.person for m in Membership.objects.all()]
+ normal_people = [m.person for m in Membership.objects.order_by('pk')]
self.assertEqual(people, normal_people)
def test_prefetch_foreignkey_reverse_works(self):
Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia)
Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat)
with self.assertNumQueries(2):
- membership_sets = [list(p.membership_set.all())
- for p in Person.objects.prefetch_related('membership_set')]
+ membership_sets = [
+ list(p.membership_set.all())
+ for p in Person.objects.prefetch_related('membership_set').order_by('pk')]
- normal_membership_sets = [list(p.membership_set.all()) for p in Person.objects.all()]
+ normal_membership_sets = [list(p.membership_set.all())
+ for p in Person.objects.order_by('pk')]
self.assertEqual(membership_sets, normal_membership_sets)
def test_m2m_through_forward_returns_valid_members(self):
From 80b95a2b1fc8c8425a81a23706af971061dc238f Mon Sep 17 00:00:00 2001
From: Alex Cucu
Date: Tue, 26 Feb 2013 11:51:56 +0200
Subject: [PATCH 130/944] [1.6.x] Fixed #19918 -- Modified select_for_update to
run on the write database.
Backport of 1c64a0f29e from master
---
django/db/models/query.py | 1 +
tests/select_for_update/tests.py | 14 +++++++++++++-
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 27a87a3f65bc..31b79ed0a2c0 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -635,6 +635,7 @@ def select_for_update(self, **kwargs):
# Default to false for nowait
nowait = kwargs.pop('nowait', False)
obj = self._clone()
+ obj._for_write = True
obj.query.select_for_update = True
obj.query.select_for_update_nowait = nowait
return obj
diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py
index f087ca123af5..edef455154df 100644
--- a/tests/select_for_update/tests.py
+++ b/tests/select_for_update/tests.py
@@ -4,12 +4,14 @@
import time
from django.conf import settings
-from django.db import transaction, connection
+from django.db import transaction, connection, router
from django.db.utils import ConnectionHandler, DEFAULT_DB_ALIAS, DatabaseError
from django.test import (TransactionTestCase, skipIfDBFeature,
skipUnlessDBFeature)
from django.utils import unittest
+from multiple_database.tests import TestRouter
+
from .models import Person
# Some tests require threading, which might not be available. So create a
@@ -268,3 +270,13 @@ def test_transaction_dirty_managed(self):
"""
people = list(Person.objects.select_for_update())
self.assertTrue(transaction.is_dirty())
+
+ @skipUnlessDBFeature('has_select_for_update')
+ def test_select_for_update_on_multidb(self):
+ old_routers = router.routers
+ try:
+ router.routers = [TestRouter()]
+ query = Person.objects.select_for_update()
+ self.assertEqual(router.db_for_write(Person), query.db)
+ finally:
+ router.routers = old_routers
From 4c90081b5b296bc2d7d9689609bfe1857e35b091 Mon Sep 17 00:00:00 2001
From: Leandro Regueiro
Date: Thu, 18 Apr 2013 22:04:00 +0200
Subject: [PATCH 131/944] [1.6.x] Added section labels in cache docs
Backport of 709cd2c4b7 from master
---
docs/topics/cache.txt | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt
index 2352770badad..a2491f21983a 100644
--- a/docs/topics/cache.txt
+++ b/docs/topics/cache.txt
@@ -39,6 +39,8 @@ Django also works well with "upstream" caches, such as `Squid
caches that you don't directly control but to which you can provide hints (via
HTTP headers) about which parts of your site should be cached, and how.
+.. _setting-up-the-cache:
+
Setting up the cache
====================
@@ -152,6 +154,8 @@ permanent storage -- they're all intended to be solutions for caching, not
storage -- but we point this out here because memory-based caching is
particularly temporary.
+.. _database-caching:
+
Database caching
----------------
From e3e0cf8a0f75ae18bc401182bfbdea2c27bb0244 Mon Sep 17 00:00:00 2001
From: Collin Anderson
Date: Tue, 6 Aug 2013 12:38:31 -0400
Subject: [PATCH 132/944] [1.6.x] Fixed #20865 -- Fixed raw_id_fields to work
with callable limit_choices_to.
Backport of d53e574676 from master
---
django/contrib/admin/widgets.py | 2 ++
tests/admin_widgets/tests.py | 7 +++++++
2 files changed, 9 insertions(+)
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index eeae846320d7..c4b15cdd6ac9 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -116,6 +116,8 @@ def url_params_from_lookup_dict(lookups):
if lookups and hasattr(lookups, 'items'):
items = []
for k, v in lookups.items():
+ if callable(v):
+ v = v()
if isinstance(v, (tuple, list)):
v = ','.join([str(x) for x in v])
elif isinstance(v, bool):
diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
index b4d6da034bb7..d275c7669e95 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -226,6 +226,13 @@ def test_url_params_from_lookup_dict_any_iterable(self):
self.assertEqual(lookup1, {'color__in': 'red,blue'})
self.assertEqual(lookup1, lookup2)
+ def test_url_params_from_lookup_dict_callable(self):
+ def my_callable():
+ return 'works'
+ lookup1 = widgets.url_params_from_lookup_dict({'myfield': my_callable})
+ lookup2 = widgets.url_params_from_lookup_dict({'myfield': my_callable()})
+ self.assertEqual(lookup1, lookup2)
+
class FilteredSelectMultipleWidgetTest(DjangoTestCase):
def test_render(self):
From 6264e39c346a1b563dc0cbc31c5fb75830e032cb Mon Sep 17 00:00:00 2001
From: Christopher Medrela
Date: Wed, 7 Aug 2013 20:57:56 +0200
Subject: [PATCH 133/944] [1.6.x] Added note to GenericIPAddressField
documentation
refs #20484
Backport of fb26c4996a from master
---
docs/ref/models/fields.txt | 3 +++
1 file changed, 3 insertions(+)
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index 869996643f2f..7159ed8b709d 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -874,6 +874,9 @@ are converted to lowercase.
``192.0.2.1``. Default is disabled. Can only be used
when ``protocol`` is set to ``'both'``.
+If you allow for blank values, you have to allow for null values since blank
+values are stored as null.
+
``NullBooleanField``
--------------------
From 25ce1e0e0a37d7be7ceff1e37bcb9ebd78a959d9 Mon Sep 17 00:00:00 2001
From: Daniele Procida
Date: Wed, 7 Aug 2013 18:47:07 +0100
Subject: [PATCH 134/944] [1.6.x] Fixed #20870 -- Documented
django.utils.functional.cached_property
Backport of 7a2296eb5b from master
---
docs/ref/utils.txt | 44 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
index 6451a657456c..6e2c84c61baa 100644
--- a/docs/ref/utils.txt
+++ b/docs/ref/utils.txt
@@ -447,6 +447,50 @@ Atom1Feed
.. module:: django.utils.functional
:synopsis: Functional programming tools.
+.. class:: cached_property(object)
+
+ The ``@cached_property`` decorator caches the result of a method with a
+ single ``self`` argument as a property. The cached result will persist as
+ long as the instance does.
+
+ Consider a typical case, where a view might need to call a model's method
+ to perform some computation, before placing the model instance into the
+ context, where the template might invoke the method once more::
+
+ # the model
+ class Person(models.Model):
+
+ def friends(self):
+ # expensive computation
+ ...
+ return friends
+
+ # in the view:
+ if person.friends():
+
+ # in the template:
+ {% for friend in person.friends %}
+
+ ``friends()`` will be called twice. Since the instance ``person`` in
+ the view and the template are the same, ``@cached_property`` can avoid
+ that::
+
+ from django.utils.functional import cached_property
+
+ @cached_property
+ def friends(self):
+ # expensive computation
+ ...
+ return friends
+
+ Note that as the method is now a property, in Python code it will need to
+ be invoked appropriately::
+
+ # in the view:
+ if person.friends:
+
+ You may clear the cached result using ``del person.friends``.
+
.. function:: allow_lazy(func, *resultclasses)
Django offers many utility functions (particularly in ``django.utils``) that
From 9cc7407f2c6b457c1b8f1e7c2a57e7b065d9bc41 Mon Sep 17 00:00:00 2001
From: Daniele Procida
Date: Thu, 8 Aug 2013 13:16:48 +0100
Subject: [PATCH 135/944] [1.6.x] Added more on @cached_property, refs #20870
Backport of 7e6af9d40c from master
---
docs/ref/utils.txt | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
index 6e2c84c61baa..22d54d137f6e 100644
--- a/docs/ref/utils.txt
+++ b/docs/ref/utils.txt
@@ -450,8 +450,9 @@ Atom1Feed
.. class:: cached_property(object)
The ``@cached_property`` decorator caches the result of a method with a
- single ``self`` argument as a property. The cached result will persist as
- long as the instance does.
+ single ``self`` argument as a property. The cached result will persist
+ as long as the instance does, so if the instance is passed around and the
+ function subsequently invoked, the cached result will be returned.
Consider a typical case, where a view might need to call a model's method
to perform some computation, before placing the model instance into the
@@ -471,7 +472,7 @@ Atom1Feed
# in the template:
{% for friend in person.friends %}
- ``friends()`` will be called twice. Since the instance ``person`` in
+ Here, ``friends()`` will be called twice. Since the instance ``person`` in
the view and the template are the same, ``@cached_property`` can avoid
that::
@@ -489,7 +490,20 @@ Atom1Feed
# in the view:
if person.friends:
- You may clear the cached result using ``del person.friends``.
+ The cached value can be treated like an ordinary attribute of the instance::
+
+ # clear it, requiring re-computation next time it's called
+ del person.friends # or delattr(person, "friends")
+
+ # set a value manually, that will persist on the instance until cleared
+ person.friends = ["Huckleberry Finn", "Tom Sawyer"]
+
+ As well as offering potential performance advantages, ``@cached_property``
+ can ensure that an attribute's value does not change unexpectedly over the
+ life of an instance. This could occur with a method whose computation is
+ based on ``datetime.now()``, or simply if a change were saved to the
+ database by some other process in the brief interval between subsequent
+ invocations of a method on the same instance.
.. function:: allow_lazy(func, *resultclasses)
From 7836bd502c4d5d65728c1e9eb58bc137b316c6e5 Mon Sep 17 00:00:00 2001
From: Jaime Irurzun
Date: Thu, 8 Aug 2013 12:45:06 +0100
Subject: [PATCH 136/944] [1.6.x] Clarifed meaning of
models.User.is_authenticated()
Backport of f96fe3cd1e from master
---
docs/ref/contrib/auth.txt | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt
index afbc6ec048d3..e1d89219bc9b 100644
--- a/docs/ref/contrib/auth.txt
+++ b/docs/ref/contrib/auth.txt
@@ -114,11 +114,13 @@ Methods
Always returns ``True`` (as opposed to
``AnonymousUser.is_authenticated()`` which always returns ``False``).
This is a way to tell if the user has been authenticated. This does not
- imply any permissions, and doesn't check if the user is active - it
- only indicates that ``request.user`` has been populated by the
- :class:`~django.contrib.auth.middleware.AuthenticationMiddleware` with
- a :class:`~django.contrib.auth.models.User` object representing the
- currently logged-in user.
+ imply any permissions, and doesn't check if the user is active or has
+ a valid session. Even though normally you will call this method on
+ ``request.user`` to find out whether it has been populated by the
+ :class:`~django.contrib.auth.middleware.AuthenticationMiddleware`
+ (representing the currently logged-in user), you should know this method
+ returns ``True`` for any :class:`~django.contrib.auth.models.User`
+ instance.
.. method:: get_full_name()
From 7c5d43eea0d48f1c521c8f16b2d713b222407f90 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 8 Aug 2013 14:13:39 -0400
Subject: [PATCH 137/944] [1.6.x] Added an anchor for django.forms.Form.clean
in docs
Backport of 8442268869 from master
---
docs/ref/forms/validation.txt | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt
index 87f9741c1bd8..0831a54c74e2 100644
--- a/docs/ref/forms/validation.txt
+++ b/docs/ref/forms/validation.txt
@@ -365,6 +365,8 @@ write a cleaning method that operates on the ``recipients`` field, like so::
Cleaning and validating fields that depend on each other
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. method:: django.forms.Form.clean
+
Suppose we add another requirement to our contact form: if the ``cc_myself``
field is ``True``, the ``subject`` must contain the word ``"help"``. We are
performing validation on more than one field at a time, so the form's
From 2cd1439c06b2834942545a5679fa50691a736d50 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 9 Aug 2013 16:02:05 -0400
Subject: [PATCH 138/944] [1.6.x] Fixed #20868 -- Added an email to
django-announce as a security step.
Thanks garrison for the report.
Backport of 5737c57d95 from master
---
docs/internals/security.txt | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/docs/internals/security.txt b/docs/internals/security.txt
index 7121ff31ecae..96e114106615 100644
--- a/docs/internals/security.txt
+++ b/docs/internals/security.txt
@@ -106,8 +106,12 @@ On the day of disclosure, we will take the following steps:
relevant patches and new releases, and crediting the reporter of
the issue (if the reporter wishes to be publicly identified).
+4. Post a notice to the `django-announce`_ mailing list that links to the blog
+ post.
+
.. _the Python Package Index: http://pypi.python.org/pypi
.. _the official Django development blog: https://www.djangoproject.com/weblog/
+.. _django-announce: http://groups.google.com/group/django-announce
If a reported issue is believed to be particularly time-sensitive --
due to a known exploit in the wild, for example -- the time between
@@ -212,4 +216,4 @@ If you are added to the notification list, security-related emails
will be sent to you by Django's release manager, and all notification
emails will be signed with the same key used to sign Django releases;
that key has the ID ``0x3684C0C08C8B2AE1``, and is available from most
-commonly-used keyservers.
\ No newline at end of file
+commonly-used keyservers.
From 43f12f368b141444b47aacea2635afbc28ed46d0 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sat, 10 Aug 2013 18:08:05 -0400
Subject: [PATCH 139/944] [1.6.x] Fixed #20890 -- Added missing import in
class-based view docs.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Thanks André Augusto.
Backport of ab680725bf from master
---
docs/topics/class-based-views/intro.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/topics/class-based-views/intro.txt b/docs/topics/class-based-views/intro.txt
index a65b8879215b..5986ff2ea7d2 100644
--- a/docs/topics/class-based-views/intro.txt
+++ b/docs/topics/class-based-views/intro.txt
@@ -198,6 +198,7 @@ A similar class-based view might look like::
from django.http import HttpResponseRedirect
from django.shortcuts import render
+ from django.views.generic.base import View
from .forms import MyForm
From 4f470f5186d4c61a4b1c95bb9346d03b7c2f3aca Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Mon, 12 Aug 2013 13:20:58 -0400
Subject: [PATCH 140/944] [1.6.x] Added missing release notes for older
versions of Django
Backport of 3f6cc33cff from master
---
docs/releases/1.3.3.txt | 11 ++++++
docs/releases/1.3.4.txt | 37 +++++++++++++++++
docs/releases/1.3.5.txt | 60 ++++++++++++++++++++++++++++
docs/releases/1.3.6.txt | 78 ++++++++++++++++++++++++++++++++++++
docs/releases/1.3.7.txt | 13 ++++++
docs/releases/1.4.2.txt | 9 +++--
docs/releases/1.4.3.txt | 60 ++++++++++++++++++++++++++++
docs/releases/1.4.4.txt | 88 +++++++++++++++++++++++++++++++++++++++++
docs/releases/1.4.5.txt | 13 ++++++
docs/releases/index.txt | 8 ++++
10 files changed, 373 insertions(+), 4 deletions(-)
create mode 100644 docs/releases/1.3.3.txt
create mode 100644 docs/releases/1.3.4.txt
create mode 100644 docs/releases/1.3.5.txt
create mode 100644 docs/releases/1.3.6.txt
create mode 100644 docs/releases/1.3.7.txt
create mode 100644 docs/releases/1.4.3.txt
create mode 100644 docs/releases/1.4.4.txt
create mode 100644 docs/releases/1.4.5.txt
diff --git a/docs/releases/1.3.3.txt b/docs/releases/1.3.3.txt
new file mode 100644
index 000000000000..437cbfb41258
--- /dev/null
+++ b/docs/releases/1.3.3.txt
@@ -0,0 +1,11 @@
+==========================
+Django 1.3.3 release notes
+==========================
+
+*August 1, 2012*
+
+Following Monday's security release of :doc:`Django 1.3.2 `,
+we began receiving reports that one of the fixes applied was breaking Python
+2.4 compatibility for Django 1.3. Since Python 2.4 is a supported Python
+version for that release series, this release fixes compatibility with
+Python 2.4.
diff --git a/docs/releases/1.3.4.txt b/docs/releases/1.3.4.txt
new file mode 100644
index 000000000000..3a174b3d448e
--- /dev/null
+++ b/docs/releases/1.3.4.txt
@@ -0,0 +1,37 @@
+==========================
+Django 1.3.4 release notes
+==========================
+
+*October 17, 2012*
+
+This is the fourth release in the Django 1.3 series.
+
+Host header poisoning
+---------------------
+
+Some parts of Django -- independent of end-user-written applications -- make
+use of full URLs, including domain name, which are generated from the HTTP Host
+header. Some attacks against this are beyond Django's ability to control, and
+require the web server to be properly configured; Django's documentation has
+for some time contained notes advising users on such configuration.
+
+Django's own built-in parsing of the Host header is, however, still vulnerable,
+as was reported to us recently. The Host header parsing in Django 1.3.3 and
+Django 1.4.1 -- specifically, ``django.http.HttpRequest.get_host()`` -- was
+incorrectly handling username/password information in the header. Thus, for
+example, the following Host header would be accepted by Django when running on
+"validsite.com"::
+
+ Host: validsite.com:random@evilsite.com
+
+Using this, an attacker can cause parts of Django -- particularly the
+password-reset mechanism -- to generate and display arbitrary URLs to users.
+
+To remedy this, the parsing in ``HttpRequest.get_host()`` is being modified;
+Host headers which contain potentially dangerous content (such as
+username/password pairs) now raise the exception
+:exc:`django.core.exceptions.SuspiciousOperation`.
+
+Details of this issue were initially posted online as a `security advisory`_.
+
+.. _security advisory: https://www.djangoproject.com/weblog/2012/oct/17/security/
diff --git a/docs/releases/1.3.5.txt b/docs/releases/1.3.5.txt
new file mode 100644
index 000000000000..65c403209d13
--- /dev/null
+++ b/docs/releases/1.3.5.txt
@@ -0,0 +1,60 @@
+==========================
+Django 1.3.5 release notes
+==========================
+
+*December 10, 2012*
+
+Django 1.3.5 addresses two security issues present in previous Django releases
+in the 1.3 series.
+
+Please be aware that this security release is slightly different from previous
+ones. Both issues addressed here have been dealt with in prior security updates
+to Django. In one case, we have received ongoing reports of problems, and in
+the other we've chosen to take further steps to tighten up Django's code in
+response to independent discovery of potential problems from multiple sources.
+
+Host header poisoning
+---------------------
+
+Several earlier Django security releases focused on the issue of poisoning the
+HTTP Host header, causing Django to generate URLs pointing to arbitrary,
+potentially-malicious domains.
+
+In response to further input received and reports of continuing issues
+following the previous release, we're taking additional steps to tighten Host
+header validation. Rather than attempt to accommodate all features HTTP
+supports here, Django's Host header validation attempts to support a smaller,
+but far more common, subset:
+
+* Hostnames must consist of characters [A-Za-z0-9] plus hyphen ('-') or dot
+ ('.').
+* IP addresses -- both IPv4 and IPv6 -- are permitted.
+* Port, if specified, is numeric.
+
+Any deviation from this will now be rejected, raising the exception
+:exc:`django.core.exceptions.SuspiciousOperation`.
+
+Redirect poisoning
+------------------
+
+Also following up on a previous issue: in July of this year, we made changes to
+Django's HTTP redirect classes, performing additional validation of the scheme
+of the URL to redirect to (since, both within Django's own supplied
+applications and many third-party applications, accepting a user-supplied
+redirect target is a common pattern).
+
+Since then, two independent audits of the code turned up further potential
+problems. So, similar to the Host-header issue, we are taking steps to provide
+tighter validation in response to reported problems (primarily with third-party
+applications, but to a certain extent also within Django itself). This comes in
+two parts:
+
+1. A new utility function, ``django.utils.http.is_safe_url``, is added; this
+function takes a URL and a hostname, and checks that the URL is either
+relative, or if absolute matches the supplied hostname. This function is
+intended for use whenever user-supplied redirect targets are accepted, to
+ensure that such redirects cannot lead to arbitrary third-party sites.
+
+2. All of Django's own built-in views -- primarily in the authentication system
+-- which allow user-supplied redirect targets now use ``is_safe_url`` to
+validate the supplied URL.
diff --git a/docs/releases/1.3.6.txt b/docs/releases/1.3.6.txt
new file mode 100644
index 000000000000..d55199a88240
--- /dev/null
+++ b/docs/releases/1.3.6.txt
@@ -0,0 +1,78 @@
+==========================
+Django 1.3.6 release notes
+==========================
+
+*February 19, 2013*
+
+Django 1.3.6 fixes four security issues present in previous Django releases in
+the 1.3 series.
+
+This is the sixth bugfix/security release in the Django 1.3 series.
+
+
+Host header poisoning
+---------------------
+
+Some parts of Django -- independent of end-user-written applications -- make
+use of full URLs, including domain name, which are generated from the HTTP Host
+header. Django's documentation has for some time contained notes advising users
+on how to configure webservers to ensure that only valid Host headers can reach
+the Django application. However, it has been reported to us that even with the
+recommended webserver configurations there are still techniques available for
+tricking many common webservers into supplying the application with an
+incorrect and possibly malicious Host header.
+
+For this reason, Django 1.3.6 adds a new setting, ``ALLOWED_HOSTS``, which
+should contain an explicit list of valid host/domain names for this site. A
+request with a Host header not matching an entry in this list will raise
+``SuspiciousOperation`` if ``request.get_host()`` is called. For full details
+see the documentation for the :setting:`ALLOWED_HOSTS` setting.
+
+The default value for this setting in Django 1.3.6 is ``['*']`` (matching any
+host), for backwards-compatibility, but we strongly encourage all sites to set
+a more restrictive value.
+
+This host validation is disabled when ``DEBUG`` is ``True`` or when running tests.
+
+
+XML deserialization
+-------------------
+
+The XML parser in the Python standard library is vulnerable to a number of
+attacks via external entities and entity expansion. Django uses this parser for
+deserializing XML-formatted database fixtures. The fixture deserializer is not
+intended for use with untrusted data, but in order to err on the side of safety
+in Django 1.3.6 the XML deserializer refuses to parse an XML document with a
+DTD (DOCTYPE definition), which closes off these attack avenues.
+
+These issues in the Python standard library are CVE-2013-1664 and
+CVE-2013-1665. More information available `from the Python security team`_.
+
+Django's XML serializer does not create documents with a DTD, so this should
+not cause any issues with the typical round-trip from ``dumpdata`` to
+``loaddata``, but if you feed your own XML documents to the ``loaddata``
+management command, you will need to ensure they do not contain a DTD.
+
+.. _from the Python security team: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html
+
+
+Formset memory exhaustion
+-------------------------
+
+Previous versions of Django did not validate or limit the form-count data
+provided by the client in a formset's management form, making it possible to
+exhaust a server's available memory by forcing it to create very large numbers
+of forms.
+
+In Django 1.3.6, all formsets have a strictly-enforced maximum number of forms
+(1000 by default, though it can be set higher via the ``max_num`` formset
+factory argument).
+
+
+Admin history view information leakage
+--------------------------------------
+
+In previous versions of Django, an admin user without change permission on a
+model could still view the unicode representation of instances via their admin
+history log. Django 1.3.6 now limits the admin history log view for an object
+to users with change permission for that model.
diff --git a/docs/releases/1.3.7.txt b/docs/releases/1.3.7.txt
new file mode 100644
index 000000000000..3cccfcfb1c5e
--- /dev/null
+++ b/docs/releases/1.3.7.txt
@@ -0,0 +1,13 @@
+==========================
+Django 1.3.7 release notes
+==========================
+
+*February 20, 2013*
+
+Django 1.3.7 corrects a packaging problem with yesterday's :doc:`1.3.6 release
+`.
+
+The release contained stray ``.pyc`` files that caused "bad magic number"
+errors when running with some versions of Python. This releases corrects this,
+and also fixes a bad documentation link in the project template ``settings.py``
+file generated by ``manage.py startproject``.
diff --git a/docs/releases/1.4.2.txt b/docs/releases/1.4.2.txt
index 07eec39764d0..a6150f56c376 100644
--- a/docs/releases/1.4.2.txt
+++ b/docs/releases/1.4.2.txt
@@ -17,7 +17,7 @@ for some time contained notes advising users on such configuration.
Django's own built-in parsing of the Host header is, however, still vulnerable,
as was reported to us recently. The Host header parsing in Django 1.3.3 and
-Django 1.4.1 -- specifically, django.http.HttpRequest.get_host() -- was
+Django 1.4.1 -- specifically, ``django.http.HttpRequest.get_host()`` -- was
incorrectly handling username/password information in the header. Thus, for
example, the following Host header would be accepted by Django when running on
"validsite.com"::
@@ -27,9 +27,10 @@ example, the following Host header would be accepted by Django when running on
Using this, an attacker can cause parts of Django -- particularly the
password-reset mechanism -- to generate and display arbitrary URLs to users.
-To remedy this, the parsing in HttpRequest.get_host() is being modified; Host
-headers which contain potentially dangerous content (such as username/password
-pairs) now raise the exception django.core.exceptions.SuspiciousOperation
+To remedy this, the parsing in ``HttpRequest.get_host()`` is being modified;
+Host headers which contain potentially dangerous content (such as
+username/password pairs) now raise the exception
+:exc:`django.core.exceptions.SuspiciousOperation`.
Details of this issue were initially posted online as a `security advisory`_.
diff --git a/docs/releases/1.4.3.txt b/docs/releases/1.4.3.txt
new file mode 100644
index 000000000000..aadf623c3c74
--- /dev/null
+++ b/docs/releases/1.4.3.txt
@@ -0,0 +1,60 @@
+==========================
+Django 1.4.3 release notes
+==========================
+
+*December 10, 2012*
+
+Django 1.4.3 addresses two security issues present in previous Django releases
+in the 1.4 series.
+
+Please be aware that this security release is slightly different from previous
+ones. Both issues addressed here have been dealt with in prior security updates
+to Django. In one case, we have received ongoing reports of problems, and in
+the other we've chosen to take further steps to tighten up Django's code in
+response to independent discovery of potential problems from multiple sources.
+
+Host header poisoning
+---------------------
+
+Several earlier Django security releases focused on the issue of poisoning the
+HTTP Host header, causing Django to generate URLs pointing to arbitrary,
+potentially-malicious domains.
+
+In response to further input received and reports of continuing issues
+following the previous release, we're taking additional steps to tighten Host
+header validation. Rather than attempt to accommodate all features HTTP
+supports here, Django's Host header validation attempts to support a smaller,
+but far more common, subset:
+
+* Hostnames must consist of characters [A-Za-z0-9] plus hyphen ('-') or dot
+ ('.').
+* IP addresses -- both IPv4 and IPv6 -- are permitted.
+* Port, if specified, is numeric.
+
+Any deviation from this will now be rejected, raising the exception
+:exc:`django.core.exceptions.SuspiciousOperation`.
+
+Redirect poisoning
+------------------
+
+Also following up on a previous issue: in July of this year, we made changes to
+Django's HTTP redirect classes, performing additional validation of the scheme
+of the URL to redirect to (since, both within Django's own supplied
+applications and many third-party applications, accepting a user-supplied
+redirect target is a common pattern).
+
+Since then, two independent audits of the code turned up further potential
+problems. So, similar to the Host-header issue, we are taking steps to provide
+tighter validation in response to reported problems (primarily with third-party
+applications, but to a certain extent also within Django itself). This comes in
+two parts:
+
+1. A new utility function, ``django.utils.http.is_safe_url``, is added; this
+function takes a URL and a hostname, and checks that the URL is either
+relative, or if absolute matches the supplied hostname. This function is
+intended for use whenever user-supplied redirect targets are accepted, to
+ensure that such redirects cannot lead to arbitrary third-party sites.
+
+2. All of Django's own built-in views -- primarily in the authentication system
+-- which allow user-supplied redirect targets now use ``is_safe_url`` to
+validate the supplied URL.
diff --git a/docs/releases/1.4.4.txt b/docs/releases/1.4.4.txt
new file mode 100644
index 000000000000..c5fcbc3e39ce
--- /dev/null
+++ b/docs/releases/1.4.4.txt
@@ -0,0 +1,88 @@
+==========================
+Django 1.4.4 release notes
+==========================
+
+*February 19, 2013*
+
+Django 1.4.4 fixes four security issues present in previous Django releases in
+the 1.4 series, as well as several other bugs and numerous documentation
+improvements.
+
+This is the fourth bugfix/security release in the Django 1.4 series.
+
+
+Host header poisoning
+---------------------
+
+Some parts of Django -- independent of end-user-written applications -- make
+use of full URLs, including domain name, which are generated from the HTTP Host
+header. Django's documentation has for some time contained notes advising users
+on how to configure webservers to ensure that only valid Host headers can reach
+the Django application. However, it has been reported to us that even with the
+recommended webserver configurations there are still techniques available for
+tricking many common webservers into supplying the application with an
+incorrect and possibly malicious Host header.
+
+For this reason, Django 1.4.4 adds a new setting, ``ALLOWED_HOSTS``, containing
+an explicit list of valid host/domain names for this site. A request with a
+Host header not matching an entry in this list will raise
+``SuspiciousOperation`` if ``request.get_host()`` is called. For full details
+see the documentation for the :setting:`ALLOWED_HOSTS` setting.
+
+The default value for this setting in Django 1.4.4 is ``['*']`` (matching any
+host), for backwards-compatibility, but we strongly encourage all sites to set
+a more restrictive value.
+
+This host validation is disabled when ``DEBUG`` is ``True`` or when running tests.
+
+
+XML deserialization
+-------------------
+
+The XML parser in the Python standard library is vulnerable to a number of
+attacks via external entities and entity expansion. Django uses this parser for
+deserializing XML-formatted database fixtures. This deserializer is not
+intended for use with untrusted data, but in order to err on the side of safety
+in Django 1.4.4 the XML deserializer refuses to parse an XML document with a
+DTD (DOCTYPE definition), which closes off these attack avenues.
+
+These issues in the Python standard library are CVE-2013-1664 and
+CVE-2013-1665. More information available `from the Python security team`_.
+
+Django's XML serializer does not create documents with a DTD, so this should
+not cause any issues with the typical round-trip from ``dumpdata`` to
+``loaddata``, but if you feed your own XML documents to the ``loaddata``
+management command, you will need to ensure they do not contain a DTD.
+
+.. _from the Python security team: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html
+
+
+Formset memory exhaustion
+-------------------------
+
+Previous versions of Django did not validate or limit the form-count data
+provided by the client in a formset's management form, making it possible to
+exhaust a server's available memory by forcing it to create very large numbers
+of forms.
+
+In Django 1.4.4, all formsets have a strictly-enforced maximum number of forms
+(1000 by default, though it can be set higher via the ``max_num`` formset
+factory argument).
+
+
+Admin history view information leakage
+--------------------------------------
+
+In previous versions of Django, an admin user without change permission on a
+model could still view the unicode representation of instances via their admin
+history log. Django 1.4.4 now limits the admin history log view for an object
+to users with change permission for that model.
+
+
+Other bugfixes and changes
+==========================
+
+* Prevented transaction state from leaking from one request to the next (#19707).
+* Changed a SQL command syntax to be MySQL 4 compatible (#19702).
+* Added backwards-compatibility with old unsalted MD5 passwords (#18144).
+* Numerous documentation improvements and fixes.
diff --git a/docs/releases/1.4.5.txt b/docs/releases/1.4.5.txt
new file mode 100644
index 000000000000..9ba5235f79a4
--- /dev/null
+++ b/docs/releases/1.4.5.txt
@@ -0,0 +1,13 @@
+==========================
+Django 1.4.5 release notes
+==========================
+
+*February 20, 2013*
+
+Django 1.4.5 corrects a packaging problem with yesterday's :doc:`1.4.4 release
+`.
+
+The release contained stray ``.pyc`` files that caused "bad magic number"
+errors when running with some versions of Python. This releases corrects this,
+and also fixes a bad documentation link in the project template ``settings.py``
+file generated by ``manage.py startproject``.
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index 85b3d211a807..4bd6ffb558ba 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -37,6 +37,9 @@ Final releases
.. toctree::
:maxdepth: 1
+ 1.4.5
+ 1.4.4
+ 1.4.3
1.4.2
1.4.1
1.4
@@ -46,6 +49,11 @@ Final releases
.. toctree::
:maxdepth: 1
+ 1.3.7
+ 1.3.6
+ 1.3.5
+ 1.3.4
+ 1.3.3
1.3.2
1.3.1
1.3
From 79594b40c087c19fecc72af042c835b11a519b78 Mon Sep 17 00:00:00 2001
From: Jacob Kaplan-Moss
Date: Tue, 13 Aug 2013 11:05:41 -0500
Subject: [PATCH 141/944] Fixed is_safe_url() to reject URLs that use a scheme
other than HTTP/S.
This is a security fix; disclosure to follow shortly.
---
django/contrib/auth/tests/test_views.py | 8 ++++++--
django/utils/http.py | 7 ++++---
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/django/contrib/auth/tests/test_views.py b/django/contrib/auth/tests/test_views.py
index d5f0c40a57c3..42ceea36d388 100644
--- a/django/contrib/auth/tests/test_views.py
+++ b/django/contrib/auth/tests/test_views.py
@@ -447,7 +447,8 @@ def test_security_check(self, password='password'):
for bad_url in ('http://example.com',
'https://example.com',
'ftp://exampel.com',
- '//example.com'):
+ '//example.com',
+ 'javascript:alert("XSS")'):
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
'url': login_url,
@@ -468,6 +469,7 @@ def test_security_check(self, password='password'):
'/view?param=ftp://exampel.com',
'view/?param=//example.com',
'https:///',
+ 'HTTPS:///',
'//testserver/',
'/url%20with%20spaces/'): # see ticket #12534
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
@@ -662,7 +664,8 @@ def test_security_check(self, password='password'):
for bad_url in ('http://example.com',
'https://example.com',
'ftp://exampel.com',
- '//example.com'):
+ '//example.com',
+ 'javascript:alert("XSS")'):
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
'url': logout_url,
'next': REDIRECT_FIELD_NAME,
@@ -681,6 +684,7 @@ def test_security_check(self, password='password'):
'/view?param=ftp://exampel.com',
'view/?param=//example.com',
'https:///',
+ 'HTTPS:///',
'//testserver/',
'/url%20with%20spaces/'): # see ticket #12534
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
diff --git a/django/utils/http.py b/django/utils/http.py
index 4647d8984733..ffaf4e965707 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -253,11 +253,12 @@ def same_origin(url1, url2):
def is_safe_url(url, host=None):
"""
Return ``True`` if the url is a safe redirection (i.e. it doesn't point to
- a different host).
+ a different host and uses a safe scheme).
Always returns ``False`` on an empty url.
"""
if not url:
return False
- netloc = urllib_parse.urlparse(url)[1]
- return not netloc or netloc == host
+ url_info = urllib_parse.urlparse(url)
+ return (not url_info.netloc or url_info.netloc == host) and \
+ (not url_info.scheme or url_info.scheme in ['http', 'https'])
From bfbae15c669beab335400ab51a060e3d7d8e4c7a Mon Sep 17 00:00:00 2001
From: Jacob Kaplan-Moss
Date: Tue, 13 Aug 2013 11:06:00 -0500
Subject: [PATCH 142/944] Apply autoescaping to AdminURLFieldWidget.
This is a security fix; disclosure to follow shortly.
---
django/contrib/admin/widgets.py | 4 ++--
tests/admin_widgets/tests.py | 20 +++++++++++++-------
2 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index c4b15cdd6ac9..5773db6394db 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -305,9 +305,9 @@ 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))}
+ final_attrs = {'href': smart_urlquote(value)}
html = format_html(
- '
'
)
def test_render_quoting(self):
+ # WARNING: Don't use assertHTMLEqual in that testcase!
+ # assertHTMLEqual will get rid of some escapes which are tested here!
w = widgets.AdminURLFieldWidget()
- self.assertHTMLEqual(
- conditional_escape(w.render('test', 'http://example.com/some text')),
- '
'
)
From 57dc238d0e4783bf0532663af585a99572089c3b Mon Sep 17 00:00:00 2001
From: Jacob Kaplan-Moss
Date: Tue, 13 Aug 2013 11:12:07 -0500
Subject: [PATCH 143/944] Bumped version numbers for 1.6b2.
---
django/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/__init__.py b/django/__init__.py
index d0c5d77bfa17..d7cda5e18315 100644
--- a/django/__init__.py
+++ b/django/__init__.py
@@ -1,4 +1,4 @@
-VERSION = (1, 6, 0, 'beta', 1)
+VERSION = (1, 6, 0, 'beta', 2)
def get_version(*args, **kwargs):
# Don't litter django/__init__.py with all the get_version stuff.
From 529afa7138c6bd5efb4c6ac995597d2f25611d73 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Tue, 13 Aug 2013 11:16:30 -0500
Subject: [PATCH 144/944] Added 1.4.6/1.5.2 release notes.
---
docs/releases/1.4.6.txt | 31 +++++++++++++++++++++
docs/releases/1.5.2.txt | 62 +++++++++++++++++++++++++++++++++++++++++
docs/releases/index.txt | 2 ++
3 files changed, 95 insertions(+)
create mode 100644 docs/releases/1.4.6.txt
create mode 100644 docs/releases/1.5.2.txt
diff --git a/docs/releases/1.4.6.txt b/docs/releases/1.4.6.txt
new file mode 100644
index 000000000000..575e9fa75a53
--- /dev/null
+++ b/docs/releases/1.4.6.txt
@@ -0,0 +1,31 @@
+==========================
+Django 1.4.6 release notes
+==========================
+
+*August 13, 2013*
+
+Django 1.4.6 fixes one security issue present in previous Django releases in
+the 1.4 series, as well as one other bug.
+
+This is the sixth bugfix/security release in the Django 1.4 series.
+
+Mitigated possible XSS attack via user-supplied redirect URLs
+-------------------------------------------------------------
+
+Django relies on user input in some cases (e.g.
+:func:`django.contrib.auth.views.login`, :mod:`django.contrib.comments`, and
+:doc:`i18n `) to redirect the user to an "on success" URL.
+The security checks for these redirects (namely
+``django.util.http.is_safe_url()``) didn't check if the scheme is ``http(s)``
+and as such allowed ``javascript:...`` URLs to be entered. If a developer
+relied on ``is_safe_url()`` to provide safe redirect targets and put such a
+URL into a link, he could suffer from a XSS attack. This bug doesn't affect
+Django currently, since we only put this URL into the ``Location`` response
+header and browsers seem to ignore JavaScript there.
+
+Bugfixes
+========
+
+* Fixed an obscure bug with the :func:`~django.test.utils.override_settings`
+ decorator. If you hit an ``AttributeError: 'Settings' object has no attribute
+ '_original_allowed_hosts'`` exception, it's probably fixed (#20636).
diff --git a/docs/releases/1.5.2.txt b/docs/releases/1.5.2.txt
new file mode 100644
index 000000000000..710f16555ce2
--- /dev/null
+++ b/docs/releases/1.5.2.txt
@@ -0,0 +1,62 @@
+==========================
+Django 1.5.2 release notes
+==========================
+
+*August 13, 2013*
+
+This is Django 1.5.2, a bugfix and security release for Django 1.5.
+
+Mitigated possible XSS attack via user-supplied redirect URLs
+-------------------------------------------------------------
+
+Django relies on user input in some cases (e.g.
+:func:`django.contrib.auth.views.login`, :mod:`django.contrib.comments`, and
+:doc:`i18n `) to redirect the user to an "on success" URL.
+The security checks for these redirects (namely
+``django.util.http.is_safe_url()``) didn't check if the scheme is ``http(s)``
+and as such allowed ``javascript:...`` URLs to be entered. If a developer
+relied on ``is_safe_url()`` to provide safe redirect targets and put such a
+URL into a link, he could suffer from a XSS attack. This bug doesn't affect
+Django currently, since we only put this URL into the ``Location`` response
+header and browsers seem to ignore JavaScript there.
+
+XSS vulnerability in :mod:`django.contrib.admin`
+------------------------------------------------
+
+If a :class:`~django.db.models.URLField` is used in Django 1.5, it displays the
+current value of the field and a link to the target on the admin change page.
+The display routine of this widget was flawed and allowed for XSS.
+
+Bugfixes
+========
+
+* Fixed a crash with :meth:`~django.db.models.query.QuerySet.prefetch_related`
+ (#19607) as well as some ``pickle`` regressions with ``prefetch_related``
+ (#20157 and #20257).
+* Fixed a regression in :mod:`django.contrib.gis` in the Google Map output on
+ Python 3 (#20773).
+* Made ``DjangoTestSuiteRunner.setup_databases`` properly handle aliases for
+ the default database (#19940) and prevented ``teardown_databases`` from
+ attempting to tear down aliases (#20681).
+* Fixed the ``django.core.cache.backends.memcached.MemcachedCache`` backend's
+ ``get_many()`` method on Python 3 (#20722).
+* Fixed :mod:`django.contrib.humanize` translation syntax errors. Affected
+ languages: Mexican Spanish, Mongolian, Romanian, Turkish (#20695).
+* Added support for wheel packages (#19252).
+* The CSRF token now rotates when a user logs in.
+* Some Python 3 compatibility fixes including #20212 and #20025.
+* Fixed some rare cases where :meth:`~django.db.models.query.QuerySet.get`
+ exceptions recursed infinitely (#20278).
+* :djadmin:`makemessages` no longer crashes with ``UnicodeDecodeError``
+ (#20354).
+* Fixed ``geojson`` detection with Spatialite.
+* :meth:`~django.test.SimpleTestCase.assertContains` once again works with
+ binary content (#20237).
+* Fixed :class:`~django.db.models.ManyToManyField` if it has a unicode ``name``
+ parameter (#20207).
+* Ensured that the WSGI request's path is correctly based on the
+ ``SCRIPT_NAME`` environment variable or the :setting:`FORCE_SCRIPT_NAME`
+ setting, regardless of whether or not either has a trailing slash (#20169).
+* Fixed an obscure bug with the :func:`~django.test.utils.override_settings`
+ decorator. If you hit an ``AttributeError: 'Settings' object has no attribute
+ '_original_allowed_hosts'`` exception, it's probably fixed (#20636).
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index 4bd6ffb558ba..9cda564a9321 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -29,6 +29,7 @@ Final releases
.. toctree::
:maxdepth: 1
+ 1.5.2
1.5.1
1.5
@@ -37,6 +38,7 @@ Final releases
.. toctree::
:maxdepth: 1
+ 1.4.6
1.4.5
1.4.4
1.4.3
From 39db994790d3e98b7558618c3dbfb2163e1a530c Mon Sep 17 00:00:00 2001
From: Loic Bistuer
Date: Tue, 13 Aug 2013 17:38:29 +0700
Subject: [PATCH 145/944] [1.6.x] Fixed overflow for the "Recent Actions"
widget on the admin index.
Previously the CSS targeted "li.changelink" and therefore didn't
work for the "add" and "delete" actions.
Refs #14868.
Backport of 33fc083b0d from master
---
django/contrib/admin/static/admin/css/dashboard.css | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/django/contrib/admin/static/admin/css/dashboard.css b/django/contrib/admin/static/admin/css/dashboard.css
index ceefe1525fe1..05808bcb0a49 100644
--- a/django/contrib/admin/static/admin/css/dashboard.css
+++ b/django/contrib/admin/static/admin/css/dashboard.css
@@ -23,8 +23,8 @@ ul.actionlist li {
list-style-type: none;
}
-ul.actionlist li.changelink {
+ul.actionlist li {
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
-}
\ No newline at end of file
+}
From b720ece06505bfaea1d1f4ee46963a34c032fe25 Mon Sep 17 00:00:00 2001
From: Florian Apolloner
Date: Tue, 13 Aug 2013 22:22:46 +0200
Subject: [PATCH 146/944] [1.6.x] Removed unneeded conditional_escapes from the
testsuite.
Backport of 4e50e4065489f06d72eed8ce2d9ae98639be5a93 from master.
---
tests/admin_widgets/tests.py | 41 ++++++++++++++++++------------------
1 file changed, 20 insertions(+), 21 deletions(-)
diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
index 3184c7150bfd..5cf1ddf933f2 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -15,7 +15,6 @@
from django.test.utils import override_settings
from django.utils import six
from django.utils import translation
-from django.utils.html import conditional_escape
from django.utils.unittest import TestCase
from . import models
@@ -238,14 +237,14 @@ class FilteredSelectMultipleWidgetTest(DjangoTestCase):
def test_render(self):
w = widgets.FilteredSelectMultiple('test', False)
self.assertHTMLEqual(
- conditional_escape(w.render('test', 'test')),
+ w.render('test', 'test'),
'\n' % admin_static_prefix()
)
def test_stacked_render(self):
w = widgets.FilteredSelectMultiple('test', True)
self.assertHTMLEqual(
- conditional_escape(w.render('test', 'test')),
+ w.render('test', 'test'),
'\n' % admin_static_prefix()
)
@@ -257,13 +256,13 @@ def test_attrs(self):
"""
w = widgets.AdminDateWidget()
self.assertHTMLEqual(
- conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
+ w.render('test', datetime(2007, 12, 1, 9, 30)),
'',
)
# pass attrs to widget
w = widgets.AdminDateWidget(attrs={'size': 20, 'class': 'myDateField'})
self.assertHTMLEqual(
- conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
+ w.render('test', datetime(2007, 12, 1, 9, 30)),
'',
)
@@ -275,13 +274,13 @@ def test_attrs(self):
"""
w = widgets.AdminTimeWidget()
self.assertHTMLEqual(
- conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
+ w.render('test', datetime(2007, 12, 1, 9, 30)),
'',
)
# pass attrs to widget
w = widgets.AdminTimeWidget(attrs={'size': 20, 'class': 'myTimeField'})
self.assertHTMLEqual(
- conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
+ w.render('test', datetime(2007, 12, 1, 9, 30)),
'',
)
@@ -289,7 +288,7 @@ class AdminSplitDateTimeWidgetTest(DjangoTestCase):
def test_render(self):
w = widgets.AdminSplitDateTime()
self.assertHTMLEqual(
- conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
+ w.render('test', datetime(2007, 12, 1, 9, 30)),
'
' % { 'STORAGE_URL': default_storage.url('') },
)
self.assertHTMLEqual(
- conditional_escape(w.render('test', SimpleUploadedFile('test', b'content'))),
+ w.render('test', SimpleUploadedFile('test', b'content')),
'',
)
@@ -371,7 +370,7 @@ def test_render(self):
w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
- conditional_escape(w.render('test', band.pk, attrs={})),
+ w.render('test', band.pk, attrs={}),
'Linkin Park' % dict(admin_static_prefix(), bandpk=band.pk)
)
@@ -399,7 +398,7 @@ def test_fk_related_model_not_in_admin(self):
w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
- conditional_escape(w.render('honeycomb_widget', big_honeycomb.pk, attrs={})),
+ w.render('honeycomb_widget', big_honeycomb.pk, attrs={}),
' Honeycomb object' % {'hcombpk': big_honeycomb.pk}
)
@@ -412,7 +411,7 @@ def test_fk_to_self_model_not_in_admin(self):
w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
- conditional_escape(w.render('individual_widget', subject1.pk, attrs={})),
+ w.render('individual_widget', subject1.pk, attrs={}),
' Individual object' % {'subj1pk': subject1.pk}
)
@@ -444,12 +443,12 @@ def test_render(self):
w = widgets.ManyToManyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
- conditional_escape(w.render('test', [m1.pk, m2.pk], attrs={})),
+ w.render('test', [m1.pk, m2.pk], attrs={}),
'' % dict(admin_static_prefix(), m1pk=m1.pk, m2pk=m2.pk)
)
self.assertHTMLEqual(
- conditional_escape(w.render('test', [m1.pk])),
+ w.render('test', [m1.pk]),
'' % dict(admin_static_prefix(), m1pk=m1.pk)
)
@@ -465,12 +464,12 @@ def test_m2m_related_model_not_in_admin(self):
w = widgets.ManyToManyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
- conditional_escape(w.render('company_widget1', [c1.pk, c2.pk], attrs={})),
+ w.render('company_widget1', [c1.pk, c2.pk], attrs={}),
'' % {'c1pk': c1.pk, 'c2pk': c2.pk}
)
self.assertHTMLEqual(
- conditional_escape(w.render('company_widget2', [c1.pk])),
+ w.render('company_widget2', [c1.pk]),
'' % {'c1pk': c1.pk}
)
From 8bd5251fd5ff32e19315f51edf3cff24bc5ccc5c Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Wed, 14 Aug 2013 12:57:55 -0400
Subject: [PATCH 147/944] [1.6.x] Added some doc links for
django.contrib.messages
Backport of b6178fa24b from master
---
docs/ref/contrib/messages.txt | 11 +++++++----
docs/ref/settings.txt | 4 +++-
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt
index 608c37bb7fd3..2014c7c18371 100644
--- a/docs/ref/contrib/messages.txt
+++ b/docs/ref/contrib/messages.txt
@@ -54,20 +54,21 @@ Storage backends
The messages framework can use different backends to store temporary messages.
-Django provides three built-in storage classes:
+Django provides three built-in storage classes in
+:mod:`django.contrib.messages`:
-.. class:: django.contrib.messages.storage.session.SessionStorage
+.. class:: storage.session.SessionStorage
This class stores all messages inside of the request's session. Therefore
it requires Django's ``contrib.sessions`` application.
-.. class:: django.contrib.messages.storage.cookie.CookieStorage
+.. class:: storage.cookie.CookieStorage
This class stores the message data in a cookie (signed with a secret hash
to prevent manipulation) to persist notifications across requests. Old
messages are dropped if the cookie data size would exceed 2048 bytes.
-.. class:: django.contrib.messages.storage.fallback.FallbackStorage
+.. class:: storage.fallback.FallbackStorage
This class first uses ``CookieStorage``, and falls back to using
``SessionStorage`` for the messages that could not fit in a single cookie.
@@ -83,6 +84,8 @@ path, for example::
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
+.. class:: storage.base.BaseStorage
+
To write your own storage class, subclass the ``BaseStorage`` class in
``django.contrib.messages.storage.base`` and implement the ``_get`` and
``_store`` methods.
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index f700f6e19182..1916f6377fb1 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -2241,7 +2241,9 @@ Controls where Django stores message data. Valid values are:
See :ref:`message storage backends ` for more details.
-The backends that use cookies -- ``CookieStorage`` and ``FallbackStorage`` --
+The backends that use cookies --
+:class:`~django.contrib.messages.storage.cookie.CookieStorage` and
+:class:`~django.contrib.messages.storage.fallback.FallbackStorage` --
use the value of :setting:`SESSION_COOKIE_DOMAIN` when setting their cookies.
.. setting:: MESSAGE_TAGS
From 52741004f682a82b5e27593fd5ab740bd22d09f6 Mon Sep 17 00:00:00 2001
From: Alasdair Nicol
Date: Thu, 15 Aug 2013 00:27:16 +0100
Subject: [PATCH 148/944] [1.6.x] Updated docs following deprecation of
django.views.defaults.shortcut
Follows 3f2befc
Backport of 354009d67e from master
---
docs/internals/deprecation.txt | 13 +++++--------
docs/ref/contrib/sites.txt | 7 ++++---
2 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 5513c7996608..52dcd404ca2a 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -370,8 +370,11 @@ these changes.
* Remove the backward compatible shims introduced to rename the attributes
``ChangeList.root_query_set`` and ``ChangeList.query_set``.
-* ``django.conf.urls.shortcut`` and ``django.views.defaults.shortcut`` will be
- removed.
+* ``django.views.defaults.shortcut`` will be removed, as part of the
+ goal of removing all ``django.contrib`` references from the core
+ Django codebase. Instead use
+ ``django.contrib.contenttypes.views.shortcut``. ``django.conf.urls.shortcut``
+ will also be removed.
* Support for the Python Imaging Library (PIL) module will be removed, as it
no longer appears to be actively maintained & does not work on Python 3.
@@ -410,11 +413,5 @@ these changes.
2.0
---
-* ``django.views.defaults.shortcut()``. This function has been moved
- to ``django.contrib.contenttypes.views.shortcut()`` as part of the
- goal of removing all ``django.contrib`` references from the core
- Django codebase. The old shortcut will be removed in the 2.0
- release.
-
* ``ssi`` and ``url`` template tags will be removed from the ``future`` template
tag library (used during the 1.3/1.4 deprecation period).
diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt
index 65838dfa3e28..131c9645e8ae 100644
--- a/docs/ref/contrib/sites.txt
+++ b/docs/ref/contrib/sites.txt
@@ -417,9 +417,10 @@ Here's how Django uses the sites framework:
:class:`~django.contrib.sites.models.Site` name to the template as
``{{ site_name }}``.
-* The shortcut view (``django.views.defaults.shortcut``) uses the domain
- of the current :class:`~django.contrib.sites.models.Site` object when
- calculating an object's URL.
+* The shortcut view (``django.contrib.contenttypes.views.shortcut``)
+ uses the domain of the current
+ :class:`~django.contrib.sites.models.Site` object when calculating
+ an object's URL.
* In the admin framework, the "view on site" link uses the current
:class:`~django.contrib.sites.models.Site` to work out the domain for the
From d2419bb2b85003ba9a35feac8814c9b698ef557c Mon Sep 17 00:00:00 2001
From: James Bennett
Date: Tue, 26 Feb 2013 13:12:27 -0600
Subject: [PATCH 149/944] [1.6.x] Added release date to 1.5 release notes.
Forwardport of 61283a8208 from 1.5.x
---
docs/releases/1.5.txt | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
index a1b046776ba3..a738f4d7fdad 100644
--- a/docs/releases/1.5.txt
+++ b/docs/releases/1.5.txt
@@ -2,6 +2,8 @@
Django 1.5 release notes
========================
+*February 26, 2013*
+
Welcome to Django 1.5!
These release notes cover the `new features`_, as well
From ccff25b1431bd1bb9d633b1ca1d3aff79acc33d9 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Mon, 12 Aug 2013 12:41:39 -0400
Subject: [PATCH 150/944] [1.6.x] Fixed #17778 -- Prevented class attributes on
context from resolving as template variables.
Thanks KyleMac for the report, regebro for the patch, and Aymeric for the test.
Backport of 71b5617c24 from master.
---
django/template/base.py | 5 ++++-
tests/template_tests/test_context.py | 11 ++++++++++-
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/django/template/base.py b/django/template/base.py
index 364b42807098..26f8111f1e72 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -5,7 +5,7 @@
from inspect import getargspec
from django.conf import settings
-from django.template.context import (Context, RequestContext,
+from django.template.context import (BaseContext, Context, RequestContext,
ContextPopException)
from django.utils.importlib import import_module
from django.utils.itercompat import is_iterable
@@ -765,6 +765,9 @@ def _resolve_lookup(self, context):
current = current[bit]
except (TypeError, AttributeError, KeyError, ValueError):
try: # attribute lookup
+ # Don't return class attributes if the class is the context:
+ if isinstance(current, BaseContext) and getattr(type(current), bit):
+ raise AttributeError
current = getattr(current, bit)
except (TypeError, AttributeError):
try: # list-index lookup
diff --git a/tests/template_tests/test_context.py b/tests/template_tests/test_context.py
index 05c1dd57b92f..778f4051e87f 100644
--- a/tests/template_tests/test_context.py
+++ b/tests/template_tests/test_context.py
@@ -1,5 +1,5 @@
# coding: utf-8
-from django.template import Context
+from django.template import Context, Variable, VariableDoesNotExist
from django.utils.unittest import TestCase
@@ -14,3 +14,12 @@ def test_context(self):
self.assertEqual(c.pop(), {"a": 2})
self.assertEqual(c["a"], 1)
self.assertEqual(c.get("foo", 42), 42)
+
+ def test_resolve_on_context_method(self):
+ # Regression test for #17778
+ empty_context = Context()
+ self.assertRaises(VariableDoesNotExist,
+ Variable('no_such_variable').resolve, empty_context)
+ self.assertRaises(VariableDoesNotExist,
+ Variable('new').resolve, empty_context)
+ self.assertEqual(Variable('new').resolve(Context({'new': 'foo'})), 'foo')
From 7825fb878873bfab6762ce001b0446f3ebfb25ea Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 15 Aug 2013 15:24:58 -0400
Subject: [PATCH 151/944] [1.6.x] Fixed #20891 -- Removed part of the tutorial
that requires pytz
Thanks AtomicSpark for the report.
Backport of 55339a7669 from master
---
docs/intro/tutorial02.txt | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt
index c5c5f8f28850..3ffe475e1b6a 100644
--- a/docs/intro/tutorial02.txt
+++ b/docs/intro/tutorial02.txt
@@ -385,15 +385,6 @@ search terms, Django will search the ``question`` field. You can use as many
fields as you'd like -- although because it uses a ``LIKE`` query behind the
scenes, keep it reasonable, to keep your database happy.
-Finally, because ``Poll`` objects have dates, it'd be convenient to be able to
-drill down by date. Add this line::
-
- date_hierarchy = 'pub_date'
-
-That adds hierarchical navigation, by date, to the top of the change list page.
-At top level, it displays all available years. Then it drills down to months
-and, ultimately, days.
-
Now's also a good time to note that change lists give you free pagination. The
default is to display 100 items per page. Change-list pagination, search boxes,
filters, date-hierarchies and column-header-ordering all work together like you
From 919934602fd2767024126e094d33f0ad64947270 Mon Sep 17 00:00:00 2001
From: Alasdair Nicol
Date: Sun, 11 Aug 2013 21:19:09 +0100
Subject: [PATCH 152/944] [1.6.x] Fixed #20895 -- Made check management command
warn if a BooleanField does not have a default value
Thanks to Collin Anderson for the suggestion and Tim Graham for
reviewing the patch.
Backport of 22c6497f99 from master
---
AUTHORS | 1 +
django/contrib/gis/tests/geoapp/models.py | 2 +-
.../core/checks/compatibility/django_1_6_0.py | 29 ++++++++++++++++++-
tests/admin_views/models.py | 8 ++---
tests/aggregation_regress/models.py | 2 +-
tests/check/models.py | 10 ++++++-
tests/check/tests.py | 20 +++++++++++++
tests/comment_tests/models.py | 2 +-
tests/custom_managers/models.py | 4 +--
tests/generic_relations/models.py | 2 +-
tests/inspectdb/models.py | 2 +-
tests/model_fields/models.py | 4 +--
tests/model_fields/tests.py | 21 +++++++++++---
tests/model_formsets/models.py | 6 ++--
tests/model_inheritance/models.py | 8 ++---
tests/model_inheritance_regress/models.py | 8 ++---
.../models.py | 4 +--
tests/modeladmin/models.py | 2 +-
tests/one_to_one/models.py | 4 +--
tests/one_to_one_regress/models.py | 4 +--
tests/raw_query/models.py | 2 +-
tests/reverse_single_related/models.py | 2 +-
tests/serializers_regress/models.py | 4 +--
23 files changed, 110 insertions(+), 41 deletions(-)
diff --git a/AUTHORS b/AUTHORS
index 6b232deb7054..e2598ddbad37 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -437,6 +437,7 @@ answer newbie questions, and generally made Django that much better:
Gopal Narayanan
Fraser Nevett
Sam Newman
+ Alasdair Nicol
Ryan Niemeyer
Filip Noetzel
Afonso Fernández Nogueira
diff --git a/django/contrib/gis/tests/geoapp/models.py b/django/contrib/gis/tests/geoapp/models.py
index abde509c8bb2..fa838590636e 100644
--- a/django/contrib/gis/tests/geoapp/models.py
+++ b/django/contrib/gis/tests/geoapp/models.py
@@ -40,7 +40,7 @@ class Track(models.Model):
def __str__(self): return self.name
class Truth(models.Model):
- val = models.BooleanField()
+ val = models.BooleanField(default=False)
objects = models.GeoManager()
if not spatialite:
diff --git a/django/core/checks/compatibility/django_1_6_0.py b/django/core/checks/compatibility/django_1_6_0.py
index 1998c5ba77b4..e38b2d32ec37 100644
--- a/django/core/checks/compatibility/django_1_6_0.py
+++ b/django/core/checks/compatibility/django_1_6_0.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
+from django.db import models
def check_test_runner():
"""
@@ -24,6 +25,31 @@ def check_test_runner():
]
return ' '.join(message)
+def check_boolean_field_default_value():
+ """
+ Checks if there are any BooleanFields without a default value, &
+ warns the user that the default has changed from False to Null.
+ """
+ fields = []
+ for cls in models.get_models():
+ opts = cls._meta
+ for f in opts.local_fields:
+ if isinstance(f, models.BooleanField) and not f.has_default():
+ fields.append(
+ '%s.%s: "%s"' % (opts.app_label, opts.object_name, f.name)
+ )
+ if fields:
+ fieldnames = ", ".join(fields)
+ message = [
+ "You have not set a default value for one or more BooleanFields:",
+ "%s." % fieldnames,
+ "In Django 1.6 the default value of BooleanField was changed from",
+ "False to Null when Field.default isn't defined. See",
+ "https://docs.djangoproject.com/en/1.6/ref/models/fields/#booleanfield"
+ "for more information."
+ ]
+ return ' '.join(message)
+
def run_checks():
"""
@@ -31,7 +57,8 @@ def run_checks():
messages from all the relevant check functions for this version of Django.
"""
checks = [
- check_test_runner()
+ check_test_runner(),
+ check_boolean_field_default_value(),
]
# Filter out the ``None`` or empty strings.
return [output for output in checks if output]
diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py
index 5ec4fbb544ba..9a7f0ff30ec5 100644
--- a/tests/admin_views/models.py
+++ b/tests/admin_views/models.py
@@ -115,7 +115,7 @@ def get_absolute_url(self):
@python_2_unicode_compatible
class Color(models.Model):
value = models.CharField(max_length=10)
- warm = models.BooleanField()
+ warm = models.BooleanField(default=False)
def __str__(self):
return self.value
@@ -144,7 +144,7 @@ def __str__(self):
@python_2_unicode_compatible
class Inquisition(models.Model):
- expected = models.BooleanField()
+ expected = models.BooleanField(default=False)
leader = models.ForeignKey(Actor)
country = models.CharField(max_length=20)
@@ -376,7 +376,7 @@ class Link(models.Model):
class PrePopulatedPost(models.Model):
title = models.CharField(max_length=100)
- published = models.BooleanField()
+ published = models.BooleanField(default=False)
slug = models.SlugField()
@@ -607,7 +607,7 @@ class PrePopulatedPostLargeSlug(models.Model):
the javascript (ie, using THOUSAND_SEPARATOR ends up with maxLength=1,000)
"""
title = models.CharField(max_length=100)
- published = models.BooleanField()
+ published = models.BooleanField(default=False)
slug = models.SlugField(max_length=1000)
class AdminOrderedField(models.Model):
diff --git a/tests/aggregation_regress/models.py b/tests/aggregation_regress/models.py
index 047c871c39a7..dfef691882b0 100644
--- a/tests/aggregation_regress/models.py
+++ b/tests/aggregation_regress/models.py
@@ -64,7 +64,7 @@ def __str__(self):
class Entries(models.Model):
EntryID = models.AutoField(primary_key=True, db_column='Entry ID')
Entry = models.CharField(unique=True, max_length=50)
- Exclude = models.BooleanField()
+ Exclude = models.BooleanField(default=False)
class Clues(models.Model):
diff --git a/tests/check/models.py b/tests/check/models.py
index 78a10abba613..212b01bdd2b9 100644
--- a/tests/check/models.py
+++ b/tests/check/models.py
@@ -1 +1,9 @@
-# Stubby.
+from django.db import models
+
+class Book(models.Model):
+ title = models.CharField(max_length=250)
+ is_published = models.BooleanField(default=False)
+
+class BlogPost(models.Model):
+ title = models.CharField(max_length=250)
+ is_published = models.BooleanField(default=False)
diff --git a/tests/check/tests.py b/tests/check/tests.py
index 98495e38ae36..3faf086557cd 100644
--- a/tests/check/tests.py
+++ b/tests/check/tests.py
@@ -2,8 +2,10 @@
from django.core.checks.compatibility import django_1_6_0
from django.core.management.commands import check
from django.core.management import call_command
+from django.db.models.fields import NOT_PROVIDED
from django.test import TestCase
+from .models import Book
class StubCheckModule(object):
# Has no ``run_checks`` attribute & will trigger a warning.
@@ -53,6 +55,24 @@ def test_run_checks_overridden(self):
with self.settings(TEST_RUNNER='myapp.test.CustomRunnner'):
self.assertEqual(len(django_1_6_0.run_checks()), 0)
+ def test_boolean_field_default_value(self):
+ with self.settings(TEST_RUNNER='myapp.test.CustomRunnner'):
+ # We patch the field's default value to trigger the warning
+ boolean_field = Book._meta.get_field('is_published')
+ old_default = boolean_field.default
+ try:
+ boolean_field.default = NOT_PROVIDED
+ result = django_1_6_0.run_checks()
+ self.assertEqual(len(result), 1)
+ self.assertTrue("You have not set a default value for one or more BooleanFields" in result[0])
+ self.assertTrue('check.Book: "is_published"' in result[0])
+ # We did not patch the BlogPost.is_published field so
+ # there should not be a warning about it
+ self.assertFalse('check.BlogPost' in result[0])
+ finally:
+ # Restore the ``default``
+ boolean_field.default = old_default
+
def test_check_compatibility(self):
with self.settings(TEST_RUNNER='django.test.runner.DiscoverRunner'):
result = base.check_compatibility()
diff --git a/tests/comment_tests/models.py b/tests/comment_tests/models.py
index 472b66decdd7..a2b27e7e878d 100644
--- a/tests/comment_tests/models.py
+++ b/tests/comment_tests/models.py
@@ -30,7 +30,7 @@ class Entry(models.Model):
title = models.CharField(max_length=250)
body = models.TextField()
pub_date = models.DateField()
- enable_comments = models.BooleanField()
+ enable_comments = models.BooleanField(default=False)
def __str__(self):
return self.title
diff --git a/tests/custom_managers/models.py b/tests/custom_managers/models.py
index 2f5e62fc7aaf..d59ccc8642c9 100644
--- a/tests/custom_managers/models.py
+++ b/tests/custom_managers/models.py
@@ -24,7 +24,7 @@ def get_fun_people(self):
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
- fun = models.BooleanField()
+ fun = models.BooleanField(default=False)
objects = PersonManager()
def __str__(self):
@@ -40,7 +40,7 @@ def get_queryset(self):
class Book(models.Model):
title = models.CharField(max_length=50)
author = models.CharField(max_length=30)
- is_published = models.BooleanField()
+ is_published = models.BooleanField(default=False)
published_objects = PublishedBookManager()
authors = models.ManyToManyField(Person, related_name='books')
diff --git a/tests/generic_relations/models.py b/tests/generic_relations/models.py
index 211df8aa3dd3..d819a53c0e15 100644
--- a/tests/generic_relations/models.py
+++ b/tests/generic_relations/models.py
@@ -92,7 +92,7 @@ def get_queryset(self):
return super(GeckoManager, self).get_queryset().filter(has_tail=True)
class Gecko(models.Model):
- has_tail = models.BooleanField()
+ has_tail = models.BooleanField(default=False)
objects = GeckoManager()
# To test fix for #11263
diff --git a/tests/inspectdb/models.py b/tests/inspectdb/models.py
index 2862f7f3dc5e..c25202f248f2 100644
--- a/tests/inspectdb/models.py
+++ b/tests/inspectdb/models.py
@@ -37,7 +37,7 @@ class SpecialColumnName(models.Model):
class ColumnTypes(models.Model):
id = models.AutoField(primary_key=True)
big_int_field = models.BigIntegerField()
- bool_field = models.BooleanField()
+ bool_field = models.BooleanField(default=False)
null_bool_field = models.NullBooleanField()
char_field = models.CharField(max_length=10)
comma_separated_int_field = models.CommaSeparatedIntegerField(max_length=99)
diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py
index 2d602d64121d..8c304cc7265f 100644
--- a/tests/model_fields/models.py
+++ b/tests/model_fields/models.py
@@ -58,7 +58,7 @@ class NullBooleanModel(models.Model):
nbfield = models.NullBooleanField()
class BooleanModel(models.Model):
- bfield = models.BooleanField()
+ bfield = models.BooleanField(default=None)
string = models.CharField(max_length=10, default='abc')
class FksToBooleans(models.Model):
@@ -72,7 +72,7 @@ class RenamedField(models.Model):
class VerboseNameField(models.Model):
id = models.AutoField("verbose pk", primary_key=True)
field1 = models.BigIntegerField("verbose field1")
- field2 = models.BooleanField("verbose field2")
+ field2 = models.BooleanField("verbose field2", default=False)
field3 = models.CharField("verbose field3", max_length=10)
field4 = models.CommaSeparatedIntegerField("verbose field4", max_length=99)
field5 = models.DateField("verbose field5")
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py
index 703c0ee8f6ef..1fb503762565 100644
--- a/tests/model_fields/tests.py
+++ b/tests/model_fields/tests.py
@@ -265,10 +265,23 @@ def test_null_default(self):
Check that a BooleanField defaults to None -- which isn't
a valid value (#15124).
"""
- b = BooleanModel()
- self.assertIsNone(b.bfield)
- with self.assertRaises(IntegrityError):
- b.save()
+ # Patch the boolean field's default value. We give it a default
+ # value when defining the model to satisfy the check tests
+ # #20895.
+ boolean_field = BooleanModel._meta.get_field('bfield')
+ self.assertTrue(boolean_field.has_default())
+ old_default = boolean_field.default
+ try:
+ boolean_field.default = models.NOT_PROVIDED
+ # check patch was succcessful
+ self.assertFalse(boolean_field.has_default())
+ b = BooleanModel()
+ self.assertIsNone(b.bfield)
+ with self.assertRaises(IntegrityError):
+ b.save()
+ finally:
+ boolean_field.default = old_default
+
nb = NullBooleanModel()
self.assertIsNone(nb.nbfield)
nb.save() # no error
diff --git a/tests/model_formsets/models.py b/tests/model_formsets/models.py
index ae152448abd1..adeb3455a4f7 100644
--- a/tests/model_formsets/models.py
+++ b/tests/model_formsets/models.py
@@ -117,7 +117,7 @@ def __str__(self):
@python_2_unicode_compatible
class Restaurant(Place):
- serves_pizza = models.BooleanField()
+ serves_pizza = models.BooleanField(default=False)
def __str__(self):
return self.name
@@ -141,11 +141,11 @@ class Meta:
unique_together = (('price', 'quantity'),)
class MexicanRestaurant(Restaurant):
- serves_tacos = models.BooleanField()
+ serves_tacos = models.BooleanField(default=False)
class ClassyMexicanRestaurant(MexicanRestaurant):
restaurant = models.OneToOneField(MexicanRestaurant, parent_link=True, primary_key=True)
- tacos_are_yummy = models.BooleanField()
+ tacos_are_yummy = models.BooleanField(default=False)
# models for testing unique_together validation when a fk is involved and
# using inlineformset_factory.
diff --git a/tests/model_inheritance/models.py b/tests/model_inheritance/models.py
index 2101f394f770..106645d23c78 100644
--- a/tests/model_inheritance/models.py
+++ b/tests/model_inheritance/models.py
@@ -63,7 +63,7 @@ def __str__(self):
return self.content
class Comment(Attachment):
- is_spam = models.BooleanField()
+ is_spam = models.BooleanField(default=False)
class Link(Attachment):
url = models.URLField()
@@ -96,8 +96,8 @@ class Meta:
@python_2_unicode_compatible
class Restaurant(Place, Rating):
- serves_hot_dogs = models.BooleanField()
- serves_pizza = models.BooleanField()
+ serves_hot_dogs = models.BooleanField(default=False)
+ serves_pizza = models.BooleanField(default=False)
chef = models.ForeignKey(Chef, null=True, blank=True)
class Meta(Rating.Meta):
@@ -108,7 +108,7 @@ def __str__(self):
@python_2_unicode_compatible
class ItalianRestaurant(Restaurant):
- serves_gnocchi = models.BooleanField()
+ serves_gnocchi = models.BooleanField(default=False)
def __str__(self):
return "%s the italian restaurant" % self.name
diff --git a/tests/model_inheritance_regress/models.py b/tests/model_inheritance_regress/models.py
index 811c8175bb71..007f70864030 100644
--- a/tests/model_inheritance_regress/models.py
+++ b/tests/model_inheritance_regress/models.py
@@ -18,15 +18,15 @@ def __str__(self):
@python_2_unicode_compatible
class Restaurant(Place):
- serves_hot_dogs = models.BooleanField()
- serves_pizza = models.BooleanField()
+ serves_hot_dogs = models.BooleanField(default=False)
+ serves_pizza = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.name
@python_2_unicode_compatible
class ItalianRestaurant(Restaurant):
- serves_gnocchi = models.BooleanField()
+ serves_gnocchi = models.BooleanField(default=False)
def __str__(self):
return "%s the italian restaurant" % self.name
@@ -171,7 +171,7 @@ class Meta:
class BusStation(Station):
bus_routes = models.CommaSeparatedIntegerField(max_length=128)
- inbound = models.BooleanField()
+ inbound = models.BooleanField(default=False)
class TrainStation(Station):
zone = models.IntegerField()
diff --git a/tests/model_inheritance_select_related/models.py b/tests/model_inheritance_select_related/models.py
index 6b287726201e..46c67cf07de0 100644
--- a/tests/model_inheritance_select_related/models.py
+++ b/tests/model_inheritance_select_related/models.py
@@ -20,8 +20,8 @@ def __str__(self):
@python_2_unicode_compatible
class Restaurant(Place):
- serves_sushi = models.BooleanField()
- serves_steak = models.BooleanField()
+ serves_sushi = models.BooleanField(default=False)
+ serves_steak = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.name
diff --git a/tests/modeladmin/models.py b/tests/modeladmin/models.py
index fdbcabd1873f..4789e35a43af 100644
--- a/tests/modeladmin/models.py
+++ b/tests/modeladmin/models.py
@@ -32,7 +32,7 @@ class ValidationTestModel(models.Model):
slug = models.SlugField()
users = models.ManyToManyField(User)
state = models.CharField(max_length=2, choices=(("CO", "Colorado"), ("WA", "Washington")))
- is_active = models.BooleanField()
+ is_active = models.BooleanField(default=False)
pub_date = models.DateTimeField()
band = models.ForeignKey(Band)
no = models.IntegerField(verbose_name="Number", blank=True, null=True) # This field is intentionally 2 characters long. See #16080.
diff --git a/tests/one_to_one/models.py b/tests/one_to_one/models.py
index 9599496cb7d6..ff809be22dab 100644
--- a/tests/one_to_one/models.py
+++ b/tests/one_to_one/models.py
@@ -22,8 +22,8 @@ def __str__(self):
@python_2_unicode_compatible
class Restaurant(models.Model):
place = models.OneToOneField(Place, primary_key=True)
- serves_hot_dogs = models.BooleanField()
- serves_pizza = models.BooleanField()
+ serves_hot_dogs = models.BooleanField(default=False)
+ serves_pizza = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.place.name
diff --git a/tests/one_to_one_regress/models.py b/tests/one_to_one_regress/models.py
index 058baafc8269..9b65edf2ab4e 100644
--- a/tests/one_to_one_regress/models.py
+++ b/tests/one_to_one_regress/models.py
@@ -15,8 +15,8 @@ def __str__(self):
@python_2_unicode_compatible
class Restaurant(models.Model):
place = models.OneToOneField(Place)
- serves_hot_dogs = models.BooleanField()
- serves_pizza = models.BooleanField()
+ serves_hot_dogs = models.BooleanField(default=False)
+ serves_pizza = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.place.name
diff --git a/tests/raw_query/models.py b/tests/raw_query/models.py
index e7e221dc6ed1..33f754958e90 100644
--- a/tests/raw_query/models.py
+++ b/tests/raw_query/models.py
@@ -18,7 +18,7 @@ def __init__(self, *args, **kwargs):
class Book(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(Author)
- paperback = models.BooleanField()
+ paperback = models.BooleanField(default=False)
opening_line = models.TextField()
class Coffee(models.Model):
diff --git a/tests/reverse_single_related/models.py b/tests/reverse_single_related/models.py
index 30ba34512009..5d53e0477242 100644
--- a/tests/reverse_single_related/models.py
+++ b/tests/reverse_single_related/models.py
@@ -6,7 +6,7 @@ def get_queryset(self):
return super(SourceManager, self).get_queryset().filter(is_public=True)
class Source(models.Model):
- is_public = models.BooleanField()
+ is_public = models.BooleanField(default=False)
objects = SourceManager()
class Item(models.Model):
diff --git a/tests/serializers_regress/models.py b/tests/serializers_regress/models.py
index 21a3448a8eee..ab3d3063a446 100644
--- a/tests/serializers_regress/models.py
+++ b/tests/serializers_regress/models.py
@@ -16,7 +16,7 @@ class BinaryData(models.Model):
data = models.BinaryField(null=True)
class BooleanData(models.Model):
- data = models.BooleanField()
+ data = models.BooleanField(default=False)
class CharData(models.Model):
data = models.CharField(max_length=30, null=True)
@@ -166,7 +166,7 @@ class Intermediate(models.Model):
# or all database backends.
class BooleanPKData(models.Model):
- data = models.BooleanField(primary_key=True)
+ data = models.BooleanField(primary_key=True, default=False)
class CharPKData(models.Model):
data = models.CharField(max_length=30, primary_key=True)
From c769c266017c9a527444f8f026c7a76b394d0412 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 16 Aug 2013 07:57:55 -0400
Subject: [PATCH 153/944] [1.6.x] Revert "Fixed #18491 -- deleting a proxy
doesn't show warning about cascade deletes"
This reverts commit 2b48fcc607010065c0f8107baf669dd41b164f3c.
It introduced a regression (#20777) which we can't easily fix in 1.6.
---
django/contrib/admin/util.py | 3 ---
tests/proxy_models/fixtures/myhorses.json | 24 -----------------------
tests/proxy_models/tests.py | 22 +--------------------
3 files changed, 1 insertion(+), 48 deletions(-)
delete mode 100644 tests/proxy_models/fixtures/myhorses.json
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
index dd9047c42824..b31756d76249 100644
--- a/django/contrib/admin/util.py
+++ b/django/contrib/admin/util.py
@@ -155,9 +155,6 @@ def collect(self, objs, source_attr=None, **kwargs):
if source_attr:
self.add_edge(getattr(obj, source_attr), obj)
else:
- if obj._meta.proxy:
- # Take concrete model's instance to avoid mismatch in edges
- obj = obj._meta.concrete_model(pk=obj.pk)
self.add_edge(None, obj)
try:
return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs)
diff --git a/tests/proxy_models/fixtures/myhorses.json b/tests/proxy_models/fixtures/myhorses.json
deleted file mode 100644
index 15943064c8d5..000000000000
--- a/tests/proxy_models/fixtures/myhorses.json
+++ /dev/null
@@ -1,24 +0,0 @@
-[
- {
- "pk": 100,
- "model": "proxy_models.BaseUser",
- "fields": {
- "name": "Django Pony"
- }
- },
- {
- "pk": 100,
- "model": "proxy_models.TrackerUser",
- "fields": {
- "status": "emperor"
- }
- },
- {
- "pk": 100,
- "model": "proxy_models.Issue",
- "fields": {
- "summary": "Pony's Issue",
- "assignee": 100
- }
- }
-]
\ No newline at end of file
diff --git a/tests/proxy_models/tests.py b/tests/proxy_models/tests.py
index 5cc5ef54789e..0124f03d2325 100644
--- a/tests/proxy_models/tests.py
+++ b/tests/proxy_models/tests.py
@@ -2,7 +2,6 @@
import copy
from django.conf import settings
-from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from django.core import management
from django.core.exceptions import FieldError
@@ -15,7 +14,7 @@
from .models import (MyPerson, Person, StatusPerson, LowerStatusPerson,
MyPersonProxy, Abstract, OtherPerson, User, UserProxy, UserProxyProxy,
Country, State, StateProxy, TrackerUser, BaseUser, Bug, ProxyTrackerUser,
- Improvement, ProxyProxyBug, ProxyBug, ProxyImprovement, Issue)
+ Improvement, ProxyProxyBug, ProxyBug, ProxyImprovement)
class ProxyModelTests(TestCase):
@@ -361,22 +360,3 @@ def test_proxy_load_from_fixture(self):
management.call_command('loaddata', 'mypeople.json', verbosity=0)
p = MyPerson.objects.get(pk=100)
self.assertEqual(p.name, 'Elvis Presley')
-
-
-class ProxyModelAdminTests(TestCase):
- fixtures = ['myhorses']
-
- def test_cascade_delete_proxy_model_admin_warning(self):
- """
- Test if admin gives warning about cascade deleting models referenced
- to concrete model by deleting proxy object.
- """
- tracker_user = TrackerUser.objects.all()[0]
- base_user = BaseUser.objects.all()[0]
- issue = Issue.objects.all()[0]
- with self.assertNumQueries(7):
- collector = admin.util.NestedObjects('default')
- collector.collect(ProxyTrackerUser.objects.all())
- self.assertTrue(tracker_user in collector.edges.get(None, ()))
- self.assertTrue(base_user in collector.edges.get(None, ()))
- self.assertTrue(issue in collector.edges.get(tracker_user, ()))
From 1b48de06c3ca5cba57a734a09c815a2be5c4bcfe Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Fri, 16 Aug 2013 15:13:31 +0200
Subject: [PATCH 154/944] [1.6.x] Updated translation templates
---
django/conf/locale/en/LC_MESSAGES/django.po | 250 +++++++++---------
.../formtools/locale/en/LC_MESSAGES/django.po | 6 +-
2 files changed, 137 insertions(+), 119 deletions(-)
diff --git a/django/conf/locale/en/LC_MESSAGES/django.po b/django/conf/locale/en/LC_MESSAGES/django.po
index f8069f07d2ca..a707cb9ba50b 100644
--- a/django/conf/locale/en/LC_MESSAGES/django.po
+++ b/django/conf/locale/en/LC_MESSAGES/django.po
@@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-06-11 18:44+0200\n"
+"POT-Creation-Date: 2013-08-16 15:07+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English \n"
@@ -337,10 +337,14 @@ msgstr ""
msgid "Enter a valid value."
msgstr ""
-#: core/validators.py:53 forms/fields.py:639
+#: core/validators.py:53 forms/fields.py:650
msgid "Enter a valid URL."
msgstr ""
+#: core/validators.py:79
+msgid "Enter a valid integer."
+msgstr ""
+
#: core/validators.py:83
msgid "Enter a valid email address."
msgstr ""
@@ -362,7 +366,7 @@ msgstr ""
msgid "Enter a valid IPv4 or IPv6 address."
msgstr ""
-#: core/validators.py:175 db/models/fields/__init__.py:706
+#: core/validators.py:175 db/models/fields/__init__.py:712
msgid "Enter only digits separated by commas."
msgstr ""
@@ -371,17 +375,17 @@ msgstr ""
msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)."
msgstr ""
-#: core/validators.py:200
+#: core/validators.py:196
#, python-format
msgid "Ensure this value is less than or equal to %(limit_value)s."
msgstr ""
-#: core/validators.py:206
+#: core/validators.py:202
#, python-format
msgid "Ensure this value is greater than or equal to %(limit_value)s."
msgstr ""
-#: core/validators.py:214
+#: core/validators.py:210
#, python-format
msgid ""
"Ensure this value has at least %(limit_value)d character (it has "
@@ -392,7 +396,7 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: core/validators.py:224
+#: core/validators.py:220
#, python-format
msgid ""
"Ensure this value has at most %(limit_value)d character (it has "
@@ -403,23 +407,23 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: db/models/base.py:882
+#: db/models/base.py:884
#, python-format
msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s."
msgstr ""
-#: db/models/base.py:905 forms/models.py:643
+#: db/models/base.py:907 forms/models.py:686
msgid "and"
msgstr ""
-#: db/models/base.py:906 db/models/fields/__init__.py:83
+#: db/models/base.py:908 db/models/fields/__init__.py:83
#, python-format
msgid "%(model_name)s with this %(field_label)s already exists."
msgstr ""
#: db/models/fields/__init__.py:80
#, python-format
-msgid "Value %r is not a valid choice."
+msgid "Value %(value)r is not a valid choice."
msgstr ""
#: db/models/fields/__init__.py:81
@@ -435,181 +439,185 @@ msgstr ""
msgid "Field of type: %(field_type)s"
msgstr ""
-#: db/models/fields/__init__.py:570 db/models/fields/__init__.py:1036
+#: db/models/fields/__init__.py:570 db/models/fields/__init__.py:1063
msgid "Integer"
msgstr ""
-#: db/models/fields/__init__.py:574 db/models/fields/__init__.py:1034
+#: db/models/fields/__init__.py:574 db/models/fields/__init__.py:1061
#, python-format
-msgid "'%s' value must be an integer."
+msgid "'%(value)s' value must be an integer."
msgstr ""
-#: db/models/fields/__init__.py:622
+#: db/models/fields/__init__.py:625
#, python-format
-msgid "'%s' value must be either True or False."
+msgid "'%(value)s' value must be either True or False."
msgstr ""
-#: db/models/fields/__init__.py:624
+#: db/models/fields/__init__.py:627
msgid "Boolean (Either True or False)"
msgstr ""
-#: db/models/fields/__init__.py:673
+#: db/models/fields/__init__.py:679
#, python-format
msgid "String (up to %(max_length)s)"
msgstr ""
-#: db/models/fields/__init__.py:701
+#: db/models/fields/__init__.py:707
msgid "Comma-separated integers"
msgstr ""
-#: db/models/fields/__init__.py:715
+#: db/models/fields/__init__.py:721
#, python-format
-msgid "'%s' value has an invalid date format. It must be in YYYY-MM-DD format."
+msgid ""
+"'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD "
+"format."
msgstr ""
-#: db/models/fields/__init__.py:717 db/models/fields/__init__.py:805
+#: db/models/fields/__init__.py:723 db/models/fields/__init__.py:817
#, python-format
msgid ""
-"'%s' value has the correct format (YYYY-MM-DD) but it is an invalid date."
+"'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid "
+"date."
msgstr ""
-#: db/models/fields/__init__.py:720
+#: db/models/fields/__init__.py:726
msgid "Date (without time)"
msgstr ""
-#: db/models/fields/__init__.py:803
+#: db/models/fields/__init__.py:815
#, python-format
msgid ""
-"'%s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[."
+"'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[."
"uuuuuu]][TZ] format."
msgstr ""
-#: db/models/fields/__init__.py:807
+#: db/models/fields/__init__.py:819
#, python-format
msgid ""
-"'%s' value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) but "
-"it is an invalid date/time."
+"'%(value)s' value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]"
+"[TZ]) but it is an invalid date/time."
msgstr ""
-#: db/models/fields/__init__.py:811
+#: db/models/fields/__init__.py:823
msgid "Date (with time)"
msgstr ""
-#: db/models/fields/__init__.py:900
+#: db/models/fields/__init__.py:921
#, python-format
-msgid "'%s' value must be a decimal number."
+msgid "'%(value)s' value must be a decimal number."
msgstr ""
-#: db/models/fields/__init__.py:902
+#: db/models/fields/__init__.py:923
msgid "Decimal number"
msgstr ""
-#: db/models/fields/__init__.py:959
+#: db/models/fields/__init__.py:983
msgid "Email address"
msgstr ""
-#: db/models/fields/__init__.py:978
+#: db/models/fields/__init__.py:1002
msgid "File path"
msgstr ""
-#: db/models/fields/__init__.py:1005
+#: db/models/fields/__init__.py:1029
#, python-format
-msgid "'%s' value must be a float."
+msgid "'%(value)s' value must be a float."
msgstr ""
-#: db/models/fields/__init__.py:1007
+#: db/models/fields/__init__.py:1031
msgid "Floating point number"
msgstr ""
-#: db/models/fields/__init__.py:1068
+#: db/models/fields/__init__.py:1098
msgid "Big (8 byte) integer"
msgstr ""
-#: db/models/fields/__init__.py:1082
+#: db/models/fields/__init__.py:1112
msgid "IPv4 address"
msgstr ""
-#: db/models/fields/__init__.py:1098
+#: db/models/fields/__init__.py:1128
msgid "IP address"
msgstr ""
-#: db/models/fields/__init__.py:1141
+#: db/models/fields/__init__.py:1175
#, python-format
-msgid "'%s' value must be either None, True or False."
+msgid "'%(value)s' value must be either None, True or False."
msgstr ""
-#: db/models/fields/__init__.py:1143
+#: db/models/fields/__init__.py:1177
msgid "Boolean (Either True, False or None)"
msgstr ""
-#: db/models/fields/__init__.py:1192
+#: db/models/fields/__init__.py:1229
msgid "Positive integer"
msgstr ""
-#: db/models/fields/__init__.py:1203
+#: db/models/fields/__init__.py:1240
msgid "Positive small integer"
msgstr ""
-#: db/models/fields/__init__.py:1214
+#: db/models/fields/__init__.py:1252
#, python-format
msgid "Slug (up to %(max_length)s)"
msgstr ""
-#: db/models/fields/__init__.py:1232
+#: db/models/fields/__init__.py:1270
msgid "Small integer"
msgstr ""
-#: db/models/fields/__init__.py:1238
+#: db/models/fields/__init__.py:1276
msgid "Text"
msgstr ""
-#: db/models/fields/__init__.py:1256
+#: db/models/fields/__init__.py:1294
#, python-format
msgid ""
-"'%s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] format."
+"'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] "
+"format."
msgstr ""
-#: db/models/fields/__init__.py:1258
+#: db/models/fields/__init__.py:1296
#, python-format
msgid ""
-"'%s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an invalid "
-"time."
+"'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an "
+"invalid time."
msgstr ""
-#: db/models/fields/__init__.py:1261
+#: db/models/fields/__init__.py:1299
msgid "Time"
msgstr ""
-#: db/models/fields/__init__.py:1323
+#: db/models/fields/__init__.py:1368
msgid "URL"
msgstr ""
-#: db/models/fields/__init__.py:1340
+#: db/models/fields/__init__.py:1384
msgid "Raw binary data"
msgstr ""
-#: db/models/fields/files.py:215
+#: db/models/fields/files.py:217
msgid "File"
msgstr ""
-#: db/models/fields/files.py:322
+#: db/models/fields/files.py:324
msgid "Image"
msgstr ""
-#: db/models/fields/related.py:1118
+#: db/models/fields/related.py:1128
#, python-format
-msgid "Model %(model)s with pk %(pk)r does not exist."
+msgid "%(model)s instance with pk %(pk)r does not exist."
msgstr ""
-#: db/models/fields/related.py:1120
+#: db/models/fields/related.py:1130
msgid "Foreign Key (type determined by related field)"
msgstr ""
-#: db/models/fields/related.py:1257
+#: db/models/fields/related.py:1271
msgid "One-to-one relationship"
msgstr ""
-#: db/models/fields/related.py:1324
+#: db/models/fields/related.py:1338
msgid "Many-to-many relationship"
msgstr ""
@@ -617,29 +625,29 @@ msgstr ""
msgid "This field is required."
msgstr ""
-#: forms/fields.py:227
+#: forms/fields.py:225
msgid "Enter a whole number."
msgstr ""
-#: forms/fields.py:268 forms/fields.py:296
+#: forms/fields.py:266 forms/fields.py:294
msgid "Enter a number."
msgstr ""
-#: forms/fields.py:298
+#: forms/fields.py:296
#, python-format
msgid "Ensure that there are no more than %(max)s digit in total."
msgid_plural "Ensure that there are no more than %(max)s digits in total."
msgstr[0] ""
msgstr[1] ""
-#: forms/fields.py:302
+#: forms/fields.py:300
#, python-format
msgid "Ensure that there are no more than %(max)s decimal place."
msgid_plural "Ensure that there are no more than %(max)s decimal places."
msgstr[0] ""
msgstr[1] ""
-#: forms/fields.py:306
+#: forms/fields.py:304
#, python-format
msgid ""
"Ensure that there are no more than %(max)s digit before the decimal point."
@@ -648,31 +656,31 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forms/fields.py:408 forms/fields.py:1064
+#: forms/fields.py:415 forms/fields.py:1088
msgid "Enter a valid date."
msgstr ""
-#: forms/fields.py:432 forms/fields.py:1065
+#: forms/fields.py:439 forms/fields.py:1089
msgid "Enter a valid time."
msgstr ""
-#: forms/fields.py:454
+#: forms/fields.py:461
msgid "Enter a valid date/time."
msgstr ""
-#: forms/fields.py:531
+#: forms/fields.py:539
msgid "No file was submitted. Check the encoding type on the form."
msgstr ""
-#: forms/fields.py:532
+#: forms/fields.py:540
msgid "No file was submitted."
msgstr ""
-#: forms/fields.py:533
+#: forms/fields.py:541
msgid "The submitted file is empty."
msgstr ""
-#: forms/fields.py:535
+#: forms/fields.py:543
#, python-format
msgid "Ensure this filename has at most %(max)d character (it has %(length)d)."
msgid_plural ""
@@ -680,22 +688,22 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
-#: forms/fields.py:538
+#: forms/fields.py:546
msgid "Please either submit a file or check the clear checkbox, not both."
msgstr ""
-#: forms/fields.py:599
+#: forms/fields.py:607
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
-#: forms/fields.py:749 forms/fields.py:828 forms/models.py:1096
+#: forms/fields.py:757 forms/fields.py:844 forms/models.py:1145
#, python-format
msgid "Select a valid choice. %(value)s is not one of the available choices."
msgstr ""
-#: forms/fields.py:829 forms/fields.py:933 forms/models.py:1095
+#: forms/fields.py:845 forms/fields.py:957 forms/models.py:1144
msgid "Enter a list of values."
msgstr ""
@@ -709,129 +717,131 @@ msgstr ""
msgid "(Hidden field %(name)s) %(error)s"
msgstr ""
-#. Translators: If found as last label character, these punctuation
-#. characters will prevent the default label_suffix to be appended to the label
-#: forms/forms.py:525
+#: forms/forms.py:527
msgid ":?.!"
msgstr ""
-#: forms/formsets.py:310
+#: forms/formsets.py:88
+msgid "ManagementForm data is missing or has been tampered with"
+msgstr ""
+
+#: forms/formsets.py:321
#, python-format
msgid "Please submit %d or fewer forms."
msgid_plural "Please submit %d or fewer forms."
msgstr[0] ""
msgstr[1] ""
-#: forms/formsets.py:337 forms/formsets.py:339
+#: forms/formsets.py:350 forms/formsets.py:352
msgid "Order"
msgstr ""
-#: forms/formsets.py:341
+#: forms/formsets.py:354
msgid "Delete"
msgstr ""
-#: forms/models.py:637
+#: forms/models.py:680
#, python-format
msgid "Please correct the duplicate data for %(field)s."
msgstr ""
-#: forms/models.py:641
+#: forms/models.py:684
#, python-format
msgid "Please correct the duplicate data for %(field)s, which must be unique."
msgstr ""
-#: forms/models.py:647
+#: forms/models.py:690
#, python-format
msgid ""
"Please correct the duplicate data for %(field_name)s which must be unique "
"for the %(lookup)s in %(date_field)s."
msgstr ""
-#: forms/models.py:655
+#: forms/models.py:698
msgid "Please correct the duplicate values below."
msgstr ""
-#: forms/models.py:937
+#: forms/models.py:986
msgid "The inline foreign key did not match the parent instance primary key."
msgstr ""
-#: forms/models.py:1001
+#: forms/models.py:1050
msgid "Select a valid choice. That choice is not one of the available choices."
msgstr ""
-#: forms/models.py:1098
+#: forms/models.py:1147
#, python-format
msgid "\"%(pk)s\" is not a valid value for a primary key."
msgstr ""
-#: forms/models.py:1109
+#: forms/models.py:1158
msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
-#: forms/util.py:84
+#: forms/util.py:83
#, python-format
msgid ""
"%(datetime)s couldn't be interpreted in time zone %(current_timezone)s; it "
"may be ambiguous or it may not exist."
msgstr ""
-#: forms/widgets.py:327
+#: forms/widgets.py:326
msgid "Currently"
msgstr ""
-#: forms/widgets.py:328
+#: forms/widgets.py:327
msgid "Change"
msgstr ""
-#: forms/widgets.py:329
+#: forms/widgets.py:328
msgid "Clear"
msgstr ""
-#: forms/widgets.py:547
+#: forms/widgets.py:546
msgid "Unknown"
msgstr ""
-#: forms/widgets.py:548
+#: forms/widgets.py:547
msgid "Yes"
msgstr ""
-#: forms/widgets.py:549
+#: forms/widgets.py:548
msgid "No"
msgstr ""
-#: template/defaultfilters.py:785
+#: template/defaultfilters.py:784
msgid "yes,no,maybe"
msgstr ""
-#: template/defaultfilters.py:813 template/defaultfilters.py:825
+#: template/defaultfilters.py:812 template/defaultfilters.py:824
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] ""
msgstr[1] ""
-#: template/defaultfilters.py:827
+#: template/defaultfilters.py:826
#, python-format
msgid "%s KB"
msgstr ""
-#: template/defaultfilters.py:829
+#: template/defaultfilters.py:828
#, python-format
msgid "%s MB"
msgstr ""
-#: template/defaultfilters.py:831
+#: template/defaultfilters.py:830
#, python-format
msgid "%s GB"
msgstr ""
-#: template/defaultfilters.py:833
+#: template/defaultfilters.py:832
#, python-format
msgid "%s TB"
msgstr ""
-#: template/defaultfilters.py:835
+#: template/defaultfilters.py:834
#, python-format
msgid "%s PB"
msgstr ""
@@ -1142,64 +1152,68 @@ msgstr ""
msgid "The '_imaging' module for the PIL could not be imported: %s"
msgstr ""
-#: utils/text.py:70
+#: utils/ipv6.py:9
+msgid "This is not a valid IPv6 address."
+msgstr ""
+
+#: utils/text.py:69
#, python-format
msgctxt "String to return when truncating text"
msgid "%(truncated_text)s..."
msgstr ""
-#: utils/text.py:225
+#: utils/text.py:224
msgid "or"
msgstr ""
#. Translators: This string is used as a separator between list elements
-#: utils/text.py:242 utils/timesince.py:55
+#: utils/text.py:241 utils/timesince.py:56
msgid ", "
msgstr ""
-#: utils/timesince.py:23
+#: utils/timesince.py:24
#, python-format
msgid "%d year"
msgid_plural "%d years"
msgstr[0] ""
msgstr[1] ""
-#: utils/timesince.py:24
+#: utils/timesince.py:25
#, python-format
msgid "%d month"
msgid_plural "%d months"
msgstr[0] ""
msgstr[1] ""
-#: utils/timesince.py:25
+#: utils/timesince.py:26
#, python-format
msgid "%d week"
msgid_plural "%d weeks"
msgstr[0] ""
msgstr[1] ""
-#: utils/timesince.py:26
+#: utils/timesince.py:27
#, python-format
msgid "%d day"
msgid_plural "%d days"
msgstr[0] ""
msgstr[1] ""
-#: utils/timesince.py:27
+#: utils/timesince.py:28
#, python-format
msgid "%d hour"
msgid_plural "%d hours"
msgstr[0] ""
msgstr[1] ""
-#: utils/timesince.py:28
+#: utils/timesince.py:29
#, python-format
msgid "%d minute"
msgid_plural "%d minutes"
msgstr[0] ""
msgstr[1] ""
-#: utils/timesince.py:44
+#: utils/timesince.py:45
msgid "0 minutes"
msgstr ""
diff --git a/django/contrib/formtools/locale/en/LC_MESSAGES/django.po b/django/contrib/formtools/locale/en/LC_MESSAGES/django.po
index 048b9fea4564..caea4ba79192 100644
--- a/django/contrib/formtools/locale/en/LC_MESSAGES/django.po
+++ b/django/contrib/formtools/locale/en/LC_MESSAGES/django.po
@@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-05-02 16:18+0200\n"
+"POT-Creation-Date: 2013-08-16 15:07+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English \n"
@@ -24,3 +24,7 @@ msgstr ""
#: templates/formtools/wizard/wizard_form.html:18
msgid "submit"
msgstr ""
+
+#: wizard/views.py:275
+msgid "ManagementForm data is missing or has been tampered."
+msgstr ""
From a6ac4f90d0d09da884289d6f03df4e4800c8f742 Mon Sep 17 00:00:00 2001
From: Harm Geerts
Date: Wed, 31 Jul 2013 22:35:44 +0200
Subject: [PATCH 155/944] [1.6.x] Fixed #20829 -- Skip postgis metadata tables
with introspection
Backport of 24088618 from master.
---
django/contrib/gis/db/backends/postgis/introspection.py | 8 ++++++++
django/db/backends/postgresql_psycopg2/introspection.py | 6 ++++--
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/django/contrib/gis/db/backends/postgis/introspection.py b/django/contrib/gis/db/backends/postgis/introspection.py
index 7962d19ff99d..7df09d09371e 100644
--- a/django/contrib/gis/db/backends/postgis/introspection.py
+++ b/django/contrib/gis/db/backends/postgis/introspection.py
@@ -9,6 +9,14 @@ class PostGISIntrospection(DatabaseIntrospection):
# introspection is actually performed.
postgis_types_reverse = {}
+ ignored_tables = DatabaseIntrospection.ignored_tables + [
+ 'geography_columns',
+ 'geometry_columns',
+ 'raster_columns',
+ 'spatial_ref_sys',
+ 'raster_overviews',
+ ]
+
def get_postgis_types(self):
"""
Returns a dictionary with keys that are the PostgreSQL object
diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py
index c334b9d6d0b0..ea4e3e11f3ce 100644
--- a/django/db/backends/postgresql_psycopg2/introspection.py
+++ b/django/db/backends/postgresql_psycopg2/introspection.py
@@ -25,7 +25,9 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
1266: 'TimeField',
1700: 'DecimalField',
}
-
+
+ ignored_tables = []
+
def get_table_list(self, cursor):
"Returns a list of table names in the current database."
cursor.execute("""
@@ -35,7 +37,7 @@ def get_table_list(self, cursor):
WHERE c.relkind IN ('r', 'v', '')
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
AND pg_catalog.pg_table_is_visible(c.oid)""")
- return [row[0] for row in cursor.fetchall()]
+ return [row[0] for row in cursor.fetchall() if row[0] not in self.ignored_tables]
def get_table_description(self, cursor, table_name):
"Returns a description of the table, with the DB-API cursor.description interface."
From e7a6eaf5fee9c5854e0d2d86f21132ac4a1415f6 Mon Sep 17 00:00:00 2001
From: Simon Charette
Date: Mon, 19 Aug 2013 18:42:48 -0400
Subject: [PATCH 156/944] [1.6.x] Correctly format missing Pillow/PIL
exceptions messages. refs #19934
Backport of b9590a6935 from master.
---
django/utils/image.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/django/utils/image.py b/django/utils/image.py
index d251ab9d0bfa..bb731929a892 100644
--- a/django/utils/image.py
+++ b/django/utils/image.py
@@ -102,7 +102,7 @@ def _detect_image_library():
except ImportError as err:
# Neither worked, so it's likely not installed.
raise ImproperlyConfigured(
- _("Neither Pillow nor PIL could be imported: %s" % err)
+ _("Neither Pillow nor PIL could be imported: %s") % err
)
# ``Image.alpha_composite`` was added to Pillow in SHA: e414c6 & is not
@@ -125,7 +125,7 @@ def _detect_image_library():
except ImportError as err:
raise ImproperlyConfigured(
_("The '_imaging' module for the PIL could not be "
- "imported: %s" % err)
+ "imported: %s") % err
)
# Try to import ImageFile as well.
From f0bc2865ff9d85c952fa86ae19aee062a5e883cd Mon Sep 17 00:00:00 2001
From: Simon Charette
Date: Mon, 19 Aug 2013 23:14:21 -0400
Subject: [PATCH 157/944] Fixed #20943 -- Weakly reference senders when caching
their associated receivers
Backport of e55ca60903 from master.
---
django/db/models/signals.py | 2 +-
django/dispatch/dispatcher.py | 12 ++++++++----
tests/dispatch/tests/test_dispatcher.py | 21 +++++++++++++++++++++
3 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/django/db/models/signals.py b/django/db/models/signals.py
index 3e321893c1ee..07824421d882 100644
--- a/django/db/models/signals.py
+++ b/django/db/models/signals.py
@@ -13,6 +13,6 @@
post_delete = Signal(providing_args=["instance", "using"], use_caching=True)
pre_syncdb = Signal(providing_args=["app", "create_models", "verbosity", "interactive", "db"])
-post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive", "db"], use_caching=True)
+post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive", "db"])
m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"], use_caching=True)
diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py
index 65c5c408ff2c..a8cdc93b215d 100644
--- a/django/dispatch/dispatcher.py
+++ b/django/dispatch/dispatcher.py
@@ -4,8 +4,10 @@
from django.dispatch import saferef
from django.utils.six.moves import xrange
+
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
+
def _make_id(target):
if hasattr(target, '__func__'):
return (id(target.__self__), id(target.__func__))
@@ -15,6 +17,7 @@ def _make_id(target):
# A marker for caching
NO_RECEIVERS = object()
+
class Signal(object):
"""
Base class for all signals
@@ -42,7 +45,7 @@ def __init__(self, providing_args=None, use_caching=False):
# distinct sender we cache the receivers that sender has in
# 'sender_receivers_cache'. The cache is cleaned when .connect() or
# .disconnect() is called and populated on send().
- self.sender_receivers_cache = {}
+ self.sender_receivers_cache = weakref.WeakKeyDictionary() if use_caching else {}
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
"""
@@ -116,7 +119,7 @@ def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
break
else:
self.receivers.append((lookup_key, receiver))
- self.sender_receivers_cache = {}
+ self.sender_receivers_cache.clear()
def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
"""
@@ -151,7 +154,7 @@ def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
if r_key == lookup_key:
del self.receivers[index]
break
- self.sender_receivers_cache = {}
+ self.sender_receivers_cache.clear()
def has_listeners(self, sender=None):
return bool(self._live_receivers(sender))
@@ -276,7 +279,8 @@ def _remove_receiver(self, receiver):
for idx, (r_key, _) in enumerate(reversed(self.receivers)):
if r_key == key:
del self.receivers[last_idx - idx]
- self.sender_receivers_cache = {}
+ self.sender_receivers_cache.clear()
+
def receiver(signal, **kwargs):
"""
diff --git a/tests/dispatch/tests/test_dispatcher.py b/tests/dispatch/tests/test_dispatcher.py
index a1d4c7e17692..2586aeee62c0 100644
--- a/tests/dispatch/tests/test_dispatcher.py
+++ b/tests/dispatch/tests/test_dispatcher.py
@@ -1,6 +1,7 @@
import gc
import sys
import time
+import weakref
from django.dispatch import Signal, receiver
from django.utils import unittest
@@ -35,6 +36,8 @@ def a(self, val, **kwargs):
a_signal = Signal(providing_args=["val"])
b_signal = Signal(providing_args=["val"])
c_signal = Signal(providing_args=["val"])
+d_signal = Signal(providing_args=["val"], use_caching=True)
+
class DispatcherTests(unittest.TestCase):
"""Test suite for dispatcher (barely started)"""
@@ -72,6 +75,24 @@ def testGarbageCollected(self):
self.assertEqual(result, expected)
self._testIsClean(a_signal)
+ def testCachedGarbagedCollected(self):
+ """
+ Make sure signal caching sender receivers don't prevent garbage
+ collection of senders.
+ """
+ class sender:
+ pass
+ wref = weakref.ref(sender)
+ d_signal.connect(receiver_1_arg)
+ d_signal.send(sender, val='garbage')
+ del sender
+ garbage_collect()
+ try:
+ self.assertIsNone(wref())
+ finally:
+ # Disconnect after reference check since it flushes the tested cache.
+ d_signal.disconnect(receiver_1_arg)
+
def testMultipleRegistration(self):
a = Callable()
a_signal.connect(a)
From 3ae585b44977d0753a5e5f142b790716d6939258 Mon Sep 17 00:00:00 2001
From: Simon Charette
Date: Tue, 20 Aug 2013 04:01:42 -0400
Subject: [PATCH 158/944] [1.6.x] Fixed a test failure introduced in
f0bc2865ff.
`classobj` objects cannot be weakly referenced on Python 2.6
---
tests/dispatch/tests/test_dispatcher.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/dispatch/tests/test_dispatcher.py b/tests/dispatch/tests/test_dispatcher.py
index 2586aeee62c0..b5edf2867da5 100644
--- a/tests/dispatch/tests/test_dispatcher.py
+++ b/tests/dispatch/tests/test_dispatcher.py
@@ -80,7 +80,7 @@ def testCachedGarbagedCollected(self):
Make sure signal caching sender receivers don't prevent garbage
collection of senders.
"""
- class sender:
+ class sender(object):
pass
wref = weakref.ref(sender)
d_signal.connect(receiver_1_arg)
From 2b1101a4a6acbf9350b62638a04edcf20ad83e97 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?=
Date: Tue, 20 Aug 2013 16:23:25 +0300
Subject: [PATCH 159/944] [1.6.x] Fixed #20820 -- Model inheritance + m2m
fixture loading regression
Tests by Tim Graham, report from jeroen.pulles@redslider.net.
Backport of 1ed77e7782 from master
---
django/db/models/fields/related.py | 11 ++++++++++-
tests/fixtures_regress/fixtures/special-article.json | 10 ++++++++++
tests/fixtures_regress/models.py | 5 +++++
tests/fixtures_regress/tests.py | 11 +++++++++++
4 files changed, 36 insertions(+), 1 deletion(-)
create mode 100644 tests/fixtures_regress/fixtures/special-article.json
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 5c13205fbbef..2813a39567a4 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -989,7 +989,16 @@ def get_foreign_related_value(self, instance):
@staticmethod
def get_instance_value_for_fields(instance, fields):
- return tuple([getattr(instance, field.attname) for field in fields])
+ ret = []
+ for field in fields:
+ # Gotcha: in some cases (like fixture loading) a model can have
+ # different values in parent_ptr_id and parent's id. So, use
+ # instance.pk (that is, parent_ptr_id) when asked for instance.id.
+ if field.primary_key:
+ ret.append(instance.pk)
+ else:
+ ret.append(getattr(instance, field.attname))
+ return tuple(ret)
def get_attname_column(self):
attname, column = super(ForeignObject, self).get_attname_column()
diff --git a/tests/fixtures_regress/fixtures/special-article.json b/tests/fixtures_regress/fixtures/special-article.json
new file mode 100644
index 000000000000..435ceb7ca4fa
--- /dev/null
+++ b/tests/fixtures_regress/fixtures/special-article.json
@@ -0,0 +1,10 @@
+[
+ {
+ "pk": 1,
+ "model": "fixtures_regress.specialarticle",
+ "fields": {
+ "title": "Article Title 1",
+ "channels": []
+ }
+ }
+]
diff --git a/tests/fixtures_regress/models.py b/tests/fixtures_regress/models.py
index 1cc30d2e51fc..7ba069ad08a0 100644
--- a/tests/fixtures_regress/models.py
+++ b/tests/fixtures_regress/models.py
@@ -70,6 +70,11 @@ class Meta:
ordering = ('id',)
+# Subclass of a model with a ManyToManyField for test_ticket_20820
+class SpecialArticle(Article):
+ pass
+
+
# Models to regression test #11428
@python_2_unicode_compatible
class Widget(models.Model):
diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py
index a6bad5716db7..1d0fb9e724aa 100644
--- a/tests/fixtures_regress/tests.py
+++ b/tests/fixtures_regress/tests.py
@@ -431,6 +431,17 @@ def test_loaddata_not_existant_fixture_file(self):
self.assertTrue("No fixture 'this_fixture_doesnt_exist' in" in
force_text(stdout_output.getvalue()))
+ def test_ticket_20820(self):
+ """
+ Regression for ticket #20820 -- loaddata on a model that inherits
+ from a model with a M2M shouldn't blow up.
+ """
+ management.call_command(
+ 'loaddata',
+ 'special-article.json',
+ verbosity=0,
+ )
+
class NaturalKeyFixtureTests(TestCase):
From b189169ed031c4e1d8792b19d238db16245ed0ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?=
Date: Tue, 20 Aug 2013 17:48:02 +0300
Subject: [PATCH 160/944] [1.6.x] Fixed invalid testing fixture
Backport of 86f4459f9e from master
---
tests/fixtures_regress/fixtures/special-article.json | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/tests/fixtures_regress/fixtures/special-article.json b/tests/fixtures_regress/fixtures/special-article.json
index 435ceb7ca4fa..a36244acc1a0 100644
--- a/tests/fixtures_regress/fixtures/special-article.json
+++ b/tests/fixtures_regress/fixtures/special-article.json
@@ -1,4 +1,10 @@
[
+ {
+ "pk": 1,
+ "model": "fixtures_regress.article",
+ "fields": {"title": "foof"
+ }
+ },
{
"pk": 1,
"model": "fixtures_regress.specialarticle",
From 12d364a9b0b4bf820a68104a64ba312c7290518b Mon Sep 17 00:00:00 2001
From: Florian Apolloner
Date: Tue, 20 Aug 2013 19:03:33 +0200
Subject: [PATCH 161/944] [1.6.x] Fixed #20933 -- Allowed loaddata to load
fixtures from relative paths.
Backport of 6e846f7627ecf0dc15053624a23bfbf47535972d from master.
---
django/core/management/commands/loaddata.py | 2 +-
docs/howto/initial-data.txt | 4 ++--
tests/fixtures_regress/models.py | 6 ------
tests/fixtures_regress/tests.py | 17 ++++++++++++++++-
4 files changed, 19 insertions(+), 10 deletions(-)
diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py
index 6856e85e450e..e8ec992af488 100644
--- a/django/core/management/commands/loaddata.py
+++ b/django/core/management/commands/loaddata.py
@@ -181,7 +181,7 @@ def _find_fixtures(self, fixture_label):
if self.verbosity >= 2:
self.stdout.write("Loading '%s' fixtures..." % fixture_name)
- if os.path.isabs(fixture_name):
+ if os.path.sep in fixture_name:
fixture_dirs = [os.path.dirname(fixture_name)]
fixture_name = os.path.basename(fixture_name)
else:
diff --git a/docs/howto/initial-data.txt b/docs/howto/initial-data.txt
index cea07bfea369..c84664fd9aa7 100644
--- a/docs/howto/initial-data.txt
+++ b/docs/howto/initial-data.txt
@@ -90,8 +90,8 @@ fixtures. You can set the :setting:`FIXTURE_DIRS` setting to a list of
additional directories where Django should look.
When running :djadmin:`manage.py loaddata `, you can also
-specify an absolute path to a fixture file, which overrides searching
-the usual directories.
+specify a path to a fixture file, which overrides searching the usual
+directories.
.. seealso::
diff --git a/tests/fixtures_regress/models.py b/tests/fixtures_regress/models.py
index 7ba069ad08a0..ee93de22937f 100644
--- a/tests/fixtures_regress/models.py
+++ b/tests/fixtures_regress/models.py
@@ -39,12 +39,6 @@ def __str__(self):
class Absolute(models.Model):
name = models.CharField(max_length=40)
- load_count = 0
-
- def __init__(self, *args, **kwargs):
- super(Absolute, self).__init__(*args, **kwargs)
- Absolute.load_count += 1
-
class Parent(models.Model):
name = models.CharField(max_length=10)
diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py
index 1d0fb9e724aa..58339c9280ff 100644
--- a/tests/fixtures_regress/tests.py
+++ b/tests/fixtures_regress/tests.py
@@ -148,7 +148,22 @@ def test_absolute_path(self):
load_absolute_path,
verbosity=0,
)
- self.assertEqual(Absolute.load_count, 1)
+ self.assertEqual(Absolute.objects.count(), 1)
+
+ def test_relative_path(self):
+ directory = os.path.dirname(upath(__file__))
+ relative_path = os.path.join('fixtures', 'absolute.json')
+ cwd = os.getcwd()
+ try:
+ os.chdir(directory)
+ management.call_command(
+ 'loaddata',
+ relative_path,
+ verbosity=0,
+ )
+ finally:
+ os.chdir(cwd)
+ self.assertEqual(Absolute.objects.count(), 1)
def test_unknown_format(self):
"""
From e7d4d41a30b4cf60690fe55e47b4e10403fc8b46 Mon Sep 17 00:00:00 2001
From: Kevin Christopher Henry
Date: Tue, 20 Aug 2013 23:22:25 -0400
Subject: [PATCH 162/944] [1.6.x] Documentation - Noted that OneToOneField
doesn't respect unique.
Added OneToOneField to the list of model fields for which the unique
argument isn't valid. (OneToOneFields are inherently unique, and if
the user supplies a value for unique it is ignored / overwritten.)
---
docs/ref/models/fields.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index 7159ed8b709d..fe83b54e1cdf 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -272,8 +272,8 @@ you try to save a model with a duplicate value in a :attr:`~Field.unique`
field, a :exc:`django.db.IntegrityError` will be raised by the model's
:meth:`~django.db.models.Model.save` method.
-This option is valid on all field types except :class:`ManyToManyField` and
-:class:`FileField`.
+This option is valid on all field types except :class:`ManyToManyField`,
+:class:`OneToOneField`, and :class:`FileField`.
Note that when ``unique`` is ``True``, you don't need to specify
:attr:`~Field.db_index`, because ``unique`` implies the creation of an index.
From b0821e6d3a5dcb8b040ce2323f49b46dabbc9e37 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Wed, 21 Aug 2013 09:01:52 -0400
Subject: [PATCH 163/944] [1.6.x] Fixed docstring typo, thanks minddust.
Backport of d3ed15b79d from master
---
django/template/loader_tags.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
index 767f0e5ff822..a330c42a3774 100644
--- a/django/template/loader_tags.py
+++ b/django/template/loader_tags.py
@@ -206,7 +206,7 @@ def do_extends(parser, token):
uses the literal value "base" as the name of the parent template to extend,
or ``{% extends variable %}`` uses the value of ``variable`` as either the
name of the parent template to extend (if it evaluates to a string) or as
- the parent tempate itelf (if it evaluates to a Template object).
+ the parent tempate itself (if it evaluates to a Template object).
"""
bits = token.split_contents()
if len(bits) != 2:
From 28b1317fd815325b5a58a7acae12ca45de5a6ac9 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Wed, 21 Aug 2013 10:49:50 -0400
Subject: [PATCH 164/944] [1.6.x] Fixed #20949 -- Typo #2 in docstring
Backport of 0073f1d94f from master
---
django/template/loader_tags.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
index a330c42a3774..85ffcf17bb6a 100644
--- a/django/template/loader_tags.py
+++ b/django/template/loader_tags.py
@@ -206,7 +206,7 @@ def do_extends(parser, token):
uses the literal value "base" as the name of the parent template to extend,
or ``{% extends variable %}`` uses the value of ``variable`` as either the
name of the parent template to extend (if it evaluates to a string) or as
- the parent tempate itself (if it evaluates to a Template object).
+ the parent template itself (if it evaluates to a Template object).
"""
bits = token.split_contents()
if len(bits) != 2:
From 69a4594cb7b4eab7c689866557c491c85cdf3aa2 Mon Sep 17 00:00:00 2001
From: Kevin Christopher Henry
Date: Wed, 21 Aug 2013 15:38:07 -0400
Subject: [PATCH 165/944] [1.6.x] Documentation -- Corrected error about
Model.full_clean()
Although the ModelForm validation code was changed to call
Model.full_clean(), the documentation still said otherwise. The
offending phrase was removed.
Backport of bb011cf809 from master
---
docs/ref/models/instances.txt | 11 +++--------
1 file changed, 3 insertions(+), 8 deletions(-)
diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt
index c9c01d56796e..4bb6af2a5142 100644
--- a/docs/ref/models/instances.txt
+++ b/docs/ref/models/instances.txt
@@ -104,14 +104,9 @@ aren't present on your form from being validated since any errors raised could
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. 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.
-
-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::
+your model's :meth:`~Model.save()` method. You'll need to call it manually
+when you want to run one-step model validation for your own manually created
+models. For example::
from django.core.exceptions import ValidationError
try:
From 161e26c2ec9f88bf0395941aaa2fd193b110affd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?=
Date: Thu, 22 Aug 2013 10:42:10 +0300
Subject: [PATCH 166/944] [1.6.x] Fixed #20955 -- select_related regression
In cases where the same connection (from model A to model B along the
same field) was needed multiple times in a select_related query, the
join setup code mistakenly reused an existing join.
Backpatch of 8d65b6082c8bf5df25608d8733470879a8a61d7d.
Conflicts:
django/db/models/sql/compiler.py
tests/queries/tests.py
---
django/db/models/sql/compiler.py | 5 ++---
tests/queries/models.py | 26 ++++++++++++++++++++++++++
tests/queries/tests.py | 22 +++++++++++++++++++++-
3 files changed, 49 insertions(+), 4 deletions(-)
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index f70750abed12..e768cedf885f 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -687,9 +687,8 @@ def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1,
# Use True here because we are looking at the _reverse_ side of
# the relation, which is always nullable.
new_nullable = True
- table = model._meta.db_table
- self.fill_related_selections(model._meta, table, cur_depth+1,
- next, restricted, new_nullable)
+ self.fill_related_selections(model._meta, alias, cur_depth + 1,
+ next, restricted, new_nullable)
def deferred_to_columns(self):
"""
diff --git a/tests/queries/models.py b/tests/queries/models.py
index 71346d8be93d..724824626155 100644
--- a/tests/queries/models.py
+++ b/tests/queries/models.py
@@ -497,3 +497,29 @@ class Meta:
def __str__(self):
return '%s' % self.pk
+
+class BaseUser(models.Model):
+ pass
+
+@python_2_unicode_compatible
+class Task(models.Model):
+ title = models.CharField(max_length=10)
+ owner = models.ForeignKey(BaseUser, related_name='owner')
+ creator = models.ForeignKey(BaseUser, related_name='creator')
+
+ def __str__(self):
+ return self.title
+
+@python_2_unicode_compatible
+class Staff(models.Model):
+ name = models.CharField(max_length=10)
+
+ def __str__(self):
+ return self.name
+
+@python_2_unicode_compatible
+class StaffUser(BaseUser):
+ staff = models.OneToOneField(Staff, related_name='user')
+
+ def __str__(self):
+ return self.staff
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index ff6c0bb7a1d7..fbeadf726a44 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -25,7 +25,7 @@
OneToOneCategory, NullableName, ProxyCategory, SingleObject, RelatedObject,
ModelA, ModelB, ModelC, ModelD, Responsibility, Job, JobResponsibilities,
BaseA, FK1, Identifier, Program, Channel, Page, Paragraph, Chapter, Book,
- MyObject, Order, OrderItem)
+ MyObject, Order, OrderItem, Task, Staff, StaffUser)
class BaseQuerysetTest(TestCase):
@@ -2943,3 +2943,23 @@ def test_wrong_type_lookup(self):
self.assertQuerysetEqual(
ObjectB.objects.filter(objecta__in=[wrong_type]),
[ob], lambda x: x)
+
+class Ticket20955Tests(TestCase):
+ def test_ticket_20955(self):
+ jack = Staff.objects.create(name='jackstaff')
+ jackstaff = StaffUser.objects.create(staff=jack)
+ jill = Staff.objects.create(name='jillstaff')
+ jillstaff = StaffUser.objects.create(staff=jill)
+ task = Task.objects.create(creator=jackstaff, owner=jillstaff, title="task")
+ task_get = Task.objects.get(pk=task.pk)
+ # Load data so that assertNumQueries doesn't complain about the get
+ # version's queries.
+ task_get.creator.staffuser.staff
+ task_get.owner.staffuser.staff
+ task_select_related = Task.objects.select_related(
+ 'creator__staffuser__staff', 'owner__staffuser__staff').get(pk=task.pk)
+ with self.assertNumQueries(0):
+ self.assertEqual(task_select_related.creator.staffuser.staff,
+ task_get.creator.staffuser.staff)
+ self.assertEqual(task_select_related.owner.staffuser.staff,
+ task_get.owner.staffuser.staff)
From ff92a6eb5b87c65ca47568e0ca37df949e17feb2 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Thu, 22 Aug 2013 09:14:06 +0200
Subject: [PATCH 167/944] [1.6.x] Moved translator comment just above the
target string
Backport of 8cd874298 from master.
---
django/conf/locale/en/LC_MESSAGES/django.po | 4 +++-
django/forms/forms.py | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/django/conf/locale/en/LC_MESSAGES/django.po b/django/conf/locale/en/LC_MESSAGES/django.po
index a707cb9ba50b..de23c8dcf2e6 100644
--- a/django/conf/locale/en/LC_MESSAGES/django.po
+++ b/django/conf/locale/en/LC_MESSAGES/django.po
@@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-08-16 15:07+0200\n"
+"POT-Creation-Date: 2013-08-22 09:54+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English \n"
@@ -717,6 +717,8 @@ msgstr ""
msgid "(Hidden field %(name)s) %(error)s"
msgstr ""
+#. Translators: If found as last label character, these punctuation
+#. characters will prevent the default label_suffix to be appended to the label
#: forms/forms.py:527
msgid ":?.!"
msgstr ""
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 10ae1248da1f..0dfcdebbf0ac 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -521,9 +521,9 @@ def label_tag(self, contents=None, attrs=None, label_suffix=None):
"""
contents = contents or self.label
# Only add the suffix if the label does not end in punctuation.
+ label_suffix = label_suffix if label_suffix is not None else self.form.label_suffix
# Translators: If found as last label character, these punctuation
# characters will prevent the default label_suffix to be appended to the label
- label_suffix = label_suffix if label_suffix is not None else self.form.label_suffix
if label_suffix and contents and contents[-1] not in _(':?.!'):
contents = format_html('{0}{1}', contents, label_suffix)
widget = self.field.widget
From 26a4c835987d93f845cc2915d08780854a90c587 Mon Sep 17 00:00:00 2001
From: Ramiro Morales
Date: Thu, 22 Aug 2013 08:25:21 -0300
Subject: [PATCH 168/944] [1.6.x] Made description of LANGUAGE_CODE setting
more clear.
297f5af222bde02a7cdd005da2e4b00ec81801de from master.
---
docs/ref/settings.txt | 15 +++++++++++++--
docs/topics/i18n/translation.txt | 13 ++++++++-----
2 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 1916f6377fb1..46482b80a648 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -1257,11 +1257,22 @@ LANGUAGE_CODE
Default: ``'en-us'``
-A string representing the language code for this installation. This should be
-in standard :term:`language format`. For example, U.S. English
+A string representing the language code for this installation. This should be in
+standard :term:`language ID format `. For example, U.S. English
is ``"en-us"``. See also the `list of language identifiers`_ and
:doc:`/topics/i18n/index`.
+:setting:`USE_I18N` must be active to this setting to have any effect.
+
+it serves two purposes:
+
+* If the locale middleware isn't in use, it decides which translation is served
+ to all users.
+* If the locale middleware is active, it provides the fallback translation when
+ no translation exist for a given literal to the user preferred language.
+
+See :ref:`how-django-discovers-language-preference` for more details.
+
.. _list of language identifiers: http://www.i18nguy.com/unicode/language-identifiers.html
.. setting:: LANGUAGE_COOKIE_NAME
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index e724837ce156..4052b50b07f8 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -1552,14 +1552,17 @@ should be used -- installation-wide, for a particular user, or both.
To set an installation-wide language preference, set :setting:`LANGUAGE_CODE`.
Django uses this language as the default translation -- the final attempt if no
-other translator finds a translation.
+better matching translation is found by one of the methods employed by the
+locale middleware (see below).
-If all you want to do is run Django with your native language, and a language
-file is available for it, all you need to do is set :setting:`LANGUAGE_CODE`.
+If all you want to do is run Django with your native language all you need to do
+is set :setting:`LANGUAGE_CODE` and make sure the corresponding :term:`message
+files ` and their compiled versions (``.mo``) exist.
If you want to let each individual user specify which language he or she
-prefers, use ``LocaleMiddleware``. ``LocaleMiddleware`` enables language
-selection based on data from the request. It customizes content for each user.
+prefers, the you also need to use use the ``LocaleMiddleware``.
+``LocaleMiddleware`` enables language selection based on data from the request.
+It customizes content for each user.
To use ``LocaleMiddleware``, add ``'django.middleware.locale.LocaleMiddleware'``
to your :setting:`MIDDLEWARE_CLASSES` setting. Because middleware order
From 8d02c378ab78694991483bc3f0b01e2e89f73264 Mon Sep 17 00:00:00 2001
From: Marc Tamlyn
Date: Tue, 20 Aug 2013 10:26:48 +0100
Subject: [PATCH 169/944] [1.6.x] Fixed #20944 -- Removed inaccurate statement
about View.dispatch().
Backport of bac4d03ce6 from master
---
docs/ref/class-based-views/base.txt | 4 ----
1 file changed, 4 deletions(-)
diff --git a/docs/ref/class-based-views/base.txt b/docs/ref/class-based-views/base.txt
index 24058311a4c2..da4c66280efd 100644
--- a/docs/ref/class-based-views/base.txt
+++ b/docs/ref/class-based-views/base.txt
@@ -79,10 +79,6 @@ View
you can override the ``head()`` method. See
:ref:`supporting-other-http-methods` for an example.
- The default implementation also sets ``request``, ``args`` and
- ``kwargs`` as instance variables, so any method on the view can know
- the full details of the request that was made to invoke the view.
-
.. method:: http_method_not_allowed(request, *args, **kwargs)
If the view was called with a HTTP method it doesn't support, this
From bf9382fb02c54a5991fb2eb91322befe21177a49 Mon Sep 17 00:00:00 2001
From: Kevin Christopher Henry
Date: Thu, 22 Aug 2013 04:39:31 -0400
Subject: [PATCH 170/944] [1.6.x] Documentation -- Clarified use of 'view' in
test client introduction.
Backport of 2e926b041c from master
---
docs/topics/testing/overview.txt | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt
index d56b1be20f49..72e2c4f32d12 100644
--- a/docs/topics/testing/overview.txt
+++ b/docs/topics/testing/overview.txt
@@ -346,7 +346,8 @@ Some of the things you can do with the test client are:
everything from low-level HTTP (result headers and status codes) to
page content.
-* Test that the correct view is executed for a given URL.
+* See the chain of redirects (if any) and check the URL and status code at
+ each step.
* Test that a given request is rendered by a given Django template, with
a template context that contains certain values.
@@ -355,8 +356,8 @@ Note that the test client is not intended to be a replacement for Selenium_ or
other "in-browser" frameworks. Django's test client has a different focus. In
short:
-* Use Django's test client to establish that the correct view is being
- called and that the view is collecting the correct context data.
+* Use Django's test client to establish that the correct template is being
+ rendered and that the template is passed the correct context data.
* Use in-browser frameworks like Selenium_ to test *rendered* HTML and the
*behavior* of Web pages, namely JavaScript functionality. Django also
From 5f061986b9903b335e4bfe41cf172d710604d5cb Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Wed, 21 Aug 2013 20:12:19 -0400
Subject: [PATCH 171/944] [1.6.x] Fixed #20922 -- Allowed customizing the
serializer used by contrib.sessions
Added settings.SESSION_SERIALIZER which is the import path of a serializer
to use for sessions.
Thanks apollo13, carljm, shaib, akaariai, charettes, and dstufft for reviews.
Backport of b0ce6fe656 from master
---
django/conf/global_settings.py | 1 +
django/contrib/messages/storage/session.py | 17 +++-
django/contrib/messages/tests/base.py | 1 +
django/contrib/messages/tests/test_session.py | 4 +-
django/contrib/sessions/backends/base.py | 21 ++---
.../sessions/backends/signed_cookies.py | 21 +----
django/contrib/sessions/models.py | 2 +-
django/contrib/sessions/serializers.py | 20 ++++
django/contrib/sessions/tests.py | 34 ++++---
docs/ref/settings.txt | 26 +++++-
docs/releases/1.6.txt | 23 +++++
docs/topics/http/sessions.txt | 92 +++++++++++++++++--
tests/defer_regress/tests.py | 40 ++++----
13 files changed, 225 insertions(+), 77 deletions(-)
create mode 100644 django/contrib/sessions/serializers.py
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 596f4ae78a46..68d9ded9156e 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -469,6 +469,7 @@
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether a user's session cookie expires when the Web browser is closed.
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data
SESSION_FILE_PATH = None # Directory to store session files if using the file session module. If None, the backend will use a sensible default.
+SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer' # class to serialize session data
#########
# CACHE #
diff --git a/django/contrib/messages/storage/session.py b/django/contrib/messages/storage/session.py
index 225dfda28956..c3e293c22e97 100644
--- a/django/contrib/messages/storage/session.py
+++ b/django/contrib/messages/storage/session.py
@@ -1,4 +1,8 @@
+import json
+
from django.contrib.messages.storage.base import BaseStorage
+from django.contrib.messages.storage.cookie import MessageEncoder, MessageDecoder
+from django.utils import six
class SessionStorage(BaseStorage):
@@ -20,14 +24,23 @@ def _get(self, *args, **kwargs):
always stores everything it is given, so return True for the
all_retrieved flag.
"""
- return self.request.session.get(self.session_key), True
+ return self.deserialize_messages(self.request.session.get(self.session_key)), True
def _store(self, messages, response, *args, **kwargs):
"""
Stores a list of messages to the request's session.
"""
if messages:
- self.request.session[self.session_key] = messages
+ self.request.session[self.session_key] = self.serialize_messages(messages)
else:
self.request.session.pop(self.session_key, None)
return []
+
+ def serialize_messages(self, messages):
+ encoder = MessageEncoder(separators=(',', ':'))
+ return encoder.encode(messages)
+
+ def deserialize_messages(self, data):
+ if data and isinstance(data, six.string_types):
+ return json.loads(data, cls=MessageDecoder)
+ return data
diff --git a/django/contrib/messages/tests/base.py b/django/contrib/messages/tests/base.py
index 0d689729aaaa..f8a089c33560 100644
--- a/django/contrib/messages/tests/base.py
+++ b/django/contrib/messages/tests/base.py
@@ -60,6 +60,7 @@ def setUp(self):
MESSAGE_TAGS = '',
MESSAGE_STORAGE = '%s.%s' % (self.storage_class.__module__,
self.storage_class.__name__),
+ SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer',
)
self.settings_override.enable()
diff --git a/django/contrib/messages/tests/test_session.py b/django/contrib/messages/tests/test_session.py
index 2ce564b773ae..940e1c02d03f 100644
--- a/django/contrib/messages/tests/test_session.py
+++ b/django/contrib/messages/tests/test_session.py
@@ -11,13 +11,13 @@ def set_session_data(storage, messages):
Sets the messages into the backend request's session and remove the
backend's loaded data cache.
"""
- storage.request.session[storage.session_key] = messages
+ storage.request.session[storage.session_key] = storage.serialize_messages(messages)
if hasattr(storage, '_loaded_data'):
del storage._loaded_data
def stored_session_messages_count(storage):
- data = storage.request.session.get(storage.session_key, [])
+ data = storage.deserialize_messages(storage.request.session.get(storage.session_key, []))
return len(data)
diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py
index 759d7ac7ad59..7f5e958a608d 100644
--- a/django/contrib/sessions/backends/base.py
+++ b/django/contrib/sessions/backends/base.py
@@ -3,11 +3,6 @@
import base64
from datetime import datetime, timedelta
import logging
-
-try:
- from django.utils.six.moves import cPickle as pickle
-except ImportError:
- import pickle
import string
from django.conf import settings
@@ -17,6 +12,7 @@
from django.utils.crypto import salted_hmac
from django.utils import timezone
from django.utils.encoding import force_bytes, force_text
+from django.utils.module_loading import import_by_path
from django.contrib.sessions.exceptions import SuspiciousSession
@@ -42,6 +38,7 @@ def __init__(self, session_key=None):
self._session_key = session_key
self.accessed = False
self.modified = False
+ self.serializer = import_by_path(settings.SESSION_SERIALIZER)
def __contains__(self, key):
return key in self._session
@@ -86,21 +83,21 @@ def _hash(self, value):
return salted_hmac(key_salt, value).hexdigest()
def encode(self, session_dict):
- "Returns the given session dictionary pickled and encoded as a string."
- pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL)
- hash = self._hash(pickled)
- return base64.b64encode(hash.encode() + b":" + pickled).decode('ascii')
+ "Returns the given session dictionary serialized and encoded as a string."
+ serialized = self.serializer().dumps(session_dict)
+ hash = self._hash(serialized)
+ return base64.b64encode(hash.encode() + b":" + serialized).decode('ascii')
def decode(self, session_data):
encoded_data = base64.b64decode(force_bytes(session_data))
try:
# could produce ValueError if there is no ':'
- hash, pickled = encoded_data.split(b':', 1)
- expected_hash = self._hash(pickled)
+ hash, serialized = encoded_data.split(b':', 1)
+ expected_hash = self._hash(serialized)
if not constant_time_compare(hash.decode(), expected_hash):
raise SuspiciousSession("Session data corrupted")
else:
- return pickle.loads(pickled)
+ return self.serializer().loads(serialized)
except Exception as e:
# ValueError, SuspiciousOperation, unpickling exceptions. If any of
# these happen, just return an empty dictionary (an empty session).
diff --git a/django/contrib/sessions/backends/signed_cookies.py b/django/contrib/sessions/backends/signed_cookies.py
index c2b7a3123f5c..77a6750ce471 100644
--- a/django/contrib/sessions/backends/signed_cookies.py
+++ b/django/contrib/sessions/backends/signed_cookies.py
@@ -1,26 +1,9 @@
-try:
- from django.utils.six.moves import cPickle as pickle
-except ImportError:
- import pickle
-
from django.conf import settings
from django.core import signing
from django.contrib.sessions.backends.base import SessionBase
-class PickleSerializer(object):
- """
- Simple wrapper around pickle to be used in signing.dumps and
- signing.loads.
- """
- def dumps(self, obj):
- return pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
-
- def loads(self, data):
- return pickle.loads(data)
-
-
class SessionStore(SessionBase):
def load(self):
@@ -31,7 +14,7 @@ def load(self):
"""
try:
return signing.loads(self.session_key,
- serializer=PickleSerializer,
+ serializer=self.serializer,
# This doesn't handle non-default expiry dates, see #19201
max_age=settings.SESSION_COOKIE_AGE,
salt='django.contrib.sessions.backends.signed_cookies')
@@ -91,7 +74,7 @@ def _get_session_key(self):
session_cache = getattr(self, '_session_cache', {})
return signing.dumps(session_cache, compress=True,
salt='django.contrib.sessions.backends.signed_cookies',
- serializer=PickleSerializer)
+ serializer=self.serializer)
@classmethod
def clear_expired(cls):
diff --git a/django/contrib/sessions/models.py b/django/contrib/sessions/models.py
index 0179c358b3ae..3a6e31152f08 100644
--- a/django/contrib/sessions/models.py
+++ b/django/contrib/sessions/models.py
@@ -5,7 +5,7 @@
class SessionManager(models.Manager):
def encode(self, session_dict):
"""
- Returns the given session dictionary pickled and encoded as a string.
+ Returns the given session dictionary serialized and encoded as a string.
"""
return SessionStore().encode(session_dict)
diff --git a/django/contrib/sessions/serializers.py b/django/contrib/sessions/serializers.py
new file mode 100644
index 000000000000..92a31c054bbb
--- /dev/null
+++ b/django/contrib/sessions/serializers.py
@@ -0,0 +1,20 @@
+from django.core.signing import JSONSerializer as BaseJSONSerializer
+try:
+ from django.utils.six.moves import cPickle as pickle
+except ImportError:
+ import pickle
+
+
+class PickleSerializer(object):
+ """
+ Simple wrapper around pickle to be used in signing.dumps and
+ signing.loads.
+ """
+ def dumps(self, obj):
+ return pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
+
+ def loads(self, data):
+ return pickle.loads(data)
+
+
+JSONSerializer = BaseJSONSerializer
diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py
index cd8191a6a42e..ff0a70c34d4a 100644
--- a/django/contrib/sessions/tests.py
+++ b/django/contrib/sessions/tests.py
@@ -285,21 +285,25 @@ def test_decode_failure_logged_to_security(self):
def test_actual_expiry(self):
- # Regression test for #19200
- old_session_key = None
- new_session_key = None
- try:
- self.session['foo'] = 'bar'
- self.session.set_expiry(-timedelta(seconds=10))
- self.session.save()
- old_session_key = self.session.session_key
- # With an expiry date in the past, the session expires instantly.
- new_session = self.backend(self.session.session_key)
- new_session_key = new_session.session_key
- self.assertNotIn('foo', new_session)
- finally:
- self.session.delete(old_session_key)
- self.session.delete(new_session_key)
+ # this doesn't work with JSONSerializer (serializing timedelta)
+ with override_settings(SESSION_SERIALIZER='django.contrib.sessions.serializers.PickleSerializer'):
+ self.session = self.backend() # reinitialize after overriding settings
+
+ # Regression test for #19200
+ old_session_key = None
+ new_session_key = None
+ try:
+ self.session['foo'] = 'bar'
+ self.session.set_expiry(-timedelta(seconds=10))
+ self.session.save()
+ old_session_key = self.session.session_key
+ # With an expiry date in the past, the session expires instantly.
+ new_session = self.backend(self.session.session_key)
+ new_session_key = new_session.session_key
+ self.assertNotIn('foo', new_session)
+ finally:
+ self.session.delete(old_session_key)
+ self.session.delete(new_session_key)
class DatabaseSessionTests(SessionTestsMixin, TestCase):
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 46482b80a648..a137b229dcc7 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -2392,7 +2392,7 @@ SESSION_ENGINE
Default: ``django.contrib.sessions.backends.db``
-Controls where Django stores session data. Valid values are:
+Controls where Django stores session data. Included engines are:
* ``'django.contrib.sessions.backends.db'``
* ``'django.contrib.sessions.backends.file'``
@@ -2435,6 +2435,30 @@ Whether to save the session data on every request. If this is ``False``
(default), then the session data will only be saved if it has been modified --
that is, if any of its dictionary values have been assigned or deleted.
+.. setting:: SESSION_SERIALIZER
+
+SESSION_SERIALIZER
+------------------
+
+.. versionadded:: 1.5.3
+
+Default: ``'django.contrib.sessions.serializers.JSONSerializer'``
+
+.. versionchanged:: 1.6
+
+ The default switched from
+ :class:`~django.contrib.sessions.serializers.PickleSerializer` to
+ :class:`~django.contrib.sessions.serializers.JSONSerializer` in Django 1.6.
+
+Full import path of a serializer class to use for serializing session data.
+Included serializers are:
+
+* ``'django.contrib.sessions.serializers.PickleSerializer'``
+* ``'django.contrib.sessions.serializers.JSONSerializer'``
+
+See :ref:`session_serialization` for details, including a warning regarding
+possible remote code execution when using
+:class:`~django.contrib.sessions.serializers.PickleSerializer`.
Sites
=====
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index b545cbcd64f9..105e302b7d57 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -727,6 +727,29 @@ the ``name`` argument so it doesn't conflict with the new url::
You can remove this url pattern after your app has been deployed with Django
1.6 for :setting:`PASSWORD_RESET_TIMEOUT_DAYS`.
+Default session serialization switched to JSON
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Historically, :mod:`django.contrib.sessions` used :mod:`pickle` to serialize
+session data before storing it in the backend. If you're using the :ref:`signed
+cookie session backend` and :setting:`SECRET_KEY` is
+known by an attacker, the attacker could insert a string into his session
+which, when unpickled, executes arbitrary code on the server. The technique for
+doing so is simple and easily available on the internet. Although the cookie
+session storage signs the cookie-stored data to prevent tampering, a
+:setting:`SECRET_KEY` leak immediately escalates to a remote code execution
+vulnerability.
+
+This attack can be mitigated by serializing session data using JSON rather
+than :mod:`pickle`. To facilitate this, Django 1.5.3 introduced a new setting,
+:setting:`SESSION_SERIALIZER`, to customize the session serialization format.
+For backwards compatibility, this setting defaulted to using :mod:`pickle`
+in Django 1.5.3, but we've changed the default to JSON in 1.6. If you upgrade
+and switch from pickle to JSON, sessions created before the upgrade will be
+lost. While JSON serialization does not support all Python objects like
+:mod:`pickle` does, we highly recommend using JSON-serialized sessions. See the
+:ref:`session_serialization` documentation for more details.
+
Miscellaneous
~~~~~~~~~~~~~
diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt
index 772ee122d5ba..1dd8c2ae3d70 100644
--- a/docs/topics/http/sessions.txt
+++ b/docs/topics/http/sessions.txt
@@ -132,8 +132,9 @@ and the :setting:`SECRET_KEY` setting.
.. warning::
- **If the SECRET_KEY is not kept secret, this can lead to arbitrary remote
- code execution.**
+ **If the SECRET_KEY is not kept secret and you are using the**
+ :class:`~django.contrib.sessions.serializers.PickleSerializer`, **this can
+ lead to arbitrary remote code execution.**
An attacker in possession of the :setting:`SECRET_KEY` can not only
generate falsified session data, which your site will trust, but also
@@ -260,7 +261,9 @@ You can edit it multiple times.
in 5 minutes.
* If ``value`` is a ``datetime`` or ``timedelta`` object, the
- session will expire at that specific date/time.
+ session will expire at that specific date/time. Note that ``datetime``
+ and ``timedelta`` values are only serializable if you are using the
+ :class:`~django.contrib.sessions.serializers.PickleSerializer`.
* If ``value`` is ``0``, the user's session cookie will expire
when the user's Web browser is closed.
@@ -307,6 +310,77 @@ You can edit it multiple times.
Removes expired sessions from the session store. This class method is
called by :djadmin:`clearsessions`.
+.. _session_serialization:
+
+Session serialization
+---------------------
+
+.. versionadded:: 1.5.3
+
+ The ability to customize the session serialization format using the
+ :setting:`SESSION_SERIALIZER` settings was added.
+
+.. versionchanged:: 1.6
+
+Before version 1.6, Django defaulted to using :mod:`pickle` to serialize
+session data before storing it in the backend. If you're using the :ref:`signed
+cookie session backend` and :setting:`SECRET_KEY` is
+known by an attacker, the attacker could insert a string into his session
+which, when unpickled, executes arbitrary code on the server. The technique for
+doing so is simple and easily available on the internet. Although the cookie
+session storage signs the cookie-stored data to prevent tampering, a
+:setting:`SECRET_KEY` leak immediately escalates to a remote code execution
+vulnerability.
+
+This attack can be mitigated by serializing session data using JSON rather
+than :mod:`pickle`. To facilitate this, Django 1.5.3 introduced a new setting,
+:setting:`SESSION_SERIALIZER`, to customize the session serialization format.
+For backwards compatibility, this setting defaults to
+using :class:`django.contrib.sessions.serializers.PickleSerializer` in
+Django 1.5.x, but, for security hardening, defaults to
+:class:`django.contrib.sessions.serializers.JSONSerializer` in Django 1.6.
+Even with the caveats described in :ref:`custom-serializers`, we highly
+recommend sticking with JSON serialization *especially if you are using the
+cookie backend*.
+
+Bundled Serializers
+^^^^^^^^^^^^^^^^^^^
+
+.. class:: serializers.JSONSerializer
+
+ A wrapper around the JSON serializer from :mod:`django.core.signing`. Can
+ only serialize basic data types. See the :ref:`custom-serializers` section
+ for more details.
+
+.. class:: serializers.PickleSerializer
+
+ Supports arbitrary Python objects, but, as described above, can lead to a
+ remote code execution vulnerability if :setting:`SECRET_KEY` becomes known
+ by an attacker.
+
+.. _custom-serializers:
+
+Write Your Own Serializer
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Note that unlike :class:`~django.contrib.sessions.serializers.PickleSerializer`,
+the :class:`~django.contrib.sessions.serializers.JSONSerializer` cannot handle
+arbitrary Python data types. As is often the case, there is a trade-off between
+convenience and security. If you wish to store more advanced data types
+including ``datetime`` and ``Decimal`` in JSON backed sessions, you will need
+to write a custom serializer (or convert such values to a JSON serializable
+object before storing them in ``request.session``). While serializing these
+values is fairly straightforward
+(``django.core.serializers.json.DateTimeAwareJSONEncoder`` may be helpful),
+writing a decoder that can reliably get back the same thing that you put in is
+more fragile. For example, you run the risk of returning a ``datetime`` that
+was actually a string that just happened to be in the same format chosen for
+``datetime``\s).
+
+Your serializer class must implement two methods,
+``dumps(self, obj)`` and ``loads(self, data)``, to serialize and deserialize
+the dictionary of session data, respectively.
+
Session object guidelines
-------------------------
@@ -396,14 +470,15 @@ An API is available to manipulate session data outside of a view::
>>> from django.contrib.sessions.backends.db import SessionStore
>>> import datetime
>>> s = SessionStore()
- >>> s['last_login'] = datetime.datetime(2005, 8, 20, 13, 35, 10)
+ >>> # stored as seconds since epoch since datetimes are not serializable in JSON.
+ >>> s['last_login'] = 1376587691
>>> s.save()
>>> s.session_key
'2b1189a188b44ad18c35e113ac6ceead'
>>> s = SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead')
>>> s['last_login']
- datetime.datetime(2005, 8, 20, 13, 35, 0)
+ 1376587691
In order to prevent session fixation attacks, sessions keys that don't exist
are regenerated::
@@ -551,8 +626,11 @@ behavior:
Technical details
=================
-* The session dictionary should accept any pickleable Python object. See
- the :mod:`pickle` module for more information.
+* The session dictionary accepts any :mod:`json` serializable value when using
+ :class:`~django.contrib.sessions.serializers.JSONSerializer` or any
+ pickleable Python object when using
+ :class:`~django.contrib.sessions.serializers.PickleSerializer`. See the
+ :mod:`pickle` module for more information.
* Session data is stored in a database table named ``django_session`` .
diff --git a/tests/defer_regress/tests.py b/tests/defer_regress/tests.py
index ad2546794cab..e8690f1c2223 100644
--- a/tests/defer_regress/tests.py
+++ b/tests/defer_regress/tests.py
@@ -7,6 +7,7 @@
from django.db.models import Count
from django.db.models.loading import cache
from django.test import TestCase
+from django.test.utils import override_settings
from .models import (
ResolveThis, Item, RelatedItem, Child, Leaf, Proxy, SimpleItem, Feature,
@@ -83,24 +84,6 @@ def test_basic(self):
self.assertEqual(results[0].child.name, "c1")
self.assertEqual(results[0].second_child.name, "c2")
- # Test for #12163 - Pickling error saving session with unsaved model
- # instances.
- SESSION_KEY = '2b1189a188b44ad18c35e1baac6ceead'
-
- item = Item()
- item._deferred = False
- s = SessionStore(SESSION_KEY)
- s.clear()
- s["item"] = item
- s.save()
-
- s = SessionStore(SESSION_KEY)
- s.modified = True
- s.save()
-
- i2 = s["item"]
- self.assertFalse(i2._deferred)
-
# Regression for #16409 - make sure defer() and only() work with annotate()
self.assertIsInstance(
list(SimpleItem.objects.annotate(Count('feature')).defer('name')),
@@ -147,6 +130,27 @@ def test_ticket_11936(self):
cache.get_app("defer_regress"), include_deferred=True))
)
+ @override_settings(SESSION_SERIALIZER='django.contrib.sessions.serializers.PickleSerializer')
+ def test_ticket_12163(self):
+ # Test for #12163 - Pickling error saving session with unsaved model
+ # instances.
+ SESSION_KEY = '2b1189a188b44ad18c35e1baac6ceead'
+
+ item = Item()
+ item._deferred = False
+ s = SessionStore(SESSION_KEY)
+ s.clear()
+ s["item"] = item
+ s.save()
+
+ s = SessionStore(SESSION_KEY)
+ s.modified = True
+ s.save()
+
+ i2 = s["item"]
+ self.assertFalse(i2._deferred)
+
+ def test_ticket_16409(self):
# Regression for #16409 - make sure defer() and only() work with annotate()
self.assertIsInstance(
list(SimpleItem.objects.annotate(Count('feature')).defer('name')),
From 2a166623a63b27a2b6c5c9d0f46bd726ee4e82fb Mon Sep 17 00:00:00 2001
From: Ramiro Morales
Date: Thu, 22 Aug 2013 12:14:56 -0300
Subject: [PATCH 172/944] [1.6.x] Typos introduced in 57c82f909b.
---
docs/ref/settings.txt | 8 ++++----
docs/topics/i18n/translation.txt | 6 +++---
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index a137b229dcc7..e47e8708b0ea 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -1258,18 +1258,18 @@ LANGUAGE_CODE
Default: ``'en-us'``
A string representing the language code for this installation. This should be in
-standard :term:`language ID format `. For example, U.S. English
+standard :term:`language ID format `. For example, U.S. English
is ``"en-us"``. See also the `list of language identifiers`_ and
:doc:`/topics/i18n/index`.
-:setting:`USE_I18N` must be active to this setting to have any effect.
+:setting:`USE_I18N` must be active for this setting to have any effect.
-it serves two purposes:
+It serves two purposes:
* If the locale middleware isn't in use, it decides which translation is served
to all users.
* If the locale middleware is active, it provides the fallback translation when
- no translation exist for a given literal to the user preferred language.
+ no translation exist for a given literal to the user's preferred language.
See :ref:`how-django-discovers-language-preference` for more details.
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index 4052b50b07f8..a4303ac25fda 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -1552,15 +1552,15 @@ should be used -- installation-wide, for a particular user, or both.
To set an installation-wide language preference, set :setting:`LANGUAGE_CODE`.
Django uses this language as the default translation -- the final attempt if no
-better matching translation is found by one of the methods employed by the
+better matching translation is found through one of the methods employed by the
locale middleware (see below).
-If all you want to do is run Django with your native language all you need to do
+If all you want is to run Django with your native language all you need to do
is set :setting:`LANGUAGE_CODE` and make sure the corresponding :term:`message
files ` and their compiled versions (``.mo``) exist.
If you want to let each individual user specify which language he or she
-prefers, the you also need to use use the ``LocaleMiddleware``.
+prefers, then you also need to use use the ``LocaleMiddleware``.
``LocaleMiddleware`` enables language selection based on data from the request.
It customizes content for each user.
From 2c08d474a83b438cf02595c6854f2f79e708b852 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Sat, 24 Aug 2013 18:08:05 +0200
Subject: [PATCH 173/944] [1.6.x] Fixed #20961 -- Fixed HttpResponse default
empty content
Thanks epandurski at gmail.com for the report.
Backport of f4e980456 from master.
---
django/http/response.py | 2 +-
tests/httpwrappers/tests.py | 7 +++++++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/django/http/response.py b/django/http/response.py
index 8d8db2c8b4bd..f522c0684b74 100644
--- a/django/http/response.py
+++ b/django/http/response.py
@@ -323,7 +323,7 @@ class HttpResponse(HttpResponseBase):
streaming = False
- def __init__(self, content='', *args, **kwargs):
+ def __init__(self, content=b'', *args, **kwargs):
super(HttpResponse, self).__init__(*args, **kwargs)
# Content is a bytestring. See the `content` property methods.
self.content = content
diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py
index c4c7996aa5bf..8e0c415623c3 100644
--- a/tests/httpwrappers/tests.py
+++ b/tests/httpwrappers/tests.py
@@ -398,6 +398,13 @@ def test_file_interface(self):
self.assertEqual(r.tell(), 6)
self.assertEqual(r.content, b'abcdef')
+ # with Content-Encoding header
+ r = HttpResponse()
+ r['Content-Encoding'] = 'winning'
+ r.write(b'abc')
+ r.write(b'def')
+ self.assertEqual(r.content, b'abcdef')
+
def test_unsafe_redirect(self):
bad_urls = [
'data:text/html,',
From 28026c3e26388efe27c92b6e94d7363022f1efb3 Mon Sep 17 00:00:00 2001
From: Matt Robenolt
Date: Sun, 25 Aug 2013 19:07:00 -0700
Subject: [PATCH 174/944] [1.6.x] Updated instructions for running contrib
tests.
Backport of 08e7a64369 from master
---
docs/internals/contributing/writing-code/unit-tests.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt
index 7ee65cd6fa8f..62fa754e301c 100644
--- a/docs/internals/contributing/writing-code/unit-tests.txt
+++ b/docs/internals/contributing/writing-code/unit-tests.txt
@@ -210,4 +210,4 @@ If you have URLs that need to be mapped, put them in ``tests/urls.py``.
To run tests for just one contrib app (e.g. ``auth``), use the same
method as above::
- ./runtests.py --settings=settings auth
+ ./runtests.py --settings=settings django.contrib.auth
From c4e2e4f630fd86cb9993daf874c1a8964e4b92f8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20B=C3=B6cker?=
Date: Mon, 26 Aug 2013 11:18:21 +0200
Subject: [PATCH 175/944] [1.6.x] Fixed typo in
docs/topics/conditional-view-processing.txt
Backport of 5fd2c979cb from master
---
docs/topics/conditional-view-processing.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/topics/conditional-view-processing.txt b/docs/topics/conditional-view-processing.txt
index caa737618976..83b454a4aae1 100644
--- a/docs/topics/conditional-view-processing.txt
+++ b/docs/topics/conditional-view-processing.txt
@@ -147,7 +147,7 @@ Using the decorators with other HTTP methods
The ``condition`` decorator is useful for more than only ``GET`` and
``HEAD`` requests (``HEAD`` requests are the same as ``GET`` in this
-situation). It can be used also to be used to provide checking for ``POST``,
+situation). It can also be used to provide checking for ``POST``,
``PUT`` and ``DELETE`` requests. In these situations, the idea isn't to return
a "not modified" response, but to tell the client that the resource they are
trying to change has been altered in the meantime.
From 544a190ebf53d4a6661474d3020eab2334c7f181 Mon Sep 17 00:00:00 2001
From: Krzysztof Jurewicz
Date: Tue, 27 Aug 2013 02:00:11 +0200
Subject: [PATCH 176/944] [1.6.x] Fixed #20981 -- Noted the default value of
disable_existing_loggers.
Backport of 095643e691 from master
---
docs/topics/logging.txt | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt
index a88201ad4728..05a1a73f3c7f 100644
--- a/docs/topics/logging.txt
+++ b/docs/topics/logging.txt
@@ -223,8 +223,9 @@ Django logging configuration `. From Django
1.5 forward, the project's logging configuration is merged with Django's
defaults, hence you can decide if you want to add to, or replace the existing
configuration. To completely override the default configuration, set the
-``disable_existing_loggers`` key to True in the :setting:`LOGGING`
-dictConfig. Alternatively you can redefine some or all of the loggers.
+``disable_existing_loggers`` key to ``True`` (which is the default) in the
+:setting:`LOGGING` dictConfig. Alternatively you can redefine some or all of
+the loggers by setting ``disable_existing_loggers`` to ``False``.
Logging is configured as soon as settings have been loaded
(either manually using :func:`~django.conf.settings.configure` or when at least
From 68eca2b36ff8e4091fbfd210ca0782c7b8313a05 Mon Sep 17 00:00:00 2001
From: Phaneendra Chiruvella
Date: Wed, 28 Aug 2013 16:05:46 +0530
Subject: [PATCH 177/944] [1.6.x] Minor spelling correction in ModelForms docs
Backport of 2fbf949760 from master
---
docs/topics/forms/modelforms.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
index 0f3c5bb81573..2bb58721d8e5 100644
--- a/docs/topics/forms/modelforms.txt
+++ b/docs/topics/forms/modelforms.txt
@@ -17,7 +17,7 @@ model, and you want to create a form that lets people submit comments. In this
case, it would be redundant to define the field types in your form, because
you've already defined the fields in your model.
-For this reason, Django provides a helper class that let you create a ``Form``
+For this reason, Django provides a helper class that lets you create a ``Form``
class from a Django model.
For example::
From 58157be5ad31b42a1dc73e357cfdece02fd0b6ee Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Thu, 29 Aug 2013 08:35:17 +0200
Subject: [PATCH 178/944] [1.6.x] Fixed #20984 -- Stopped decoding bytes in
sqlite3 adapter on Python 3
Thanks lvella at gmail.com for the report.
Backport of 169637649 from master.
---
django/db/backends/sqlite3/base.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index 92dbf354ae7e..3a0a9f57b7f8 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -78,7 +78,7 @@ def decoder(conv_func):
Database.register_adapter(datetime.datetime, adapt_datetime_with_timezone_support)
Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal)
-if Database.version_info >= (2, 4, 1):
+if not six.PY3 and Database.version_info >= (2, 4, 1):
# Starting in 2.4.1, the str type is not accepted anymore, therefore,
# we convert all str objects to Unicode
# As registering a adapter for a primitive type causes a small
From ef1259342b46cd4b63678a4acddf2a2b631d342c Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 29 Aug 2013 09:39:31 -0400
Subject: [PATCH 179/944] [1.6.x] Fixed #16433 -- Fixed a help_text/read only
field interaction that caused an admin crash.
Thanks chris at cogdon.org for the report and admackin for the patch.
Backport of af953c45cc from master
---
django/contrib/admin/util.py | 9 +++++++--
tests/admin_views/admin.py | 6 +++++-
tests/admin_views/models.py | 2 +-
tests/admin_views/tests.py | 13 ++++++++++++-
4 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
index b31756d76249..962d8f7f1dba 100644
--- a/django/contrib/admin/util.py
+++ b/django/contrib/admin/util.py
@@ -320,10 +320,15 @@ def label_for_field(name, model, model_admin=None, return_attr=False):
def help_text_for_field(name, model):
+ help_text = ""
try:
- help_text = model._meta.get_field_by_name(name)[0].help_text
+ field_data = model._meta.get_field_by_name(name)
except models.FieldDoesNotExist:
- help_text = ""
+ pass
+ else:
+ field = field_data[0]
+ if not isinstance(field, RelatedObject):
+ help_text = field.help_text
return smart_text(help_text)
diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py
index 039abb819be8..b31c5b59e4ab 100644
--- a/tests/admin_views/admin.py
+++ b/tests/admin_views/admin.py
@@ -450,6 +450,10 @@ def get_changelist(self, request, **kwargs):
return CustomChangeList
+class ToppingAdmin(admin.ModelAdmin):
+ readonly_fields = ('pizzas',)
+
+
class PizzaAdmin(admin.ModelAdmin):
readonly_fields = ('toppings',)
@@ -755,7 +759,7 @@ class ChoiceList(admin.ModelAdmin):
site.register(Promo)
site.register(ChapterXtra1, ChapterXtra1Admin)
site.register(Pizza, PizzaAdmin)
-site.register(Topping)
+site.register(Topping, ToppingAdmin)
site.register(Album, AlbumAdmin)
site.register(Question)
site.register(Answer)
diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py
index 9a7f0ff30ec5..f92b3a1a240a 100644
--- a/tests/admin_views/models.py
+++ b/tests/admin_views/models.py
@@ -495,7 +495,7 @@ class Topping(models.Model):
class Pizza(models.Model):
name = models.CharField(max_length=20)
- toppings = models.ManyToManyField('Topping')
+ toppings = models.ManyToManyField('Topping', related_name='pizzas')
class Album(models.Model):
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 3c276199ff36..28dea38cfd2f 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -49,7 +49,7 @@
OtherStory, ComplexSortedPerson, PluggableSearchPerson, Parent, Child, AdminOrderedField,
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject,
- Simple, UndeletableObject, Choice, ShortMessage, Telegram)
+ Simple, UndeletableObject, Choice, ShortMessage, Telegram, Pizza, Topping)
from .admin import site, site2
@@ -3559,6 +3559,17 @@ def test_change_form_renders_correct_null_choice_value(self):
self.assertContains(response, '
No opinion
', html=True)
self.assertNotContains(response, '
(None)
')
+ def test_readonly_backwards_ref(self):
+ """
+ Regression test for #16433 - backwards references for related objects
+ broke if the related field is read-only due to the help_text attribute
+ """
+ topping = Topping.objects.create(name='Salami')
+ pizza = Pizza.objects.create(name='Americano')
+ pizza.toppings.add(topping)
+ response = self.client.get('/test_admin/admin/admin_views/topping/add/')
+ self.assertEqual(response.status_code, 200)
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class RawIdFieldsTest(TestCase):
From 10d15f79e5e2ca7b733e2bf1860e1778c3a712dc Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 29 Aug 2013 11:09:58 -0400
Subject: [PATCH 180/944] [1.6.x] Fixed #14786 -- Fixed get_db_prep_lookup
calling get_prep_value twice if prepared is False.
Thanks homm for the report and Aramgutang and lrekucki for work on
the patch.
Backport of f19a3669b8 from master
---
django/db/models/fields/__init__.py | 1 +
tests/model_fields/tests.py | 18 ++++++++++++++++++
2 files changed, 19 insertions(+)
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 0b5e7d47829e..e2f8c6c48ed3 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -385,6 +385,7 @@ def get_db_prep_lookup(self, lookup_type, value, connection,
"""
if not prepared:
value = self.get_prep_lookup(lookup_type, value)
+ prepared = True
if hasattr(value, 'get_compiler'):
value = value.get_compiler(connection=connection)
if hasattr(value, 'as_sql') or hasattr(value, '_as_sql'):
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py
index 1fb503762565..432984f6bbd3 100644
--- a/tests/model_fields/tests.py
+++ b/tests/model_fields/tests.py
@@ -494,3 +494,21 @@ def test_genericipaddressfield_formfield_protocol(self):
model_field = models.GenericIPAddressField(protocol='IPv6')
form_field = model_field.formfield()
self.assertRaises(ValidationError, form_field.clean, '127.0.0.1')
+
+
+class CustomFieldTests(unittest.TestCase):
+
+ def test_14786(self):
+ """
+ Regression test for #14786 -- Test that field values are not prepared
+ twice in get_db_prep_lookup().
+ """
+ prepare_count = [0]
+ class NoopField(models.TextField):
+ def get_prep_value(self, value):
+ prepare_count[0] += 1
+ return super(NoopField, self).get_prep_value(value)
+
+ field = NoopField()
+ field.get_db_prep_lookup('exact', 'TEST', connection=connection, prepared=False)
+ self.assertEqual(prepare_count[0], 1)
From 60df34d4771b6a97f3695f2095698082073e3379 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jorge=20C=2E=20Leit=C3=A3o?=
Date: Thu, 29 Aug 2013 12:48:22 -0400
Subject: [PATCH 181/944] [1.6.x] Added links to file docs.
Backport of d72f83c410 from master
---
docs/ref/files/file.txt | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/docs/ref/files/file.txt b/docs/ref/files/file.txt
index 7562f9b6bfad..3f121e0ead3b 100644
--- a/docs/ref/files/file.txt
+++ b/docs/ref/files/file.txt
@@ -11,15 +11,15 @@ The ``File`` Class
.. class:: File(file_object)
- The :class:`File` is a thin wrapper around Python's built-in file object
- with some Django-specific additions. Internally, Django uses this class
- any time it needs to represent a file.
+ The :class:`File` class is a thin wrapper around Python's :py:ref:`built-in
+ file object` with some Django-specific additions.
+ Internally, Django uses this class when it needs to represent a file.
:class:`File` objects have the following attributes and methods:
.. attribute:: name
- The name of file including the relative path from
+ The name of the file including the relative path from
:setting:`MEDIA_ROOT`.
.. attribute:: size
@@ -28,8 +28,8 @@ The ``File`` Class
.. attribute:: file
- The underlying Python ``file`` object passed to
- :class:`~django.core.files.File`.
+ The underlying :py:ref:`built-in file object` that
+ this class wraps.
.. attribute:: mode
@@ -37,9 +37,9 @@ The ``File`` Class
.. method:: open([mode=None])
- Open or reopen the file (which by definition also does
- ``File.seek(0)``). The ``mode`` argument allows the same values
- as Python's standard ``open()``.
+ Open or reopen the file (which also does ``File.seek(0)``).
+ The ``mode`` argument allows the same values
+ as Python's built-in :func:`python:open()`.
When reopening a file, ``mode`` will override whatever mode the file
was originally opened with; ``None`` means to reopen with the original
@@ -71,14 +71,14 @@ The ``File`` Class
Writes the specified content string to the file. Depending on the
storage system behind the scenes, this content might not be fully
- committed until ``close()`` is called on the file.
+ committed until :func:`close()` is called on the file.
.. method:: close()
Close the file.
In addition to the listed methods, :class:`~django.core.files.File` exposes
- the following attributes and methods of the underlying ``file`` object:
+ the following attributes and methods of its ``file`` object:
``encoding``, ``fileno``, ``flush``, ``isatty``, ``newlines``,
``read``, ``readinto``, ``readlines``, ``seek``, ``softspace``, ``tell``,
``truncate``, ``writelines``, ``xreadlines``.
@@ -129,7 +129,7 @@ The ``ImageFile`` Class
Additional methods on files attached to objects
-----------------------------------------------
-Any :class:`File` that's associated with an object (as with ``Car.photo``,
+Any :class:`File` that is associated with an object (as with ``Car.photo``,
below) will also have a couple of extra methods:
.. method:: File.save(name, content, [save=True])
@@ -142,7 +142,7 @@ below) will also have a couple of extra methods:
>>> car.photo.save('myphoto.jpg', content, save=False)
>>> car.save()
- are the same as this one line::
+ are equivalent to::
>>> car.photo.save('myphoto.jpg', content, save=True)
From cd10e998b6a8f9daaf2e25cad55f33f3df51ea88 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?=
Date: Fri, 30 Aug 2013 09:44:56 +0300
Subject: [PATCH 182/944] [1.6.x] Removed stale add_q() comment
Backport of 13be3bfef1 from master
---
django/db/models/sql/query.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 75e8e7540d52..510b21b64b3b 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -1207,8 +1207,6 @@ def _add_q(self, q_object, used_aliases, branch_negated=False,
current_negated=False):
"""
Adds a Q-object to the current filter.
-
- Can also be used to add anything that has an 'add_to_query()' method.
"""
connector = q_object.connector
current_negated = current_negated ^ q_object.negated
From 76e38a21777243fec58c640d617bb71a251c5ba1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?=
Date: Fri, 30 Aug 2013 09:41:07 +0300
Subject: [PATCH 183/944] [1.6.x] Fixed #20988 -- Added model meta option
select_on_save
The option can be used to force pre 1.6 style SELECT on save behaviour.
This is needed in case the database returns zero updated rows even if
there is a matching row in the DB. One such case is PostgreSQL update
trigger that returns NULL.
Reviewed by Tim Graham.
Refs #16649
Backport of e973ee6a9879969b8ae05bb7ff681172cc5386a5 from master
Conflicts:
django/db/models/options.py
tests/basic/tests.py
---
django/db/models/base.py | 18 ++++++++---
django/db/models/options.py | 3 +-
docs/ref/models/instances.txt | 17 +++++++---
docs/ref/models/options.txt | 22 +++++++++++++
docs/releases/1.6.txt | 20 +++++++++---
tests/basic/models.py | 5 +++
tests/basic/tests.py | 61 ++++++++++++++++++++++++++++++++++-
7 files changed, 130 insertions(+), 16 deletions(-)
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 1238bfb4cee3..29846bcfa2ff 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -635,7 +635,9 @@ def _save_table(self, raw=False, cls=None, force_insert=False,
base_qs = cls._base_manager.using(using)
values = [(f, None, (getattr(self, f.attname) if raw else f.pre_save(self, False)))
for f in non_pks]
- updated = self._do_update(base_qs, using, pk_val, values, update_fields)
+ forced_update = update_fields or force_update
+ updated = self._do_update(base_qs, using, pk_val, values, update_fields,
+ forced_update)
if force_update and not updated:
raise DatabaseError("Forced update did not affect any rows.")
if update_fields and not updated:
@@ -659,21 +661,27 @@ def _save_table(self, raw=False, cls=None, force_insert=False,
setattr(self, meta.pk.attname, result)
return updated
- def _do_update(self, base_qs, using, pk_val, values, update_fields):
+ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_update):
"""
This method will try to update the model. If the model was updated (in
the sense that an update query was done and a matching row was found
from the DB) the method will return True.
"""
+ filtered = base_qs.filter(pk=pk_val)
if not values:
# We can end up here when saving a model in inheritance chain where
# update_fields doesn't target any field in current model. In that
# case we just say the update succeeded. Another case ending up here
# is a model with just PK - in that case check that the PK still
# exists.
- return update_fields is not None or base_qs.filter(pk=pk_val).exists()
- else:
- return base_qs.filter(pk=pk_val)._update(values) > 0
+ return update_fields is not None or filtered.exists()
+ if self._meta.select_on_save and not forced_update:
+ if filtered.exists():
+ filtered._update(values)
+ return True
+ else:
+ return False
+ return filtered._update(values) > 0
def _do_insert(self, manager, using, fields, update_pk, raw):
"""
diff --git a/django/db/models/options.py b/django/db/models/options.py
index b37965ca4f9c..6ccc67d1425e 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -22,7 +22,7 @@
'unique_together', 'permissions', 'get_latest_by',
'order_with_respect_to', 'app_label', 'db_tablespace',
'abstract', 'managed', 'proxy', 'swappable', 'auto_created',
- 'index_together')
+ 'index_together', 'select_on_save')
@python_2_unicode_compatible
@@ -36,6 +36,7 @@ def __init__(self, meta, app_label=None):
self.ordering = []
self.unique_together = []
self.index_together = []
+ self.select_on_save = False
self.permissions = []
self.object_name, self.app_label = None, app_label
self.get_latest_by = None
diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt
index 4bb6af2a5142..ce943f141d16 100644
--- a/docs/ref/models/instances.txt
+++ b/docs/ref/models/instances.txt
@@ -305,16 +305,23 @@ follows this algorithm:
* If the object's primary key attribute is *not* set or if the ``UPDATE``
didn't update anything, Django executes an ``INSERT``.
-.. versionchanged:: 1.6
-
- Previously Django used ``SELECT`` - if not found ``INSERT`` else ``UPDATE``
- algorithm. The old algorithm resulted in one more query in ``UPDATE`` case.
-
The one gotcha here is that you should be careful not to specify a primary-key
value explicitly when saving new objects, if you cannot guarantee the
primary-key value is unused. For more on this nuance, see `Explicitly specifying
auto-primary-key values`_ above and `Forcing an INSERT or UPDATE`_ below.
+.. versionchanged:: 1.6
+
+ Previously Django did a ``SELECT`` when the primary key attribute was set.
+ If the ``SELECT`` found a row, then Django did an ``UPDATE``, otherwise it
+ did an ``INSERT``. The old algorithm results in one more query in the
+ ``UPDATE`` case. There are some rare cases where the database doesn't
+ report that a row was updated even if the database contains a row for the
+ object's primary key value. An example is the PostgreSQL ``ON UPDATE``
+ trigger which returns ``NULL``. In such cases it is possible to revert to the
+ old algorithm by setting the :attr:`~django.db.models.Options.select_on_save`
+ option to ``True``.
+
.. _ref-models-force-insert:
Forcing an INSERT or UPDATE
diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
index 6944796ef1d4..9c58416e1264 100644
--- a/docs/ref/models/options.txt
+++ b/docs/ref/models/options.txt
@@ -238,6 +238,28 @@ Django quotes column and table names behind the scenes.
If ``proxy = True``, a model which subclasses another model will be treated as
a :ref:`proxy model `.
+``select_on_save``
+------------------
+
+.. attribute:: Options.select_on_save
+
+ .. versionadded:: 1.6
+
+ Determines if Django will use the pre-1.6
+ :meth:`django.db.models.Model.save()` algorithm. The old algorithm
+ uses ``SELECT`` to determine if there is an existing row to be updated.
+ The new algorith tries an ``UPDATE`` directly. In some rare cases the
+ ``UPDATE`` of an existing row isn't visible to Django. An example is the
+ PostgreSQL ``ON UPDATE`` trigger which returns ``NULL``. In such cases the
+ new algorithm will end up doing an ``INSERT`` even when a row exists in
+ the database.
+
+ Usually there is no need to set this attribute. The default is
+ ``False``.
+
+ See :meth:`django.db.models.Model.save()` for more about the old and
+ new saving algorithm.
+
``unique_together``
-------------------
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index 105e302b7d57..3903aa403f1e 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -138,6 +138,22 @@ A :djadmin:`check` management command was added, enabling you to verify if your
current configuration (currently oriented at settings) is compatible with the
current version of Django.
+:meth:`Model.save() ` algorithm changed
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :meth:`Model.save() ` method now
+tries to directly ``UPDATE`` the database if the instance has a primary
+key value. Previously ``SELECT`` was performed to determine if ``UPDATE``
+or ``INSERT`` were needed. The new algorithm needs only one query for
+updating an existing row while the old algorithm needed two. See
+:meth:`Model.save() ` for more details.
+
+In some rare cases the database doesn't report that a matching row was
+found when doing an ``UPDATE``. An example is the PostgreSQL ``ON UPDATE``
+trigger which returns ``NULL``. In such cases it is possible to set
+:attr:`django.db.models.Options.select_on_save` flag to force saving to
+use the old algorithm.
+
Minor features
~~~~~~~~~~~~~~
@@ -222,10 +238,6 @@ Minor features
* Generic :class:`~django.contrib.gis.db.models.GeometryField` is now editable
with the OpenLayers widget in the admin.
-* The :meth:`Model.save() ` will do
- ``UPDATE`` - if not updated - ``INSERT`` instead of ``SELECT`` - if not
- found ``INSERT`` else ``UPDATE`` in case the model's primary key is set.
-
* The documentation contains a :doc:`deployment checklist
`.
diff --git a/tests/basic/models.py b/tests/basic/models.py
index 1bffcb9cda5d..ee44c1658a54 100644
--- a/tests/basic/models.py
+++ b/tests/basic/models.py
@@ -19,6 +19,11 @@ class Meta:
def __str__(self):
return self.headline
+class ArticleSelectOnSave(Article):
+ class Meta:
+ proxy = True
+ select_on_save = True
+
@python_2_unicode_compatible
class SelfRef(models.Model):
selfref = models.ForeignKey('self', null=True, blank=True,
diff --git a/tests/basic/tests.py b/tests/basic/tests.py
index 4b79c47be562..d8c09642888f 100644
--- a/tests/basic/tests.py
+++ b/tests/basic/tests.py
@@ -5,13 +5,14 @@
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.db import connections, DEFAULT_DB_ALIAS
+from django.db import DatabaseError
from django.db.models.fields import Field, FieldDoesNotExist
from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet
from django.test import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
from django.utils import six
from django.utils.translation import ugettext_lazy
-from .models import Article, SelfRef
+from .models import Article, SelfRef, ArticleSelectOnSave
class ModelTest(TestCase):
@@ -712,3 +713,61 @@ def deleter():
t.join()
a.save()
self.assertEqual(Article.objects.get(pk=a.pk).headline, 'foo')
+
+
+class SelectOnSaveTests(TestCase):
+ def test_select_on_save(self):
+ a1 = Article.objects.create(pub_date=datetime.now())
+ with self.assertNumQueries(1):
+ a1.save()
+ asos = ArticleSelectOnSave.objects.create(pub_date=datetime.now())
+ with self.assertNumQueries(2):
+ asos.save()
+ with self.assertNumQueries(1):
+ asos.save(force_update=True)
+ Article.objects.all().delete()
+ with self.assertRaises(DatabaseError):
+ with self.assertNumQueries(1):
+ asos.save(force_update=True)
+
+ def test_select_on_save_lying_update(self):
+ """
+ Test that select_on_save works correctly if the database
+ doesn't return correct information about matched rows from
+ UPDATE.
+ """
+ # Change the manager to not return "row matched" for update().
+ # We are going to change the Article's _base_manager class
+ # dynamically. This is a bit of a hack, but it seems hard to
+ # test this properly otherwise. Article's manager, because
+ # proxy models use their parent model's _base_manager.
+
+ orig_class = Article._base_manager.__class__
+
+ class FakeQuerySet(QuerySet):
+ # Make sure the _update method below is in fact called.
+ called = False
+
+ def _update(self, *args, **kwargs):
+ FakeQuerySet.called = True
+ super(FakeQuerySet, self)._update(*args, **kwargs)
+ return 0
+
+ class FakeManager(orig_class):
+ def get_queryset(self):
+ return FakeQuerySet(self.model)
+ try:
+ Article._base_manager.__class__ = FakeManager
+ asos = ArticleSelectOnSave.objects.create(pub_date=datetime.now())
+ with self.assertNumQueries(2):
+ asos.save()
+ self.assertTrue(FakeQuerySet.called)
+ # This is not wanted behaviour, but this is how Django has always
+ # behaved for databases that do not return correct information
+ # about matched rows for UPDATE.
+ with self.assertRaises(DatabaseError):
+ asos.save(force_update=True)
+ with self.assertRaises(DatabaseError):
+ asos.save(update_fields=['pub_date'])
+ finally:
+ Article._base_manager.__class__ = orig_class
From 1d874ce0f94432c1a4b42cacaeb02c5948000d0f Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Fri, 30 Aug 2013 12:02:23 +0200
Subject: [PATCH 184/944] [1.6.x] Set 'bidi' Urdu property to True
Refs #20454.
Backport of e4a67fd90 from master.
---
django/conf/locale/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/conf/locale/__init__.py b/django/conf/locale/__init__.py
index 59927b2397ab..7ff004d6ca27 100644
--- a/django/conf/locale/__init__.py
+++ b/django/conf/locale/__init__.py
@@ -468,7 +468,7 @@
'name_local': 'Українська',
},
'ur': {
- 'bidi': False,
+ 'bidi': True,
'code': 'ur',
'name': 'Urdu',
'name_local': 'اردو',
From 21a3efcf48559b3336ca1743d94c741a82feffd6 Mon Sep 17 00:00:00 2001
From: Carl Meyer
Date: Thu, 29 Aug 2013 21:44:37 -0600
Subject: [PATCH 185/944] [1.6.x] Fixed #20999 - Allow overriding formfield
class with choices, without subclass restrictions.
Refs #18162. Thanks claudep and mjtamlyn for review.
Backport of 7211741fc5d50425 from master.
---
django/db/models/fields/__init__.py | 6 ++++--
docs/howto/custom-model-fields.txt | 20 +++++++++++++-------
tests/model_fields/tests.py | 12 +++++-------
3 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index e2f8c6c48ed3..0621ef912ed4 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -515,7 +515,7 @@ def _get_flatchoices(self):
def save_form_data(self, instance, data):
setattr(instance, self.name, data)
- def formfield(self, form_class=None, **kwargs):
+ def formfield(self, form_class=None, choices_form_class=None, **kwargs):
"""
Returns a django.forms.Field instance for this database Field.
"""
@@ -536,7 +536,9 @@ def formfield(self, form_class=None, **kwargs):
defaults['coerce'] = self.to_python
if self.null:
defaults['empty_value'] = None
- if form_class is None or not issubclass(form_class, forms.TypedChoiceField):
+ if choices_form_class is not None:
+ form_class = choices_form_class
+ else:
form_class = forms.TypedChoiceField
# Many of the subclass-specific formfield arguments (min_value,
# max_value) don't apply for choice fields, so be sure to only pass
diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt
index af652de78c3e..7abecefb8baf 100644
--- a/docs/howto/custom-model-fields.txt
+++ b/docs/howto/custom-model-fields.txt
@@ -617,17 +617,23 @@ prepared with :meth:`.get_prep_lookup`.
Specifying the form field for a model field
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. method:: Field.formfield(self, form_class=forms.CharField, **kwargs)
+.. method:: Field.formfield(self, form_class=None, choices_form_class=None, **kwargs)
-Returns the default form field to use when this field is displayed in a model.
-This method is called by the :class:`~django.forms.ModelForm` helper.
+Returns the default form field to use when this model field is displayed in a
+form. This method is called by the :class:`~django.forms.ModelForm` helper.
+
+The form field class can be specified via the ``form_class`` and
+``choices_form_class`` arguments; the latter is used if the field has choices
+specified, the former otherwise. If these arguments are not provided,
+:class:`~django.forms.CharField` or :class:`~django.forms.TypedChoiceField`
+will be used.
All of the ``kwargs`` dictionary is passed directly to the form field's
``__init__()`` method. Normally, all you need to do is set up a good default
-for the ``form_class`` argument and then delegate further handling to the
-parent class. This might require you to write a custom form field (and even a
-form widget). See the :doc:`forms documentation ` for
-information about this.
+for the ``form_class`` (and maybe ``choices_form_class``) argument and then
+delegate further handling to the parent class. This might require you to write
+a custom form field (and even a form widget). See the :doc:`forms documentation
+` for information about this.
Continuing our ongoing example, we can write the :meth:`.formfield` method as::
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py
index 432984f6bbd3..60ce13dfb6e6 100644
--- a/tests/model_fields/tests.py
+++ b/tests/model_fields/tests.py
@@ -68,14 +68,12 @@ def test_field_verbose_name(self):
self.assertEqual(m._meta.get_field('id').verbose_name, 'verbose pk')
- def test_formclass_with_choices(self):
- # regression for 18162
- class CustomChoiceField(forms.TypedChoiceField):
- pass
- choices = [('a@b.cc', 'a@b.cc'), ('b@b.cc', 'b@b.cc')]
+ def test_choices_form_class(self):
+ """Can supply a custom choices form class. Regression for #20999."""
+ choices = [('a', 'a')]
field = models.CharField(choices=choices)
- klass = CustomChoiceField
- self.assertIsInstance(field.formfield(form_class=klass), klass)
+ klass = forms.TypedMultipleChoiceField
+ self.assertIsInstance(field.formfield(choices_form_class=klass), klass)
class DecimalFieldTests(test.TestCase):
From 0089a9a8549828d38f6c49aaefb8364c034fa434 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 30 Aug 2013 21:05:36 -0400
Subject: [PATCH 186/944] [1.6.x] Fixed typo in docs/ref/models/options.txt
Backport of a89c856a7a from master
---
docs/ref/models/options.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
index 9c58416e1264..6413050cb7e8 100644
--- a/docs/ref/models/options.txt
+++ b/docs/ref/models/options.txt
@@ -248,7 +248,7 @@ Django quotes column and table names behind the scenes.
Determines if Django will use the pre-1.6
:meth:`django.db.models.Model.save()` algorithm. The old algorithm
uses ``SELECT`` to determine if there is an existing row to be updated.
- The new algorith tries an ``UPDATE`` directly. In some rare cases the
+ The new algorithm tries an ``UPDATE`` directly. In some rare cases the
``UPDATE`` of an existing row isn't visible to Django. An example is the
PostgreSQL ``ON UPDATE`` trigger which returns ``NULL``. In such cases the
new algorithm will end up doing an ``INSERT`` even when a row exists in
From 68ae9f39b72253aa17341d749842b9c8923fc6b9 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Sat, 31 Aug 2013 10:32:58 +0200
Subject: [PATCH 187/944] [1.6.x] Fixed copy/paste error in measurement docs
Backport of e87997dd33 frmo master.
---
docs/ref/contrib/gis/measure.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/ref/contrib/gis/measure.txt b/docs/ref/contrib/gis/measure.txt
index 699677a50c65..2a05b42600c5 100644
--- a/docs/ref/contrib/gis/measure.txt
+++ b/docs/ref/contrib/gis/measure.txt
@@ -147,9 +147,9 @@ Measurement API
.. class:: Area(**kwargs)
- To initialize a distance object, pass in a keyword corresponding to
+ To initialize an area object, pass in a keyword corresponding to
the desired :ref:`unit attribute name ` set with
- desired value. For example, the following creates a distance
+ desired value. For example, the following creates an area
object representing 5 square miles::
>>> a = Area(sq_mi=5)
From 64383e8349daee3f14e3ad863f8a75b1c95a4e4a Mon Sep 17 00:00:00 2001
From: Loic Bistuer
Date: Sat, 31 Aug 2013 18:55:45 +0700
Subject: [PATCH 188/944] [1.6.x] Made the doc about translating string
literals in templates more prominent.
Backport of 9885f07757 from master
---
docs/topics/i18n/translation.txt | 36 +++++++++++++++++---------------
1 file changed, 19 insertions(+), 17 deletions(-)
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index a4303ac25fda..041d627bad61 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -674,6 +674,25 @@ markers` using the ``context`` keyword:
{% blocktrans with name=user.username context "greeting" %}Hi {{ name }}{% endblocktrans %}
+String literals passed to tags and filters
+------------------------------------------
+
+You can translate string literals passed as arguments to tags and filters
+by using the familiar ``_()`` syntax::
+
+ {% some_tag _("Page not found") value|yesno:_("yes,no") %}
+
+In this case, both the tag and the filter will see the translated string,
+so they don't need to be aware of translations.
+
+.. note::
+ In this example, the translation infrastructure will be passed the string
+ ``"yes,no"``, not the individual strings ``"yes"`` and ``"no"``. The
+ translated string will need to contain the comma so that the filter
+ parsing code knows how to split up the arguments. For example, a German
+ translator might translate the string ``"yes,no"`` as ``"ja,nein"``
+ (keeping the comma intact).
+
.. _translator-comments-in-templates:
Comments for translators in templates
@@ -783,23 +802,6 @@ three tags::
These tags also require a ``{% load i18n %}``.
-Translation hooks are also available within any template block tag that accepts
-constant strings. In those cases, just use ``_()`` syntax to specify a
-translation string::
-
- {% some_special_tag _("Page not found") value|yesno:_("yes,no") %}
-
-In this case, both the tag and the filter will see the already-translated
-string, so they don't need to be aware of translations.
-
-.. note::
- In this example, the translation infrastructure will be passed the string
- ``"yes,no"``, not the individual strings ``"yes"`` and ``"no"``. The
- translated string will need to contain the comma so that the filter
- parsing code knows how to split up the arguments. For example, a German
- translator might translate the string ``"yes,no"`` as ``"ja,nein"``
- (keeping the comma intact).
-
You can also retrieve information about any of the available languages using
provided template tags and filters. To get information about a single language,
use the ``{% get_language_info %}`` tag::
From 4e3794dd1fbc689dcbcc455f3458539d630a1ff3 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Fri, 30 Aug 2013 10:48:36 +0200
Subject: [PATCH 189/944] [1.6.x] Fixed #20998 -- Allow custom
(de)serialization for GIS widgets
Thanks Mathieu Leplatre for the report and the initial patch.
Backport of 102f26c92 from master.
---
django/contrib/gis/forms/widgets.py | 35 ++++++++++---------
.../contrib/gis/templates/gis/openlayers.html | 6 ++--
django/contrib/gis/tests/test_geoforms.py | 29 ++++++++++++++-
docs/ref/contrib/gis/forms-api.txt | 8 ++---
4 files changed, 54 insertions(+), 24 deletions(-)
diff --git a/django/contrib/gis/forms/widgets.py b/django/contrib/gis/forms/widgets.py
index d50c7c005a55..0102ab6745fc 100644
--- a/django/contrib/gis/forms/widgets.py
+++ b/django/contrib/gis/forms/widgets.py
@@ -22,51 +22,54 @@ class BaseGeometryWidget(Widget):
map_srid = 4326
map_width = 600
map_height = 400
- display_wkt = False
+ display_raw = False
supports_3d = False
template_name = '' # set on subclasses
def __init__(self, attrs=None):
self.attrs = {}
- for key in ('geom_type', 'map_srid', 'map_width', 'map_height', 'display_wkt'):
+ for key in ('geom_type', 'map_srid', 'map_width', 'map_height', 'display_raw'):
self.attrs[key] = getattr(self, key)
if attrs:
self.attrs.update(attrs)
+ def serialize(self, value):
+ return value.wkt if value else ''
+
+ def deserialize(self, value):
+ try:
+ return GEOSGeometry(value)
+ except (GEOSException, ValueError) as err:
+ logger.error(
+ "Error creating geometry from value '%s' (%s)" % (
+ value, err)
+ )
+ return None
+
def render(self, name, value, attrs=None):
# If a string reaches here (via a validation error on another
# field) then just reconstruct the Geometry.
if isinstance(value, six.string_types):
- try:
- value = GEOSGeometry(value)
- except (GEOSException, ValueError) as err:
- logger.error(
- "Error creating geometry from value '%s' (%s)" % (
- value, err)
- )
- value = None
-
- wkt = ''
+ value = self.deserialize(value)
+
if value:
# Check that srid of value and map match
if value.srid != self.map_srid:
try:
ogr = value.ogr
ogr.transform(self.map_srid)
- wkt = ogr.wkt
+ value = ogr
except gdal.OGRException as err:
logger.error(
"Error transforming geometry from srid '%s' to srid '%s' (%s)" % (
value.srid, self.map_srid, err)
)
- else:
- wkt = value.wkt
context = self.build_attrs(attrs,
name=name,
module='geodjango_%s' % name.replace('-','_'), # JS-safe
- wkt=wkt,
+ serialized=self.serialize(value),
geom_type=gdal.OGRGeomType(self.attrs['geom_type']),
STATIC_URL=settings.STATIC_URL,
LANGUAGE_BIDI=translation.get_language_bidi(),
diff --git a/django/contrib/gis/templates/gis/openlayers.html b/django/contrib/gis/templates/gis/openlayers.html
index 281c0badd63f..4884f48106ca 100644
--- a/django/contrib/gis/templates/gis/openlayers.html
+++ b/django/contrib/gis/templates/gis/openlayers.html
@@ -2,7 +2,7 @@
#{{ id }}_map { width: {{ map_width }}px; height: {{ map_height }}px; }
#{{ id }}_map .aligned label { float: inherit; }
#{{ id }}_div_map { position: relative; vertical-align: top; float: {{ LANGUAGE_BIDI|yesno:"right,left" }}; }
- {% if not display_wkt %}#{{ id }} { display: none; }{% endif %}
+ {% if not display_raw %}#{{ id }} { display: none; }{% endif %}
.olControlEditingToolbar .olControlModifyFeatureItemActive {
background-image: url("{{ STATIC_URL }}admin/img/gis/move_vertex_on.png");
background-repeat: no-repeat;
@@ -16,8 +16,8 @@