From 478d3af0003da5726404e8def5066301b86fd9ed Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 23 Sep 2015 19:36:23 -0400 Subject: [PATCH 001/756] [1.9.x] Bumped django_next_version in docs config. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 10caf0ce8acd..029e48952986 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -87,7 +87,7 @@ def django_release(): release = django_release() # The "development version" of Django -django_next_version = '1.9' +django_next_version = '1.10' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From f20364c7d0be555afe7bebd5d15154b59e8cba2e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 23 Sep 2015 19:48:53 -0400 Subject: [PATCH 002/756] [1.9.x] Bumped version to 1.9a1. --- 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 3a790cbf1477..17cdb999a32c 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 0, 'alpha', 0) +VERSION = (1, 9, 0, 'alpha', 1) __version__ = get_version(VERSION) diff --git a/setup.py b/setup.py index 8cbf4de8a73d..e94065e0edea 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ }, zip_safe=False, classifiers=[ - 'Development Status :: 2 - Pre-Alpha', + 'Development Status :: 3 - Alpha', 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', From eae5f738ba4d6623cb0b88be7c5b7dbf9d5015b8 Mon Sep 17 00:00:00 2001 From: Samir Shah Date: Thu, 24 Sep 2015 08:09:41 +0300 Subject: [PATCH 003/756] [1.9.x] Fixed typo in 1.9 release notes. Backport of dfa81bb1df81637be333e9a67120a1670703a941 from master --- docs/releases/1.9.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 57718fb8ba3d..c63ae1c351ed 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -422,7 +422,7 @@ Management Commands preceded by the operation's description. * The :djadmin:`dumpdata` command output is now deterministically ordered. - Moreover, when the ``--ouput`` option is specified, it also shows a progress + Moreover, when the ``--output`` option is specified, it also shows a progress bar in the terminal. * The :djadmin:`createcachetable` command now has a ``--dry-run`` flag to From 6c8d36a1abf7da1ea5abfa8ce4dcd8e7b9154e15 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 24 Sep 2015 09:11:00 -0400 Subject: [PATCH 004/756] [1.9.x] Removed unused Aggregate.input_field. Obsolete since 4a66a69239c493c05b322815b18c605cd4c96e7c. Backport of 1f84721fac8852ebc85a2b5c95f9a65439b3937c from master --- django/db/models/aggregates.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/django/db/models/aggregates.py b/django/db/models/aggregates.py index dfae09e34f9e..00245b4a86d8 100644 --- a/django/db/models/aggregates.py +++ b/django/db/models/aggregates.py @@ -27,10 +27,6 @@ def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize c._patch_aggregate(query) # backward-compatibility support return c - @property - def input_field(self): - return self.source_expressions[0] - @property def default_alias(self): expressions = self.get_source_expressions() From 566a01e916a0565db5af8dfb5ac9ce2fafc74c66 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 24 Sep 2015 08:17:40 -0400 Subject: [PATCH 005/756] [1.9.x] Removed unused views.debug.linebreak_iter() function. Backport of fd1e54b923871d9596f9692c0fc576cd40ce3e38 from master --- django/views/debug.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/django/views/debug.py b/django/views/debug.py index b6201ab54a3c..1aeb05f7b084 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -24,15 +24,6 @@ CLEANSED_SUBSTITUTE = '********************' -def linebreak_iter(template_source): - yield 0 - p = template_source.find('\n') - while p >= 0: - yield p + 1 - p = template_source.find('\n', p + 1) - yield len(template_source) + 1 - - class CallableSettingWrapper(object): """ Object to wrap callable appearing in settings From a2cf430c80bc844f557b150b79cdf7ef5496ed0b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 24 Sep 2015 10:51:04 -0400 Subject: [PATCH 006/756] [1.9.x] Bumped latest Python 2.7 release in docs/ref/databases.txt. Backport of ce531f7ad783facc5799cf43b59b5ce053aa5d9e from master --- docs/ref/databases.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 2d866c5ae02f..6e0dc534df19 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -635,7 +635,7 @@ SQLite 3.6.23.1 was released in March 2010, and most current binary distributions for different platforms include a newer version of SQLite, with the notable exception of the Python 2.7 installers for Windows. -As of this writing, the latest release for Windows - Python 2.7.9 - includes +As of this writing, the latest release for Windows - Python 2.7.10 - includes SQLite 3.6.21. You can install ``pysqlite2`` or replace ``sqlite3.dll`` (by default installed in ``C:\Python27\DLLs``) with a newer version from http://www.sqlite.org/ to remedy this issue. From ca2c5508be47af88bda040abd9bc95e8f3087c12 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 24 Sep 2015 13:17:39 -0400 Subject: [PATCH 007/756] [1.9.x] Fixed #25455 -- Optimized dictfetchall() example. Thanks aklim007 for the suggestion. Backport of 361f60479d1890e8144fc254d7389a67b35725e9 from master --- docs/topics/db/sql.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index 999853c5ee3d..a7970928f152 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -282,9 +282,9 @@ using something like this:: def dictfetchall(cursor): "Return all rows from a cursor as a dict" - desc = cursor.description + columns = [col[0] for col in cursor.description] return [ - dict(zip([col[0] for col in desc], row)) + dict(zip(columns, row)) for row in cursor.fetchall() ] From 9b5f1880e9697e009d5bfda6df6c5b5f35ea02e9 Mon Sep 17 00:00:00 2001 From: Moritz Sichert Date: Thu, 24 Sep 2015 19:57:19 +0200 Subject: [PATCH 008/756] [1.9.x] Refs #25294 -- Added BoundField import for backwards compatibility. Backport of a51070e7434426467869147608c5a8ca58e21e00 from master --- django/forms/forms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django/forms/forms.py b/django/forms/forms.py index a6cedd83bebe..7aceb905a75f 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -8,6 +8,8 @@ from collections import OrderedDict from django.core.exceptions import NON_FIELD_ERRORS, ValidationError +# BoundField is imported for backwards compatibility in Django 1.9 +from django.forms.boundfield import BoundField # NOQA from django.forms.fields import Field, FileField # pretty_name is imported for backwards compatibility in Django 1.9 from django.forms.utils import ErrorDict, ErrorList, pretty_name # NOQA From 9581ac39a8b913db22f9874ae04aad46548dcdeb Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 25 Sep 2015 02:18:58 +0800 Subject: [PATCH 009/756] [1.9.x] Fixed #25457 -- Improved formatting of password validation errors in management command output. Backport of 7372cdebed19a9d8e5527823c1e6825156babf98 from master --- django/contrib/auth/management/commands/changepassword.py | 2 +- django/contrib/auth/management/commands/createsuperuser.py | 2 +- tests/auth_tests/test_management.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/django/contrib/auth/management/commands/changepassword.py b/django/contrib/auth/management/commands/changepassword.py index 600911f75926..162d08a3a910 100644 --- a/django/contrib/auth/management/commands/changepassword.py +++ b/django/contrib/auth/management/commands/changepassword.py @@ -60,7 +60,7 @@ def handle(self, *args, **options): try: validate_password(p2, u) except ValidationError as err: - self.stdout.write(', '.join(err.messages)) + self.stderr.write('\n'.join(err.messages)) count += 1 else: password_validated = True diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index 56461cedfab6..58364ec543e6 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -153,7 +153,7 @@ def handle(self, *args, **options): try: validate_password(password2, self.UserModel(**fake_user_data)) except exceptions.ValidationError as err: - self.stderr.write(', '.join(err.messages)) + self.stderr.write('\n'.join(err.messages)) password = None except KeyboardInterrupt: diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py index 70d5ceac5be6..435bc9e1e5e2 100644 --- a/tests/auth_tests/test_management.py +++ b/tests/auth_tests/test_management.py @@ -160,7 +160,7 @@ def test_password_validation(self): abort_msg = "Aborting password change for user 'joe' after 3 attempts" with self.assertRaisesMessage(CommandError, abort_msg): command.execute(username="joe", stdout=self.stdout, stderr=self.stderr) - self.assertIn('This password is entirely numeric.', self.stdout.getvalue()) + self.assertIn('This password is entirely numeric.', self.stderr.getvalue()) def test_that_changepassword_command_works_with_nonascii_output(self): """ From f2f4f1cfff80244be628f24c46ab7ab0e91a11c4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 24 Sep 2015 14:48:49 -0400 Subject: [PATCH 010/756] [1.9.x] Fixed #25451 -- Added advice about organizing tests. Backport of 53e89ce2e71411ff9d1c3fd6f2b10d052a4aeace from master --- docs/topics/testing/advanced.txt | 1 + docs/topics/testing/overview.txt | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt index eacc04c6d60a..20c89db18006 100644 --- a/docs/topics/testing/advanced.txt +++ b/docs/topics/testing/advanced.txt @@ -248,6 +248,7 @@ Advanced features of ``TransactionTestCase`` Using ``reset_sequences = True`` will slow down the test, since the primary key reset is an relatively expensive database operation. +.. _testing-reusable-applications: Using the Django test runner to test reusable applications ========================================================== diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt index fc18eb0e4a8e..1d11d7155301 100644 --- a/docs/topics/testing/overview.txt +++ b/docs/topics/testing/overview.txt @@ -46,6 +46,17 @@ automatically build a test suite out of those test cases, and run that suite. For more details about :mod:`unittest`, see the Python documentation. +.. admonition:: Where should the tests live? + + The default :djadmin:`startapp` template creates a ``tests.py`` file in the + new application. This might be fine if you only have a few tests, but as + your test suite grows you'll likely want to restructure it into a tests + package so you can split your tests into different submodules such as + ``test_models.py``, ``test_views.py``, ``test_forms.py``, etc. Feel free to + pick whatever organizational scheme you like. + + See also :ref:`testing-reusable-applications`. + .. warning:: If your tests rely on database access such as creating or querying models, @@ -60,7 +71,6 @@ For more details about :mod:`unittest`, see the Python documentation. .. _running-tests: - Running tests ============= From 50ccedae98742ae0d9dfb5d7c268ee2e2a096e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 30 Jul 2015 13:56:54 +0300 Subject: [PATCH 011/756] [1.9.x] Fixed #25196 -- Normalized database representations in test database messages. Left over Oracle mostly as-is since it's more complicated. Backport of afe777639cc1c591df32591e196d4cda5783c615 from master --- django/db/backends/base/creation.py | 40 ++++++++++++++--------- django/db/backends/mysql/creation.py | 4 ++- django/db/backends/oracle/creation.py | 4 +-- django/db/backends/postgresql/creation.py | 4 ++- django/db/backends/sqlite3/creation.py | 8 +++-- 5 files changed, 38 insertions(+), 22 deletions(-) diff --git a/django/db/backends/base/creation.py b/django/db/backends/base/creation.py index 888dc792fe4e..bca8376368d7 100644 --- a/django/db/backends/base/creation.py +++ b/django/db/backends/base/creation.py @@ -39,15 +39,14 @@ def create_test_db(self, verbosity=1, autoclobber=False, serialize=True, keepdb= test_database_name = self._get_test_db_name() if verbosity >= 1: - test_db_repr = '' action = 'Creating' - if verbosity >= 2: - test_db_repr = " ('%s')" % test_database_name if keepdb: action = "Using existing" - print("%s test database for alias '%s'%s..." % ( - action, self.connection.alias, test_db_repr)) + print("%s test database for alias %s..." % ( + action, + self._get_database_display_str(verbosity, test_database_name), + )) # We could skip this call if keepdb is True, but we instead # give it the keepdb param. This is to handle the case @@ -132,6 +131,15 @@ def deserialize_db_from_string(self, data): for obj in serializers.deserialize("json", data, using=self.connection.alias): obj.save() + def _get_database_display_str(self, verbosity, database_name): + """ + Return display string for a database for use in various actions. + """ + return "'%s'%s" % ( + self.connection.alias, + (" ('%s')" % database_name) if verbosity >= 2 else '', + ) + def _get_test_db_name(self): """ Internal implementation - returns the name of the test DB that will be @@ -173,8 +181,9 @@ def _create_test_db(self, verbosity, autoclobber, keepdb=False): if autoclobber or confirm == 'yes': try: if verbosity >= 1: - print("Destroying old test database '%s'..." - % self.connection.alias) + print("Destroying old test database for alias %s..." % ( + self._get_database_display_str(verbosity, test_database_name), + )) cursor.execute( "DROP DATABASE %s" % qn(test_database_name)) cursor.execute( @@ -197,13 +206,13 @@ def clone_test_db(self, number, verbosity=1, autoclobber=False, keepdb=False): source_database_name = self.connection.settings_dict['NAME'] if verbosity >= 1: - test_db_repr = '' action = 'Cloning test database' - if verbosity >= 2: - test_db_repr = " ('%s')" % source_database_name if keepdb: action = 'Using existing clone' - print("%s for alias '%s'%s..." % (action, self.connection.alias, test_db_repr)) + print("%s for alias %s..." % ( + action, + self._get_database_display_str(verbosity, source_database_name), + )) # We could skip this call if keepdb is True, but we instead # give it the keepdb param. See create_test_db for details. @@ -241,14 +250,13 @@ def destroy_test_db(self, old_database_name=None, verbosity=1, keepdb=False, num test_database_name = self.get_test_db_clone_settings(number)['NAME'] if verbosity >= 1: - test_db_repr = '' action = 'Destroying' - if verbosity >= 2: - test_db_repr = " ('%s')" % test_database_name if keepdb: action = 'Preserving' - print("%s test database for alias '%s'%s..." % ( - action, self.connection.alias, test_db_repr)) + print("%s test database for alias %s..." % ( + action, + self._get_database_display_str(verbosity, test_database_name), + )) # if we want to preserve the database # skip the actual destroying piece. diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py index a992e65952df..43857f07eaba 100644 --- a/django/db/backends/mysql/creation.py +++ b/django/db/backends/mysql/creation.py @@ -30,7 +30,9 @@ def _clone_test_db(self, number, verbosity, keepdb=False): return try: if verbosity >= 1: - print("Destroying old test database '%s'..." % self.connection.alias) + print("Destroying old test database for alias %s..." % ( + self._get_database_display_str(target_database_name, verbosity), + )) cursor.execute("DROP DATABASE %s" % qn(target_database_name)) cursor.execute("CREATE DATABASE %s" % qn(target_database_name)) except Exception as e: diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index afe965d664c4..7f921085244d 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -48,7 +48,7 @@ def _create_test_db(self, verbosity=1, autoclobber=False, keepdb=False): "Type 'yes' to delete it, or 'no' to cancel: " % parameters['user']) if autoclobber or confirm == 'yes': if verbosity >= 1: - print("Destroying old test database '%s'..." % self.connection.alias) + print("Destroying old test database for alias '%s'..." % self.connection.alias) try: self._execute_test_db_destruction(cursor, parameters, verbosity) except DatabaseError as e: @@ -149,7 +149,7 @@ def _handle_objects_preventing_db_destruction(self, cursor, parameters, verbosit sys.exit(2) try: if verbosity >= 1: - print("Destroying old test database '%s'..." % self.connection.alias) + print("Destroying old test database for alias '%s'..." % self.connection.alias) self._execute_test_db_destruction(cursor, parameters, verbosity) except Exception as e: sys.stderr.write("Got an error destroying the test database: %s\n" % e) diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py index dcb0430f1b04..2a886bae3c69 100644 --- a/django/db/backends/postgresql/creation.py +++ b/django/db/backends/postgresql/creation.py @@ -32,7 +32,9 @@ def _clone_test_db(self, number, verbosity, keepdb=False): return try: if verbosity >= 1: - print("Destroying old test database '%s'..." % self.connection.alias) + print("Destroying old test database for alias %s..." % ( + self._get_database_display_str(target_database_name, verbosity), + )) cursor.execute("DROP DATABASE %s" % qn(target_database_name)) cursor.execute("CREATE DATABASE %s WITH TEMPLATE %s" % ( qn(target_database_name), qn(source_database_name))) diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py index d52349e57f64..a6e3155aa81d 100644 --- a/django/db/backends/sqlite3/creation.py +++ b/django/db/backends/sqlite3/creation.py @@ -30,7 +30,9 @@ def _create_test_db(self, verbosity, autoclobber, keepdb=False): if not self.connection.is_in_memory_db(test_database_name): # Erase the old test database if verbosity >= 1: - print("Destroying old test database '%s'..." % self.connection.alias) + print("Destroying old test database for alias %s..." % ( + self._get_database_display_str(verbosity, test_database_name), + )) if os.access(test_database_name, os.F_OK): if not autoclobber: confirm = input( @@ -69,7 +71,9 @@ def _clone_test_db(self, number, verbosity, keepdb=False): if keepdb: return if verbosity >= 1: - print("Destroying old test database '%s'..." % target_database_name) + print("Destroying old test database for alias %s..." % ( + self._get_database_display_str(verbosity, target_database_name), + )) try: os.remove(target_database_name) except Exception as e: From ffcf81969b50e6746061e5a687363faa667959f3 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 25 Sep 2015 09:33:03 -0400 Subject: [PATCH 012/756] [1.9.x] Removed redundancy in admin_changelist tests. Backport of c42123adb166fd297116880a5322e4e17b11e33f from master --- tests/admin_changelist/tests.py | 173 +++++++++++--------------------- 1 file changed, 58 insertions(+), 115 deletions(-) diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index 005aad6a3bbe..caf9beeea042 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -32,6 +32,24 @@ ) +def get_changelist_args(modeladmin, **kwargs): + m = modeladmin + args = ( + kwargs.pop('list_display', m.list_display), + kwargs.pop('list_display_links', m.list_display_links), + kwargs.pop('list_filter', m.list_filter), + kwargs.pop('date_hierarchy', m.date_hierarchy), + kwargs.pop('search_fields', m.search_fields), + kwargs.pop('list_select_related', m.list_select_related), + kwargs.pop('list_per_page', m.list_per_page), + kwargs.pop('list_max_show_all', m.list_max_show_all), + kwargs.pop('list_editable', m.list_editable), + m, + ) + assert not kwargs, "Unexpected kwarg %s" % kwargs + return args + + @override_settings(ROOT_URLCONF="admin_changelist.urls") class ChangeListTests(TestCase): @@ -53,11 +71,10 @@ def test_select_related_preserved(self): """ m = ChildAdmin(Child, custom_site) request = self.factory.get('/child/') - list_select_related = m.get_list_select_related(request) - cl = ChangeList(request, Child, m.list_display, m.list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - list_select_related, m.list_per_page, - m.list_max_show_all, m.list_editable, m) + cl = ChangeList( + request, Child, + *get_changelist_args(m, list_select_related=m.get_list_select_related(request)) + ) self.assertEqual(cl.queryset.query.select_related, { 'parent': {'name': {}} }) @@ -65,22 +82,20 @@ def test_select_related_preserved(self): def test_select_related_as_tuple(self): ia = InvitationAdmin(Invitation, custom_site) request = self.factory.get('/invitation/') - list_select_related = ia.get_list_select_related(request) - cl = ChangeList(request, Child, ia.list_display, ia.list_display_links, - ia.list_filter, ia.date_hierarchy, ia.search_fields, - list_select_related, ia.list_per_page, - ia.list_max_show_all, ia.list_editable, ia) + cl = ChangeList( + request, Child, + *get_changelist_args(ia, list_select_related=ia.get_list_select_related(request)) + ) self.assertEqual(cl.queryset.query.select_related, {'player': {}}) def test_select_related_as_empty_tuple(self): ia = InvitationAdmin(Invitation, custom_site) ia.list_select_related = () request = self.factory.get('/invitation/') - list_select_related = ia.get_list_select_related(request) - cl = ChangeList(request, Child, ia.list_display, ia.list_display_links, - ia.list_filter, ia.date_hierarchy, ia.search_fields, - list_select_related, ia.list_per_page, - ia.list_max_show_all, ia.list_editable, ia) + cl = ChangeList( + request, Child, + *get_changelist_args(ia, list_select_related=ia.get_list_select_related(request)) + ) self.assertEqual(cl.queryset.query.select_related, False) def test_get_select_related_custom_method(self): @@ -92,11 +107,10 @@ def get_list_select_related(self, request): ia = GetListSelectRelatedAdmin(Invitation, custom_site) request = self.factory.get('/invitation/') - list_select_related = ia.get_list_select_related(request) - cl = ChangeList(request, Child, ia.list_display, ia.list_display_links, - ia.list_filter, ia.date_hierarchy, ia.search_fields, - list_select_related, ia.list_per_page, - ia.list_max_show_all, ia.list_editable, ia) + cl = ChangeList( + request, Child, + *get_changelist_args(ia, list_select_related=ia.get_list_select_related(request)) + ) self.assertEqual(cl.queryset.query.select_related, {'player': {}, 'band': {}}) def test_result_list_empty_changelist_value(self): @@ -107,11 +121,7 @@ def test_result_list_empty_changelist_value(self): new_child = Child.objects.create(name='name', parent=None) request = self.factory.get('/child/') m = ChildAdmin(Child, custom_site) - list_display = m.get_list_display(request) - list_display_links = m.get_list_display_links(request, list_display) - cl = ChangeList(request, Child, list_display, list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m) + cl = ChangeList(request, Child, *get_changelist_args(m)) cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') context = Context({'cl': cl}) @@ -133,11 +143,7 @@ def test_result_list_set_empty_value_display_on_admin_site(self): # Set a new empty display value on AdminSite. admin.site.empty_value_display = '???' m = ChildAdmin(Child, admin.site) - list_display = m.get_list_display(request) - list_display_links = m.get_list_display_links(request, list_display) - cl = ChangeList(request, Child, list_display, list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m) + cl = ChangeList(request, Child, *get_changelist_args(m)) cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') context = Context({'cl': cl}) @@ -157,11 +163,7 @@ def test_result_list_set_empty_value_display_in_model_admin(self): new_child = Child.objects.create(name='name', parent=None) request = self.factory.get('/child/') m = EmptyValueChildAdmin(Child, admin.site) - list_display = m.get_list_display(request) - list_display_links = m.get_list_display_links(request, list_display) - cl = ChangeList(request, Child, list_display, list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m) + cl = ChangeList(request, Child, *get_changelist_args(m)) cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') context = Context({'cl': cl}) @@ -183,11 +185,7 @@ def test_result_list_html(self): new_child = Child.objects.create(name='name', parent=new_parent) request = self.factory.get('/child/') m = ChildAdmin(Child, custom_site) - list_display = m.get_list_display(request) - list_display_links = m.get_list_display_links(request, list_display) - cl = ChangeList(request, Child, list_display, list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m) + cl = ChangeList(request, Child, *get_changelist_args(m)) cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') context = Context({'cl': cl}) @@ -218,9 +216,7 @@ def test_result_list_editable_html(self): m.list_display = ['id', 'name', 'parent'] m.list_display_links = ['id'] m.list_editable = ['name'] - cl = ChangeList(request, Child, m.list_display, m.list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m) + cl = ChangeList(request, Child, *get_changelist_args(m)) FormSet = m.get_changelist_formset(request) cl.formset = FormSet(queryset=cl.result_list) template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') @@ -249,7 +245,6 @@ def test_result_list_editable(self): """ Regression test for #14312: list_editable with pagination """ - new_parent = Parent.objects.create(name='parent') for i in range(200): Child.objects.create(name='name %s' % i, parent=new_parent) @@ -260,10 +255,8 @@ def test_result_list_editable(self): m.list_display = ['id', 'name', 'parent'] m.list_display_links = ['id'] m.list_editable = ['name'] - self.assertRaises(IncorrectLookupParameters, lambda: - ChangeList(request, Child, m.list_display, m.list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)) + with self.assertRaises(IncorrectLookupParameters): + ChangeList(request, Child, *get_changelist_args(m)) @ignore_warnings(category=RemovedInDjango20Warning) def test_result_list_with_allow_tags(self): @@ -284,11 +277,7 @@ def custom_method(self, obj=None): m.custom_method = custom_method m.list_display = ['id', 'name', 'parent', 'custom_method'] - cl = ChangeList( - request, Child, m.list_display, m.list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m - ) + cl = ChangeList(request, Child, *get_changelist_args(m)) FormSet = m.get_changelist_formset(request) cl.formset = FormSet(queryset=cl.result_list) template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') @@ -305,10 +294,7 @@ def test_custom_paginator(self): request = self.factory.get('/child/') m = CustomPaginationAdmin(Child, custom_site) - cl = ChangeList(request, Child, m.list_display, m.list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m) - + cl = ChangeList(request, Child, *get_changelist_args(m)) cl.get_results(request) self.assertIsInstance(cl.paginator, CustomPaginator) @@ -326,11 +312,7 @@ def test_distinct_for_m2m_in_list_filter(self): m = BandAdmin(Band, custom_site) request = self.factory.get('/band/', data={'genres': blues.pk}) - cl = ChangeList(request, Band, m.list_display, - m.list_display_links, m.list_filter, m.date_hierarchy, - m.search_fields, m.list_select_related, m.list_per_page, - m.list_max_show_all, m.list_editable, m) - + cl = ChangeList(request, Band, *get_changelist_args(m)) cl.get_results(request) # There's only one Group instance @@ -349,11 +331,7 @@ def test_distinct_for_through_m2m_in_list_filter(self): m = GroupAdmin(Group, custom_site) request = self.factory.get('/group/', data={'members': lead.pk}) - cl = ChangeList(request, Group, m.list_display, - m.list_display_links, m.list_filter, m.date_hierarchy, - m.search_fields, m.list_select_related, m.list_per_page, - m.list_max_show_all, m.list_editable, m) - + cl = ChangeList(request, Group, *get_changelist_args(m)) cl.get_results(request) # There's only one Group instance @@ -374,11 +352,7 @@ def test_distinct_for_through_m2m_at_second_level_in_list_filter(self): m = ConcertAdmin(Concert, custom_site) request = self.factory.get('/concert/', data={'group__members': lead.pk}) - cl = ChangeList(request, Concert, m.list_display, - m.list_display_links, m.list_filter, m.date_hierarchy, - m.search_fields, m.list_select_related, m.list_per_page, - m.list_max_show_all, m.list_editable, m) - + cl = ChangeList(request, Concert, *get_changelist_args(m)) cl.get_results(request) # There's only one Concert instance @@ -398,11 +372,7 @@ def test_distinct_for_inherited_m2m_in_list_filter(self): m = QuartetAdmin(Quartet, custom_site) request = self.factory.get('/quartet/', data={'members': lead.pk}) - cl = ChangeList(request, Quartet, m.list_display, - m.list_display_links, m.list_filter, m.date_hierarchy, - m.search_fields, m.list_select_related, m.list_per_page, - m.list_max_show_all, m.list_editable, m) - + cl = ChangeList(request, Quartet, *get_changelist_args(m)) cl.get_results(request) # There's only one Quartet instance @@ -422,11 +392,7 @@ def test_distinct_for_m2m_to_inherited_in_list_filter(self): m = ChordsBandAdmin(ChordsBand, custom_site) request = self.factory.get('/chordsband/', data={'members': lead.pk}) - cl = ChangeList(request, ChordsBand, m.list_display, - m.list_display_links, m.list_filter, m.date_hierarchy, - m.search_fields, m.list_select_related, m.list_per_page, - m.list_max_show_all, m.list_editable, m) - + cl = ChangeList(request, ChordsBand, *get_changelist_args(m)) cl.get_results(request) # There's only one ChordsBand instance @@ -445,11 +411,7 @@ def test_distinct_for_non_unique_related_object_in_list_filter(self): m = ParentAdmin(Parent, custom_site) request = self.factory.get('/parent/', data={'child__name': 'Daniel'}) - cl = ChangeList(request, Parent, m.list_display, m.list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, - m.list_max_show_all, m.list_editable, m) - + cl = ChangeList(request, Parent, *get_changelist_args(m)) # Make sure distinct() was called self.assertEqual(cl.queryset.count(), 1) @@ -465,11 +427,7 @@ def test_distinct_for_non_unique_related_object_in_search_fields(self): m = ParentAdmin(Parent, custom_site) request = self.factory.get('/parent/', data={SEARCH_VAR: 'daniel'}) - cl = ChangeList(request, Parent, m.list_display, m.list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, - m.list_max_show_all, m.list_editable, m) - + cl = ChangeList(request, Parent, *get_changelist_args(m)) # Make sure distinct() was called self.assertEqual(cl.queryset.count(), 1) @@ -488,11 +446,7 @@ def test_distinct_for_many_to_many_at_second_level_in_search_fields(self): m = ConcertAdmin(Concert, custom_site) request = self.factory.get('/concert/', data={SEARCH_VAR: 'vox'}) - cl = ChangeList(request, Concert, m.list_display, - m.list_display_links, m.list_filter, m.date_hierarchy, - m.search_fields, m.list_select_related, m.list_per_page, - m.list_max_show_all, m.list_editable, m) - + cl = ChangeList(request, Concert, *get_changelist_args(m)) # There's only one Concert instance self.assertEqual(cl.queryset.count(), 1) @@ -510,20 +464,14 @@ def test_pagination(self): # Test default queryset m = ChildAdmin(Child, custom_site) - cl = ChangeList(request, Child, m.list_display, m.list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, m.list_max_show_all, - m.list_editable, m) + cl = ChangeList(request, Child, *get_changelist_args(m)) self.assertEqual(cl.queryset.count(), 60) self.assertEqual(cl.paginator.count, 60) self.assertEqual(list(cl.paginator.page_range), [1, 2, 3, 4, 5, 6]) # Test custom queryset m = FilteredChildAdmin(Child, custom_site) - cl = ChangeList(request, Child, m.list_display, m.list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, m.list_max_show_all, - m.list_editable, m) + cl = ChangeList(request, Child, *get_changelist_args(m)) self.assertEqual(cl.queryset.count(), 30) self.assertEqual(cl.paginator.count, 30) self.assertEqual(list(cl.paginator.page_range), [1, 2, 3]) @@ -594,20 +542,18 @@ def test_show_all(self): # Test valid "show all" request (number of total objects is under max) m = ChildAdmin(Child, custom_site) + m.list_max_show_all = 200 # 200 is the max we'll pass to ChangeList - cl = ChangeList(request, Child, m.list_display, m.list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, 200, m.list_editable, m) + cl = ChangeList(request, Child, *get_changelist_args(m)) cl.get_results(request) self.assertEqual(len(cl.result_list), 60) # Test invalid "show all" request (number of total objects over max) # falls back to paginated pages m = ChildAdmin(Child, custom_site) + m.list_max_show_all = 30 # 30 is the max we'll pass to ChangeList for this test - cl = ChangeList(request, Child, m.list_display, m.list_display_links, - m.list_filter, m.date_hierarchy, m.search_fields, - m.list_select_related, m.list_per_page, 30, m.list_editable, m) + cl = ChangeList(request, Child, *get_changelist_args(m)) cl.get_results(request) self.assertEqual(len(cl.result_list), 10) @@ -791,10 +737,7 @@ def test_pagination_page_range(self): # instantiating and setting up ChangeList object m = GroupAdmin(Group, custom_site) request = self.factory.get('/group/') - cl = ChangeList(request, Group, m.list_display, - m.list_display_links, m.list_filter, m.date_hierarchy, - m.search_fields, m.list_select_related, m.list_per_page, - m.list_max_show_all, m.list_editable, m) + cl = ChangeList(request, Group, *get_changelist_args(m)) per_page = cl.list_per_page = 10 for page_num, objects_count, expected_page_range in [ From 260e9f15fe05226188a63ef8e30fa069e48f2660 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 25 Sep 2015 13:28:12 -0400 Subject: [PATCH 013/756] [1.9.x] Fixed #25462 -- Removed Model.__unicode__() in favor of @python_2_unicode_compatible. Backport of de99f558d806a2a1b30072ec95bc44d412d80dab from master --- docs/intro/tutorial02.txt | 24 +++--------- docs/ref/models/instances.txt | 69 ++++------------------------------- docs/topics/db/models.txt | 6 +-- 3 files changed, 16 insertions(+), 83 deletions(-) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index de1f43708f0e..a92b75727920 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -445,15 +445,18 @@ of this object. Let's fix that by editing the ``Question`` model (in the :filename: polls/models.py from django.db import models + from django.utils.encoding import python_2_unicode_compatible + @python_2_unicode_compatible # only if you need to support Python 2 class Question(models.Model): # ... - def __str__(self): # __unicode__ on Python 2 + def __str__(self): return self.question_text + @python_2_unicode_compatible # only if you need to support Python 2 class Choice(models.Model): # ... - def __str__(self): # __unicode__ on Python 2 + def __str__(self): return self.choice_text It's important to add :meth:`~django.db.models.Model.__str__` methods to your @@ -461,23 +464,6 @@ models, not only for your own convenience when dealing with the interactive prompt, but also because objects' representations are used throughout Django's automatically-generated admin. -.. admonition:: ``__str__`` or ``__unicode__``? - - On Python 3, it's easy, just use - :meth:`~django.db.models.Model.__str__`. - - On Python 2, you should define :meth:`~django.db.models.Model.__unicode__` - methods returning ``unicode`` values instead. Django models have a default - :meth:`~django.db.models.Model.__str__` method that calls - :meth:`~django.db.models.Model.__unicode__` and converts the result to a - UTF-8 bytestring. This means that ``unicode(p)`` will return a Unicode - string, and ``str(p)`` will return a bytestring, with characters encoded - as UTF-8. Python does the opposite: ``object`` has a ``__unicode__`` - method that calls ``__str__`` and interprets the result as an ASCII - bytestring. This difference can create confusion. - - If all of this is gibberish to you, just use Python 3. - Note these are normal Python methods. Let's add a custom method, just for demonstration: diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 85d68feaf23c..5c5585e05e97 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -596,58 +596,23 @@ Other model instance methods A few object methods have special purposes. -.. note:: - On Python 3, as all strings are natively considered Unicode, only use the - ``__str__()`` method (the ``__unicode__()`` method is obsolete). - If you'd like compatibility with Python 2, you can decorate your model class - with :func:`~django.utils.encoding.python_2_unicode_compatible`. - -``__unicode__`` ---------------- - -.. method:: Model.__unicode__() - -The ``__unicode__()`` method is called whenever you call ``unicode()`` on an -object. Django uses ``unicode(obj)`` (or the related function, :meth:`str(obj) -`) in a number of places. Most notably, to display an object in -the Django admin site and as the value inserted into a template when it -displays an object. Thus, you should always return a nice, human-readable -representation of the model from the ``__unicode__()`` method. - -For example:: - - from django.db import models - - class Person(models.Model): - first_name = models.CharField(max_length=50) - last_name = models.CharField(max_length=50) - - def __unicode__(self): - return u'%s %s' % (self.first_name, self.last_name) - -If you define a ``__unicode__()`` method on your model and not a -:meth:`~Model.__str__()` method, Django will automatically provide you with a -:meth:`~Model.__str__()` that calls ``__unicode__()`` and then converts the -result correctly to a UTF-8 encoded string object. This is recommended -development practice: define only ``__unicode__()`` and let Django take care of -the conversion to string objects when required. - ``__str__`` ----------- .. method:: Model.__str__() -The ``__str__()`` method is called whenever you call ``str()`` on an -object. In Python 3, Django uses ``str(obj)`` in a number of -places. Most notably, to display an object in the Django admin site -and as the value inserted into a template when it displays an -object. Thus, you should always return a nice, human-readable +The ``__str__()`` method is called whenever you call ``str()`` on an object. +Django uses ``str(obj)`` in a number of places. Most notably, to display an +object in the Django admin site and as the value inserted into a template when +it displays an object. Thus, you should always return a nice, human-readable representation of the model from the ``__str__()`` method. For example:: from django.db import models + from django.utils.encoding import python_2_unicode_compatible + @python_2_unicode_compatible # only if you need to support Python 2 class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) @@ -655,26 +620,8 @@ For example:: def __str__(self): return '%s %s' % (self.first_name, self.last_name) -In Python 2, the main use of ``__str__`` directly inside Django is -when the ``repr()`` output of a model is displayed anywhere (for -example, in debugging output). It isn't required to put ``__str__()`` -methods everywhere if you have sensible :meth:`~Model.__unicode__()` -methods. - -The previous :meth:`~Model.__unicode__()` example could be similarly written -using ``__str__()`` like this:: - - from django.db import models - from django.utils.encoding import force_bytes - - class Person(models.Model): - first_name = models.CharField(max_length=50) - last_name = models.CharField(max_length=50) - - def __str__(self): - # Note use of django.utils.encoding.force_bytes() here because - # first_name and last_name will be unicode strings. - return force_bytes('%s %s' % (self.first_name, self.last_name)) +If you'd like compatibility with Python 2, you can decorate your model class +with :func:`~django.utils.encoding.python_2_unicode_compatible` as show above. ``__eq__`` ---------- diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index ed13e463745b..53d2396f0385 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -756,9 +756,6 @@ You can override most of these -- see `overriding predefined model methods`_, below -- but there are a couple that you'll almost always want to define: :meth:`~Model.__str__` (Python 3) - Python 3 equivalent of ``__unicode__()``. - -:meth:`~Model.__unicode__` (Python 2) A Python "magic method" that returns a unicode "representation" of any object. This is what Python and Django will use whenever a model instance needs to be coerced and displayed as a plain string. Most @@ -768,6 +765,9 @@ below -- but there are a couple that you'll almost always want to define: You'll always want to define this method; the default isn't very helpful at all. +``__unicode__()`` (Python 2) + Python 2 equivalent of ``__str__()``. + :meth:`~Model.get_absolute_url` This tells Django how to calculate the URL for an object. Django uses this in its admin interface, and any time it needs to figure out a URL From bc4ae0f5eb195271887b95e31da76fa21fc7424e Mon Sep 17 00:00:00 2001 From: Rob Hudson Date: Fri, 25 Sep 2015 14:47:36 -0700 Subject: [PATCH 014/756] [1.9.x] Corrected use of 'affect' vs 'effect' in docs. Backport of 021782d22b80ea57fdd5e040add58adeafaedc55 from master --- docs/ref/migration-operations.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/migration-operations.txt b/docs/ref/migration-operations.txt index e80630c768db..3ba1a4efef7d 100644 --- a/docs/ref/migration-operations.txt +++ b/docs/ref/migration-operations.txt @@ -152,7 +152,7 @@ The ``preserve_default`` argument indicates whether the field's default value is permanent and should be baked into the project state (``True``), or if it is temporary and just for this migration (``False``) - usually because the migration is adding a non-nullable field to a table and needs -a default value to put into existing rows. It does not effect the behavior +a default value to put into existing rows. It does not affect the behavior of setting defaults in the database directly - Django never sets database defaults and always applies them in the Django ORM code. @@ -180,7 +180,7 @@ The ``preserve_default`` argument indicates whether the field's default value is permanent and should be baked into the project state (``True``), or if it is temporary and just for this migration (``False``) - usually because the migration is altering a nullable field to a non-nullable one and -needs a default value to put into existing rows. It does not effect the +needs a default value to put into existing rows. It does not affect the behavior of setting defaults in the database directly - Django never sets database defaults and always applies them in the Django ORM code. From 5452d39e01bceaf5e4aad0dfcf323920991d442a Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 Sep 2015 10:43:12 +0200 Subject: [PATCH 015/756] [1.9.x] Separated YAML serialization tests Backport of febd5aeec from master. --- tests/serializers/test_yaml.py | 180 +++++++++++++++++++++++++++++ tests/serializers/tests.py | 166 +------------------------- tests/serializers_regress/tests.py | 12 -- 3 files changed, 181 insertions(+), 177 deletions(-) create mode 100644 tests/serializers/test_yaml.py diff --git a/tests/serializers/test_yaml.py b/tests/serializers/test_yaml.py new file mode 100644 index 000000000000..d4e5c355782e --- /dev/null +++ b/tests/serializers/test_yaml.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import importlib +import unittest + +from django.core import management, serializers +from django.core.serializers.base import DeserializationError +from django.test import SimpleTestCase, TestCase, TransactionTestCase +from django.utils import six +from django.utils.six import StringIO + +from .models import Author +from .tests import SerializersTestBase, SerializersTransactionTestBase + +try: + import yaml + HAS_YAML = True +except ImportError: + HAS_YAML = False + +YAML_IMPORT_ERROR_MESSAGE = r'No module named yaml' + + +class YamlImportModuleMock(object): + """Provides a wrapped import_module function to simulate yaml ImportError + + In order to run tests that verify the behavior of the YAML serializer + when run on a system that has yaml installed (like the django CI server), + mock import_module, so that it raises an ImportError when the yaml + serializer is being imported. The importlib.import_module() call is + being made in the serializers.register_serializer(). + + Refs: #12756 + """ + def __init__(self): + self._import_module = importlib.import_module + + def import_module(self, module_path): + if module_path == serializers.BUILTIN_SERIALIZERS['yaml']: + raise ImportError(YAML_IMPORT_ERROR_MESSAGE) + + return self._import_module(module_path) + + +class NoYamlSerializerTestCase(SimpleTestCase): + """Not having pyyaml installed provides a misleading error + + Refs: #12756 + """ + @classmethod + def setUpClass(cls): + """Removes imported yaml and stubs importlib.import_module""" + super(NoYamlSerializerTestCase, cls).setUpClass() + + cls._import_module_mock = YamlImportModuleMock() + importlib.import_module = cls._import_module_mock.import_module + + # clear out cached serializers to emulate yaml missing + serializers._serializers = {} + + @classmethod + def tearDownClass(cls): + """Puts yaml back if necessary""" + super(NoYamlSerializerTestCase, cls).tearDownClass() + + importlib.import_module = cls._import_module_mock._import_module + + # clear out cached serializers to clean out BadSerializer instances + serializers._serializers = {} + + def test_serializer_pyyaml_error_message(self): + """Using yaml serializer without pyyaml raises ImportError""" + jane = Author(name="Jane") + self.assertRaises(ImportError, serializers.serialize, "yaml", [jane]) + + def test_deserializer_pyyaml_error_message(self): + """Using yaml deserializer without pyyaml raises ImportError""" + self.assertRaises(ImportError, serializers.deserialize, "yaml", "") + + def test_dumpdata_pyyaml_error_message(self): + """Calling dumpdata produces an error when yaml package missing""" + with six.assertRaisesRegex(self, management.CommandError, YAML_IMPORT_ERROR_MESSAGE): + management.call_command('dumpdata', format='yaml') + + +@unittest.skipUnless(HAS_YAML, "No yaml library detected") +class YamlSerializerTestCase(SerializersTestBase, TestCase): + serializer_name = "yaml" + fwd_ref_str = """- fields: + headline: Forward references pose no problem + pub_date: 2006-06-16 15:00:00 + categories: [1] + author: 1 + pk: 1 + model: serializers.article +- fields: + name: Reference + pk: 1 + model: serializers.category +- fields: + name: Agnes + pk: 1 + model: serializers.author""" + + pkless_str = """- fields: + name: Reference + pk: null + model: serializers.category +- fields: + name: Non-fiction + model: serializers.category""" + + mapping_ordering_str = """- model: serializers.article + pk: %(article_pk)s + fields: + author: %(author_pk)s + headline: Poker has no place on ESPN + pub_date: 2006-06-16 11:00:00 + categories: [%(first_category_pk)s, %(second_category_pk)s] + meta_data: [] +""" + + @staticmethod + def _validate_output(serial_str): + try: + yaml.safe_load(StringIO(serial_str)) + except Exception: + return False + else: + return True + + @staticmethod + def _get_pk_values(serial_str): + ret_list = [] + stream = StringIO(serial_str) + for obj_dict in yaml.safe_load(stream): + ret_list.append(obj_dict["pk"]) + return ret_list + + @staticmethod + def _get_field_values(serial_str, field_name): + ret_list = [] + stream = StringIO(serial_str) + for obj_dict in yaml.safe_load(stream): + if "fields" in obj_dict and field_name in obj_dict["fields"]: + field_value = obj_dict["fields"][field_name] + # yaml.safe_load will return non-string objects for some + # of the fields we are interested in, this ensures that + # everything comes back as a string + if isinstance(field_value, six.string_types): + ret_list.append(field_value) + else: + ret_list.append(str(field_value)) + return ret_list + + def test_yaml_deserializer_exception(self): + with self.assertRaises(DeserializationError): + for obj in serializers.deserialize("yaml", "{"): + pass + + +@unittest.skipUnless(HAS_YAML, "No yaml library detected") +class YamlSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): + serializer_name = "yaml" + fwd_ref_str = """- fields: + headline: Forward references pose no problem + pub_date: 2006-06-16 15:00:00 + categories: [1] + author: 1 + pk: 1 + model: serializers.article +- fields: + name: Reference + pk: 1 + model: serializers.category +- fields: + name: Agnes + pk: 1 + model: serializers.author""" diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py index 8c5555d35db7..f92f0775d6e3 100644 --- a/tests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -1,14 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -import importlib import json import re -import unittest from datetime import datetime from xml.dom import minidom -from django.core import management, serializers +from django.core import serializers from django.core.serializers.base import ProgressBar from django.db import connection, transaction from django.test import ( @@ -24,12 +22,6 @@ Team, ) -try: - import yaml - HAS_YAML = True -except ImportError: - HAS_YAML = False - @override_settings( SERIALIZATION_MODULES={ @@ -682,159 +674,3 @@ class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, Transact "name": "Agnes" } }]""" - - -YAML_IMPORT_ERROR_MESSAGE = r'No module named yaml' - - -class YamlImportModuleMock(object): - """Provides a wrapped import_module function to simulate yaml ImportError - - In order to run tests that verify the behavior of the YAML serializer - when run on a system that has yaml installed (like the django CI server), - mock import_module, so that it raises an ImportError when the yaml - serializer is being imported. The importlib.import_module() call is - being made in the serializers.register_serializer(). - - Refs: #12756 - """ - def __init__(self): - self._import_module = importlib.import_module - - def import_module(self, module_path): - if module_path == serializers.BUILTIN_SERIALIZERS['yaml']: - raise ImportError(YAML_IMPORT_ERROR_MESSAGE) - - return self._import_module(module_path) - - -class NoYamlSerializerTestCase(SimpleTestCase): - """Not having pyyaml installed provides a misleading error - - Refs: #12756 - """ - @classmethod - def setUpClass(cls): - """Removes imported yaml and stubs importlib.import_module""" - super(NoYamlSerializerTestCase, cls).setUpClass() - - cls._import_module_mock = YamlImportModuleMock() - importlib.import_module = cls._import_module_mock.import_module - - # clear out cached serializers to emulate yaml missing - serializers._serializers = {} - - @classmethod - def tearDownClass(cls): - """Puts yaml back if necessary""" - super(NoYamlSerializerTestCase, cls).tearDownClass() - - importlib.import_module = cls._import_module_mock._import_module - - # clear out cached serializers to clean out BadSerializer instances - serializers._serializers = {} - - def test_serializer_pyyaml_error_message(self): - """Using yaml serializer without pyyaml raises ImportError""" - jane = Author(name="Jane") - self.assertRaises(ImportError, serializers.serialize, "yaml", [jane]) - - def test_deserializer_pyyaml_error_message(self): - """Using yaml deserializer without pyyaml raises ImportError""" - self.assertRaises(ImportError, serializers.deserialize, "yaml", "") - - def test_dumpdata_pyyaml_error_message(self): - """Calling dumpdata produces an error when yaml package missing""" - with six.assertRaisesRegex(self, management.CommandError, YAML_IMPORT_ERROR_MESSAGE): - management.call_command('dumpdata', format='yaml') - - -@unittest.skipUnless(HAS_YAML, "No yaml library detected") -class YamlSerializerTestCase(SerializersTestBase, TestCase): - serializer_name = "yaml" - fwd_ref_str = """- fields: - headline: Forward references pose no problem - pub_date: 2006-06-16 15:00:00 - categories: [1] - author: 1 - pk: 1 - model: serializers.article -- fields: - name: Reference - pk: 1 - model: serializers.category -- fields: - name: Agnes - pk: 1 - model: serializers.author""" - - pkless_str = """- fields: - name: Reference - pk: null - model: serializers.category -- fields: - name: Non-fiction - model: serializers.category""" - - mapping_ordering_str = """- model: serializers.article - pk: %(article_pk)s - fields: - author: %(author_pk)s - headline: Poker has no place on ESPN - pub_date: 2006-06-16 11:00:00 - categories: [%(first_category_pk)s, %(second_category_pk)s] - meta_data: [] -""" - - @staticmethod - def _validate_output(serial_str): - try: - yaml.safe_load(StringIO(serial_str)) - except Exception: - return False - else: - return True - - @staticmethod - def _get_pk_values(serial_str): - ret_list = [] - stream = StringIO(serial_str) - for obj_dict in yaml.safe_load(stream): - ret_list.append(obj_dict["pk"]) - return ret_list - - @staticmethod - def _get_field_values(serial_str, field_name): - ret_list = [] - stream = StringIO(serial_str) - for obj_dict in yaml.safe_load(stream): - if "fields" in obj_dict and field_name in obj_dict["fields"]: - field_value = obj_dict["fields"][field_name] - # yaml.safe_load will return non-string objects for some - # of the fields we are interested in, this ensures that - # everything comes back as a string - if isinstance(field_value, six.string_types): - ret_list.append(field_value) - else: - ret_list.append(str(field_value)) - return ret_list - - -@unittest.skipUnless(HAS_YAML, "No yaml library detected") -class YamlSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): - serializer_name = "yaml" - fwd_ref_str = """- fields: - headline: Forward references pose no problem - pub_date: 2006-06-16 15:00:00 - categories: [1] - author: 1 - pk: 1 - model: serializers.article -- fields: - name: Reference - pk: 1 - model: serializers.category -- fields: - name: Agnes - pk: 1 - model: serializers.author""" diff --git a/tests/serializers_regress/tests.py b/tests/serializers_regress/tests.py index 23837d17c0e6..3da6ddafb424 100644 --- a/tests/serializers_regress/tests.py +++ b/tests/serializers_regress/tests.py @@ -11,7 +11,6 @@ import datetime import decimal import uuid -from unittest import skipUnless from django.core import serializers from django.core.serializers import SerializerDoesNotExist @@ -39,11 +38,6 @@ TextData, TimeData, UniqueAnchor, UUIDData, ) -try: - import yaml -except ImportError: - yaml = None - # A set of functions that can be used to recreate # test data objects of various kinds. # The save method is a raw base model save, to make @@ -433,12 +427,6 @@ def test_json_deserializer_exception(self): for obj in serializers.deserialize("json", """[{"pk":1}"""): pass - @skipUnless(yaml, "PyYAML not installed") - def test_yaml_deserializer_exception(self): - with self.assertRaises(DeserializationError): - for obj in serializers.deserialize("yaml", "{"): - pass - def test_serialize_proxy_model(self): BaseModel.objects.create(parent_data=1) base_objects = BaseModel.objects.all() From 4eeec2031e2785f22a7004d890168f1d0e3bbc71 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 Sep 2015 10:51:59 +0200 Subject: [PATCH 016/756] [1.9.x] Separated XML serialization tests Backport of d3cfdfb508 from master. --- tests/serializers/test_xml.py | 118 +++++++++++++++++++++++++++++ tests/serializers/tests.py | 98 ------------------------ tests/serializers_regress/tests.py | 17 +---- 3 files changed, 119 insertions(+), 114 deletions(-) create mode 100644 tests/serializers/test_xml.py diff --git a/tests/serializers/test_xml.py b/tests/serializers/test_xml.py new file mode 100644 index 000000000000..e516444c9571 --- /dev/null +++ b/tests/serializers/test_xml.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from xml.dom import minidom + +from django.core import serializers +from django.core.serializers.xml_serializer import DTDForbidden +from django.test import TestCase, TransactionTestCase +from django.utils import six + +from .tests import SerializersTestBase, SerializersTransactionTestBase + + +class XmlSerializerTestCase(SerializersTestBase, TestCase): + serializer_name = "xml" + pkless_str = """ + + + Reference + + + Non-fiction + +""" + mapping_ordering_str = """ + + + %(author_pk)s + Poker has no place on ESPN + 2006-06-16T11:00:00 + + + +""" # NOQA + + @staticmethod + def _comparison_value(value): + # The XML serializer handles everything as strings, so comparisons + # need to be performed on the stringified value + return six.text_type(value) + + @staticmethod + def _validate_output(serial_str): + try: + minidom.parseString(serial_str) + except Exception: + return False + else: + return True + + @staticmethod + def _get_pk_values(serial_str): + ret_list = [] + dom = minidom.parseString(serial_str) + fields = dom.getElementsByTagName("object") + for field in fields: + ret_list.append(field.getAttribute("pk")) + return ret_list + + @staticmethod + def _get_field_values(serial_str, field_name): + ret_list = [] + dom = minidom.parseString(serial_str) + fields = dom.getElementsByTagName("field") + for field in fields: + if field.getAttribute("name") == field_name: + temp = [] + for child in field.childNodes: + temp.append(child.nodeValue) + ret_list.append("".join(temp)) + return ret_list + + def test_control_char_failure(self): + """ + Serializing control characters with XML should fail as those characters + are not supported in the XML 1.0 standard (except HT, LF, CR). + """ + self.a1.headline = "This contains \u0001 control \u0011 chars" + msg = "Article.headline (pk:%s) contains unserializable characters" % self.a1.pk + with self.assertRaisesMessage(ValueError, msg): + serializers.serialize(self.serializer_name, [self.a1]) + self.a1.headline = "HT \u0009, LF \u000A, and CR \u000D are allowed" + self.assertIn( + "HT \t, LF \n, and CR \r are allowed", + serializers.serialize(self.serializer_name, [self.a1]) + ) + + def test_no_dtd(self): + """ + The XML deserializer shouldn't allow a DTD. + + This is the most straightforward way to prevent all entity definitions + and avoid both external entities and entity-expansion attacks. + """ + xml = '' + with self.assertRaises(DTDForbidden): + next(serializers.deserialize('xml', xml)) + + +class XmlSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): + serializer_name = "xml" + fwd_ref_str = """ + + + 1 + Forward references pose no problem + 2006-06-16T15:00:00 + + + + + + + Agnes + + + Reference +""" diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py index f92f0775d6e3..c288bf7ab89c 100644 --- a/tests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -4,7 +4,6 @@ import json import re from datetime import datetime -from xml.dom import minidom from django.core import serializers from django.core.serializers.base import ProgressBar @@ -14,7 +13,6 @@ skipUnlessDBFeature, ) from django.test.utils import Approximate -from django.utils import six from django.utils.six import StringIO from .models import ( @@ -324,102 +322,6 @@ def test_forward_refs(self): self.assertEqual(art_obj.author.name, "Agnes") -class XmlSerializerTestCase(SerializersTestBase, TestCase): - serializer_name = "xml" - pkless_str = """ - - - Reference - - - Non-fiction - -""" - mapping_ordering_str = """ - - - %(author_pk)s - Poker has no place on ESPN - 2006-06-16T11:00:00 - - - -""" # NOQA - - @staticmethod - def _comparison_value(value): - # The XML serializer handles everything as strings, so comparisons - # need to be performed on the stringified value - return six.text_type(value) - - @staticmethod - def _validate_output(serial_str): - try: - minidom.parseString(serial_str) - except Exception: - return False - else: - return True - - @staticmethod - def _get_pk_values(serial_str): - ret_list = [] - dom = minidom.parseString(serial_str) - fields = dom.getElementsByTagName("object") - for field in fields: - ret_list.append(field.getAttribute("pk")) - return ret_list - - @staticmethod - def _get_field_values(serial_str, field_name): - ret_list = [] - dom = minidom.parseString(serial_str) - fields = dom.getElementsByTagName("field") - for field in fields: - if field.getAttribute("name") == field_name: - temp = [] - for child in field.childNodes: - temp.append(child.nodeValue) - ret_list.append("".join(temp)) - return ret_list - - def test_control_char_failure(self): - """ - Serializing control characters with XML should fail as those characters - are not supported in the XML 1.0 standard (except HT, LF, CR). - """ - self.a1.headline = "This contains \u0001 control \u0011 chars" - msg = "Article.headline (pk:%s) contains unserializable characters" % self.a1.pk - with self.assertRaisesMessage(ValueError, msg): - serializers.serialize(self.serializer_name, [self.a1]) - self.a1.headline = "HT \u0009, LF \u000A, and CR \u000D are allowed" - self.assertIn( - "HT \t, LF \n, and CR \r are allowed", - serializers.serialize(self.serializer_name, [self.a1]) - ) - - -class XmlSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): - serializer_name = "xml" - fwd_ref_str = """ - - - 1 - Forward references pose no problem - 2006-06-16T15:00:00 - - - - - - - Agnes - - - Reference -""" - - class JsonSerializerTestCase(SerializersTestBase, TestCase): serializer_name = "json" pkless_str = """[ diff --git a/tests/serializers_regress/tests.py b/tests/serializers_regress/tests.py index 3da6ddafb424..fd05eedc43bb 100644 --- a/tests/serializers_regress/tests.py +++ b/tests/serializers_regress/tests.py @@ -15,10 +15,9 @@ from django.core import serializers from django.core.serializers import SerializerDoesNotExist from django.core.serializers.base import DeserializationError -from django.core.serializers.xml_serializer import DTDForbidden from django.db import connection, models from django.http import HttpResponse -from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature +from django.test import TestCase, skipUnlessDBFeature from django.utils import six from django.utils.functional import curry @@ -570,17 +569,3 @@ def naturalKeyTest(format, self): setattr(SerializerTests, 'test_' + format + '_serializer_natural_keys', curry(naturalKeyTest, format)) if format != 'python': setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format)) - - -class XmlDeserializerSecurityTests(SimpleTestCase): - - def test_no_dtd(self): - """ - The XML deserializer shouldn't allow a DTD. - - This is the most straightforward way to prevent all entity definitions - and avoid both external entities and entity-expansion attacks. - """ - xml = '' - with self.assertRaises(DTDForbidden): - next(serializers.deserialize('xml', xml)) From d31c38cddd986a9df750a2332737d7a14da1a80d Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 Sep 2015 11:02:09 +0200 Subject: [PATCH 017/756] [1.9.x] Separated JSON serialization tests Backport of 6dc7af3e01 from master. --- tests/serializers/test_json.py | 273 +++++++++++++++++++++++++++++ tests/serializers/tests.py | 261 +-------------------------- tests/serializers_regress/tests.py | 6 - 3 files changed, 274 insertions(+), 266 deletions(-) create mode 100644 tests/serializers/test_json.py diff --git a/tests/serializers/test_json.py b/tests/serializers/test_json.py new file mode 100644 index 000000000000..4239dcef8dcc --- /dev/null +++ b/tests/serializers/test_json.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import json +import re + +from django.core import serializers +from django.core.serializers.base import DeserializationError +from django.test import TestCase, TransactionTestCase + +from .models import Score +from .tests import SerializersTestBase, SerializersTransactionTestBase + + +class JsonSerializerTestCase(SerializersTestBase, TestCase): + serializer_name = "json" + pkless_str = """[ + { + "pk": null, + "model": "serializers.category", + "fields": {"name": "Reference"} + }, { + "model": "serializers.category", + "fields": {"name": "Non-fiction"} + }]""" + mapping_ordering_str = """[ +{ + "model": "serializers.article", + "pk": %(article_pk)s, + "fields": { + "author": %(author_pk)s, + "headline": "Poker has no place on ESPN", + "pub_date": "2006-06-16T11:00:00", + "categories": [ + %(first_category_pk)s, + %(second_category_pk)s + ], + "meta_data": [] + } +} +] +""" + + @staticmethod + def _validate_output(serial_str): + try: + json.loads(serial_str) + except Exception: + return False + else: + return True + + @staticmethod + def _get_pk_values(serial_str): + ret_list = [] + serial_list = json.loads(serial_str) + for obj_dict in serial_list: + ret_list.append(obj_dict["pk"]) + return ret_list + + @staticmethod + def _get_field_values(serial_str, field_name): + ret_list = [] + serial_list = json.loads(serial_str) + for obj_dict in serial_list: + if field_name in obj_dict["fields"]: + ret_list.append(obj_dict["fields"][field_name]) + return ret_list + + def test_indentation_whitespace(self): + Score.objects.create(score=5.0) + Score.objects.create(score=6.0) + qset = Score.objects.all() + + s = serializers.json.Serializer() + json_data = s.serialize(qset, indent=2) + for line in json_data.splitlines(): + if re.search(r'.+,\s*$', line): + self.assertEqual(line, line.rstrip()) + + def test_json_deserializer_exception(self): + with self.assertRaises(DeserializationError): + for obj in serializers.deserialize("json", """[{"pk":1}"""): + pass + + def test_helpful_error_message_invalid_pk(self): + """ + If there is an invalid primary key, the error message should contain + the model associated with it. + """ + test_string = """[{ + "pk": "badpk", + "model": "serializers.player", + "fields": { + "name": "Bob", + "rank": 1, + "team": "Team" + } + }]""" + with self.assertRaisesMessage(DeserializationError, "(serializers.player:pk=badpk)"): + list(serializers.deserialize('json', test_string)) + + def test_helpful_error_message_invalid_field(self): + """ + If there is an invalid field value, the error message should contain + the model associated with it. + """ + test_string = """[{ + "pk": "1", + "model": "serializers.player", + "fields": { + "name": "Bob", + "rank": "invalidint", + "team": "Team" + } + }]""" + expected = "(serializers.player:pk=1) field_value was 'invalidint'" + with self.assertRaisesMessage(DeserializationError, expected): + list(serializers.deserialize('json', test_string)) + + def test_helpful_error_message_for_foreign_keys(self): + """ + Invalid foreign keys with a natural key should throw a helpful error + message, such as what the failing key is. + """ + test_string = """[{ + "pk": 1, + "model": "serializers.category", + "fields": { + "name": "Unknown foreign key", + "meta_data": [ + "doesnotexist", + "metadata" + ] + } + }]""" + key = ["doesnotexist", "metadata"] + expected = "(serializers.category:pk=1) field_value was '%r'" % key + with self.assertRaisesMessage(DeserializationError, expected): + list(serializers.deserialize('json', test_string)) + + def test_helpful_error_message_for_many2many_non_natural(self): + """ + Invalid many-to-many keys should throw a helpful error message. + """ + test_string = """[{ + "pk": 1, + "model": "serializers.article", + "fields": { + "author": 1, + "headline": "Unknown many to many", + "pub_date": "2014-09-15T10:35:00", + "categories": [1, "doesnotexist"] + } + }, { + "pk": 1, + "model": "serializers.author", + "fields": { + "name": "Agnes" + } + }, { + "pk": 1, + "model": "serializers.category", + "fields": { + "name": "Reference" + } + }]""" + expected = "(serializers.article:pk=1) field_value was 'doesnotexist'" + with self.assertRaisesMessage(DeserializationError, expected): + list(serializers.deserialize('json', test_string)) + + def test_helpful_error_message_for_many2many_natural1(self): + """ + Invalid many-to-many keys should throw a helpful error message. + This tests the code path where one of a list of natural keys is invalid. + """ + test_string = """[{ + "pk": 1, + "model": "serializers.categorymetadata", + "fields": { + "kind": "author", + "name": "meta1", + "value": "Agnes" + } + }, { + "pk": 1, + "model": "serializers.article", + "fields": { + "author": 1, + "headline": "Unknown many to many", + "pub_date": "2014-09-15T10:35:00", + "meta_data": [ + ["author", "meta1"], + ["doesnotexist", "meta1"], + ["author", "meta1"] + ] + } + }, { + "pk": 1, + "model": "serializers.author", + "fields": { + "name": "Agnes" + } + }]""" + key = ["doesnotexist", "meta1"] + expected = "(serializers.article:pk=1) field_value was '%r'" % key + with self.assertRaisesMessage(DeserializationError, expected): + for obj in serializers.deserialize('json', test_string): + obj.save() + + def test_helpful_error_message_for_many2many_natural2(self): + """ + Invalid many-to-many keys should throw a helpful error message. This + tests the code path where a natural many-to-many key has only a single + value. + """ + test_string = """[{ + "pk": 1, + "model": "serializers.article", + "fields": { + "author": 1, + "headline": "Unknown many to many", + "pub_date": "2014-09-15T10:35:00", + "meta_data": [1, "doesnotexist"] + } + }, { + "pk": 1, + "model": "serializers.categorymetadata", + "fields": { + "kind": "author", + "name": "meta1", + "value": "Agnes" + } + }, { + "pk": 1, + "model": "serializers.author", + "fields": { + "name": "Agnes" + } + }]""" + expected = "(serializers.article:pk=1) field_value was 'doesnotexist'" + with self.assertRaisesMessage(DeserializationError, expected): + for obj in serializers.deserialize('json', test_string, ignore=False): + obj.save() + + +class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): + serializer_name = "json" + fwd_ref_str = """[ + { + "pk": 1, + "model": "serializers.article", + "fields": { + "headline": "Forward references pose no problem", + "pub_date": "2006-06-16T15:00:00", + "categories": [1], + "author": 1 + } + }, + { + "pk": 1, + "model": "serializers.category", + "fields": { + "name": "Reference" + } + }, + { + "pk": 1, + "model": "serializers.author", + "fields": { + "name": "Agnes" + } + }]""" diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py index c288bf7ab89c..ffd573c0cce1 100644 --- a/tests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -1,16 +1,13 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -import json -import re from datetime import datetime from django.core import serializers from django.core.serializers.base import ProgressBar from django.db import connection, transaction from django.test import ( - SimpleTestCase, TestCase, TransactionTestCase, mock, override_settings, - skipUnlessDBFeature, + SimpleTestCase, mock, override_settings, skipUnlessDBFeature, ) from django.test.utils import Approximate from django.utils.six import StringIO @@ -320,259 +317,3 @@ def test_forward_refs(self): art_obj = Article.objects.all()[0] self.assertEqual(art_obj.categories.all().count(), 1) self.assertEqual(art_obj.author.name, "Agnes") - - -class JsonSerializerTestCase(SerializersTestBase, TestCase): - serializer_name = "json" - pkless_str = """[ - { - "pk": null, - "model": "serializers.category", - "fields": {"name": "Reference"} - }, { - "model": "serializers.category", - "fields": {"name": "Non-fiction"} - }]""" - mapping_ordering_str = """[ -{ - "model": "serializers.article", - "pk": %(article_pk)s, - "fields": { - "author": %(author_pk)s, - "headline": "Poker has no place on ESPN", - "pub_date": "2006-06-16T11:00:00", - "categories": [ - %(first_category_pk)s, - %(second_category_pk)s - ], - "meta_data": [] - } -} -] -""" - - @staticmethod - def _validate_output(serial_str): - try: - json.loads(serial_str) - except Exception: - return False - else: - return True - - @staticmethod - def _get_pk_values(serial_str): - ret_list = [] - serial_list = json.loads(serial_str) - for obj_dict in serial_list: - ret_list.append(obj_dict["pk"]) - return ret_list - - @staticmethod - def _get_field_values(serial_str, field_name): - ret_list = [] - serial_list = json.loads(serial_str) - for obj_dict in serial_list: - if field_name in obj_dict["fields"]: - ret_list.append(obj_dict["fields"][field_name]) - return ret_list - - def test_indentation_whitespace(self): - Score.objects.create(score=5.0) - Score.objects.create(score=6.0) - qset = Score.objects.all() - - s = serializers.json.Serializer() - json_data = s.serialize(qset, indent=2) - for line in json_data.splitlines(): - if re.search(r'.+,\s*$', line): - self.assertEqual(line, line.rstrip()) - - def test_helpful_error_message_invalid_pk(self): - """ - If there is an invalid primary key, the error message should contain - the model associated with it. - """ - test_string = """[{ - "pk": "badpk", - "model": "serializers.player", - "fields": { - "name": "Bob", - "rank": 1, - "team": "Team" - } - }]""" - with self.assertRaisesMessage(serializers.base.DeserializationError, "(serializers.player:pk=badpk)"): - list(serializers.deserialize('json', test_string)) - - def test_helpful_error_message_invalid_field(self): - """ - If there is an invalid field value, the error message should contain - the model associated with it. - """ - test_string = """[{ - "pk": "1", - "model": "serializers.player", - "fields": { - "name": "Bob", - "rank": "invalidint", - "team": "Team" - } - }]""" - expected = "(serializers.player:pk=1) field_value was 'invalidint'" - with self.assertRaisesMessage(serializers.base.DeserializationError, expected): - list(serializers.deserialize('json', test_string)) - - def test_helpful_error_message_for_foreign_keys(self): - """ - Invalid foreign keys with a natural key should throw a helpful error - message, such as what the failing key is. - """ - test_string = """[{ - "pk": 1, - "model": "serializers.category", - "fields": { - "name": "Unknown foreign key", - "meta_data": [ - "doesnotexist", - "metadata" - ] - } - }]""" - key = ["doesnotexist", "metadata"] - expected = "(serializers.category:pk=1) field_value was '%r'" % key - with self.assertRaisesMessage(serializers.base.DeserializationError, expected): - list(serializers.deserialize('json', test_string)) - - def test_helpful_error_message_for_many2many_non_natural(self): - """ - Invalid many-to-many keys should throw a helpful error message. - """ - test_string = """[{ - "pk": 1, - "model": "serializers.article", - "fields": { - "author": 1, - "headline": "Unknown many to many", - "pub_date": "2014-09-15T10:35:00", - "categories": [1, "doesnotexist"] - } - }, { - "pk": 1, - "model": "serializers.author", - "fields": { - "name": "Agnes" - } - }, { - "pk": 1, - "model": "serializers.category", - "fields": { - "name": "Reference" - } - }]""" - expected = "(serializers.article:pk=1) field_value was 'doesnotexist'" - with self.assertRaisesMessage(serializers.base.DeserializationError, expected): - list(serializers.deserialize('json', test_string)) - - def test_helpful_error_message_for_many2many_natural1(self): - """ - Invalid many-to-many keys should throw a helpful error message. - This tests the code path where one of a list of natural keys is invalid. - """ - test_string = """[{ - "pk": 1, - "model": "serializers.categorymetadata", - "fields": { - "kind": "author", - "name": "meta1", - "value": "Agnes" - } - }, { - "pk": 1, - "model": "serializers.article", - "fields": { - "author": 1, - "headline": "Unknown many to many", - "pub_date": "2014-09-15T10:35:00", - "meta_data": [ - ["author", "meta1"], - ["doesnotexist", "meta1"], - ["author", "meta1"] - ] - } - }, { - "pk": 1, - "model": "serializers.author", - "fields": { - "name": "Agnes" - } - }]""" - key = ["doesnotexist", "meta1"] - expected = "(serializers.article:pk=1) field_value was '%r'" % key - with self.assertRaisesMessage(serializers.base.DeserializationError, expected): - for obj in serializers.deserialize('json', test_string): - obj.save() - - def test_helpful_error_message_for_many2many_natural2(self): - """ - Invalid many-to-many keys should throw a helpful error message. This - tests the code path where a natural many-to-many key has only a single - value. - """ - test_string = """[{ - "pk": 1, - "model": "serializers.article", - "fields": { - "author": 1, - "headline": "Unknown many to many", - "pub_date": "2014-09-15T10:35:00", - "meta_data": [1, "doesnotexist"] - } - }, { - "pk": 1, - "model": "serializers.categorymetadata", - "fields": { - "kind": "author", - "name": "meta1", - "value": "Agnes" - } - }, { - "pk": 1, - "model": "serializers.author", - "fields": { - "name": "Agnes" - } - }]""" - expected = "(serializers.article:pk=1) field_value was 'doesnotexist'" - with self.assertRaisesMessage(serializers.base.DeserializationError, expected): - for obj in serializers.deserialize('json', test_string, ignore=False): - obj.save() - - -class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): - serializer_name = "json" - fwd_ref_str = """[ - { - "pk": 1, - "model": "serializers.article", - "fields": { - "headline": "Forward references pose no problem", - "pub_date": "2006-06-16T15:00:00", - "categories": [1], - "author": 1 - } - }, - { - "pk": 1, - "model": "serializers.category", - "fields": { - "name": "Reference" - } - }, - { - "pk": 1, - "model": "serializers.author", - "fields": { - "name": "Agnes" - } - }]""" diff --git a/tests/serializers_regress/tests.py b/tests/serializers_regress/tests.py index fd05eedc43bb..e8a7aa359bc1 100644 --- a/tests/serializers_regress/tests.py +++ b/tests/serializers_regress/tests.py @@ -14,7 +14,6 @@ from django.core import serializers from django.core.serializers import SerializerDoesNotExist -from django.core.serializers.base import DeserializationError from django.db import connection, models from django.http import HttpResponse from django.test import TestCase, skipUnlessDBFeature @@ -421,11 +420,6 @@ def test_get_unknown_deserializer(self): with self.assertRaises(SerializerDoesNotExist): serializers.get_deserializer("nonsense") - def test_json_deserializer_exception(self): - with self.assertRaises(DeserializationError): - for obj in serializers.deserialize("json", """[{"pk":1}"""): - pass - def test_serialize_proxy_model(self): BaseModel.objects.create(parent_data=1) base_objects = BaseModel.objects.all() From a74728b36d8e1a83b3dffb85ea2601b7ec3bcc2d Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 Sep 2015 11:53:20 +0200 Subject: [PATCH 018/756] [1.9.x] Separated natural key serialization tests Backport of d59d3caf3 from master. --- tests/serializers/models.py | 21 +++++++++ tests/serializers/test_natural.py | 70 ++++++++++++++++++++++++++++ tests/serializers/tests.py | 16 +++++++ tests/serializers_regress/models.py | 19 -------- tests/serializers_regress/tests.py | 72 +---------------------------- 5 files changed, 109 insertions(+), 89 deletions(-) create mode 100644 tests/serializers/test_natural.py diff --git a/tests/serializers/models.py b/tests/serializers/models.py index 6532ea953e42..c3663b533909 100644 --- a/tests/serializers/models.py +++ b/tests/serializers/models.py @@ -157,3 +157,24 @@ class Player(models.Model): def __str__(self): return '%s (%d) playing for %s' % (self.name, self.rank, self.team.to_string()) + + +# ******** Models for test_natural.py *********** + +class NaturalKeyAnchorManager(models.Manager): + def get_by_natural_key(self, data): + return self.get(data=data) + + +class NaturalKeyAnchor(models.Model): + objects = NaturalKeyAnchorManager() + + data = models.CharField(max_length=100, unique=True) + title = models.CharField(max_length=100, null=True) + + def natural_key(self): + return (self.data,) + + +class FKDataNaturalKey(models.Model): + data = models.ForeignKey(NaturalKeyAnchor, models.SET_NULL, null=True) diff --git a/tests/serializers/test_natural.py b/tests/serializers/test_natural.py new file mode 100644 index 000000000000..6682e5cdd8cd --- /dev/null +++ b/tests/serializers/test_natural.py @@ -0,0 +1,70 @@ +from __future__ import unicode_literals + +from django.core import serializers +from django.db import connection +from django.test import TestCase + +from .models import FKDataNaturalKey, NaturalKeyAnchor +from .tests import register_tests + + +class NaturalKeySerializerTests(TestCase): + pass + + +def natural_key_serializer_test(format, self): + # Create all the objects defined in the test data + with connection.constraint_checks_disabled(): + objects = [ + NaturalKeyAnchor.objects.create(id=1100, data="Natural Key Anghor"), + FKDataNaturalKey.objects.create(id=1101, data_id=1100), + FKDataNaturalKey.objects.create(id=1102, data_id=None), + ] + # Serialize the test database + serialized_data = serializers.serialize(format, objects, indent=2, + use_natural_foreign_keys=True) + + for obj in serializers.deserialize(format, serialized_data): + obj.save() + + # Assert that the deserialized data is the same + # as the original source + for obj in objects: + instance = obj.__class__.objects.get(id=obj.pk) + self.assertEqual(obj.data, instance.data, + "Objects with PK=%d not equal; expected '%s' (%s), got '%s' (%s)" % ( + obj.pk, obj.data, type(obj.data), instance, type(instance.data)) + ) + + +def natural_key_test(format, self): + book1 = {'data': '978-1590597255', 'title': 'The Definitive Guide to ' + 'Django: Web Development Done Right'} + book2 = {'data': '978-1590599969', 'title': 'Practical Django Projects'} + + # Create the books. + adrian = NaturalKeyAnchor.objects.create(**book1) + james = NaturalKeyAnchor.objects.create(**book2) + + # Serialize the books. + string_data = serializers.serialize(format, NaturalKeyAnchor.objects.all(), + indent=2, use_natural_foreign_keys=True, + use_natural_primary_keys=True) + + # Delete one book (to prove that the natural key generation will only + # restore the primary keys of books found in the database via the + # get_natural_key manager method). + james.delete() + + # Deserialize and test. + books = list(serializers.deserialize(format, string_data)) + self.assertEqual(len(books), 2) + self.assertEqual(books[0].object.title, book1['title']) + self.assertEqual(books[0].object.pk, adrian.pk) + self.assertEqual(books[1].object.title, book2['title']) + self.assertEqual(books[1].object.pk, None) + + +# Dynamically register tests for each serializer +register_tests(NaturalKeySerializerTests, 'test_%s_natural_key_serializer', natural_key_serializer_test) +register_tests(NaturalKeySerializerTests, 'test_%s_serializer_natural_keys', natural_key_test) diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py index ffd573c0cce1..2de9ce8796ae 100644 --- a/tests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -10,6 +10,7 @@ SimpleTestCase, mock, override_settings, skipUnlessDBFeature, ) from django.test.utils import Approximate +from django.utils.functional import curry from django.utils.six import StringIO from .models import ( @@ -317,3 +318,18 @@ def test_forward_refs(self): art_obj = Article.objects.all()[0] self.assertEqual(art_obj.categories.all().count(), 1) self.assertEqual(art_obj.author.name, "Agnes") + + +def register_tests(test_class, method_name, test_func, exclude=None): + """ + Dynamically create serializer tests to ensure that all registered + serializers are automatically tested. + """ + formats = [ + f for f in serializers.get_serializer_formats() + if (not isinstance(serializers.get_serializer(f), serializers.BadSerializer) + and not f == 'geojson' + and (exclude is None or f not in exclude)) + ] + for format_ in formats: + setattr(test_class, method_name % format_, curry(test_func, format_)) diff --git a/tests/serializers_regress/models.py b/tests/serializers_regress/models.py index e781cb92c87d..56c5f4f3cd62 100644 --- a/tests/serializers_regress/models.py +++ b/tests/serializers_regress/models.py @@ -130,21 +130,6 @@ class Meta: ordering = ('id',) -class NaturalKeyAnchorManager(models.Manager): - def get_by_natural_key(self, data): - return self.get(data=data) - - -class NaturalKeyAnchor(models.Model): - objects = NaturalKeyAnchorManager() - - data = models.CharField(max_length=100, unique=True) - title = models.CharField(max_length=100, null=True) - - def natural_key(self): - return (self.data,) - - class UniqueAnchor(models.Model): """This is a model that can be used as something for other models to point at""" @@ -156,10 +141,6 @@ class FKData(models.Model): data = models.ForeignKey(Anchor, models.SET_NULL, null=True) -class FKDataNaturalKey(models.Model): - data = models.ForeignKey(NaturalKeyAnchor, models.SET_NULL, null=True) - - class M2MData(models.Model): data = models.ManyToManyField(Anchor) diff --git a/tests/serializers_regress/tests.py b/tests/serializers_regress/tests.py index e8a7aa359bc1..81d4a9de0e1f 100644 --- a/tests/serializers_regress/tests.py +++ b/tests/serializers_regress/tests.py @@ -25,11 +25,11 @@ BooleanData, BooleanPKData, CharData, CharPKData, ComplexModel, DateData, DateTimeData, DecimalData, DecimalPKData, EmailData, EmailPKData, ExplicitInheritBaseModel, FileData, FilePathData, FilePathPKData, FKData, - FKDataNaturalKey, FKDataToField, FKDataToO2O, FKSelfData, FKToUUID, + FKDataToField, FKDataToO2O, FKSelfData, FKToUUID, FloatData, FloatPKData, GenericData, GenericIPAddressData, GenericIPAddressPKData, InheritAbstractModel, InheritBaseModel, IntegerData, IntegerPKData, Intermediate, LengthModel, M2MData, - M2MIntermediateData, M2MSelfData, ModifyingSaveData, NaturalKeyAnchor, + M2MIntermediateData, M2MSelfData, ModifyingSaveData, NullBooleanData, O2OData, PositiveIntegerData, PositiveIntegerPKData, PositiveSmallIntegerData, PositiveSmallIntegerPKData, ProxyBaseModel, ProxyProxyBaseModel, SlugData, SlugPKData, SmallData, SmallPKData, Tag, @@ -367,11 +367,6 @@ def inherited_compare(testcase, pk, klass, data): (data_obj, 1005, LengthModel, 1), ] -natural_key_test_data = [ - (data_obj, 1100, NaturalKeyAnchor, "Natural Key Anghor"), - (fk_obj, 1101, FKDataNaturalKey, 1100), - (fk_obj, 1102, FKDataNaturalKey, None), -] # Because Oracle treats the empty string as NULL, Oracle is expected to fail # when field.empty_strings_allowed is True and the value is None; skip these @@ -391,9 +386,6 @@ def inherited_compare(testcase, pk, klass, data): (fk_obj, 465, FKData, 0), ]) -# Dynamically create serializer tests to ensure that all -# registered serializers are automatically tested. - @skipUnlessDBFeature('can_defer_constraint_checks') class SerializerTests(TestCase): @@ -465,36 +457,6 @@ def serializerTest(format, self): self.assertEqual(count, klass.objects.count()) -def naturalKeySerializerTest(format, self): - # Create all the objects defined in the test data - objects = [] - instance_count = {} - for (func, pk, klass, datum) in natural_key_test_data: - with connection.constraint_checks_disabled(): - objects.extend(func[0](pk, klass, datum)) - - # Get a count of the number of objects created for each class - for klass in instance_count: - instance_count[klass] = klass.objects.count() - - # Serialize the test database - serialized_data = serializers.serialize(format, objects, indent=2, - use_natural_foreign_keys=True) - - for obj in serializers.deserialize(format, serialized_data): - obj.save() - - # Assert that the deserialized data is the same - # as the original source - for (func, pk, klass, datum) in natural_key_test_data: - func[1](self, pk, klass, datum) - - # Assert that the number of objects deserialized is the - # same as the number that was serialized. - for klass, count in instance_count.items(): - self.assertEqual(count, klass.objects.count()) - - def fieldsTest(format, self): obj = ComplexModel(field1='first', field2='second', field3='third') obj.save_base(raw=True) @@ -527,39 +489,9 @@ def streamTest(format, self): self.assertEqual(string_data, stream.content.decode('utf-8')) -def naturalKeyTest(format, self): - book1 = {'data': '978-1590597255', 'title': 'The Definitive Guide to ' - 'Django: Web Development Done Right'} - book2 = {'data': '978-1590599969', 'title': 'Practical Django Projects'} - - # Create the books. - adrian = NaturalKeyAnchor.objects.create(**book1) - james = NaturalKeyAnchor.objects.create(**book2) - - # Serialize the books. - string_data = serializers.serialize(format, NaturalKeyAnchor.objects.all(), - indent=2, use_natural_foreign_keys=True, - use_natural_primary_keys=True) - - # Delete one book (to prove that the natural key generation will only - # restore the primary keys of books found in the database via the - # get_natural_key manager method). - james.delete() - - # Deserialize and test. - books = list(serializers.deserialize(format, string_data)) - self.assertEqual(len(books), 2) - self.assertEqual(books[0].object.title, book1['title']) - self.assertEqual(books[0].object.pk, adrian.pk) - self.assertEqual(books[1].object.title, book2['title']) - self.assertEqual(books[1].object.pk, None) - - for format in [f for f in serializers.get_serializer_formats() if not isinstance(serializers.get_serializer(f), serializers.BadSerializer) and not f == 'geojson']: setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format)) - setattr(SerializerTests, 'test_' + format + '_natural_key_serializer', curry(naturalKeySerializerTest, format)) setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format)) - setattr(SerializerTests, 'test_' + format + '_serializer_natural_keys', curry(naturalKeyTest, format)) if format != 'python': setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format)) From b58f88f96dc4813578e78ef91af10dac569e216f Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 Sep 2015 12:46:00 +0200 Subject: [PATCH 019/756] [1.9.x] Moved more serializers_regress to serializers tests Backport of ddd6a530e from master. --- tests/serializers/models.py | 20 +++++++ tests/serializers/tests.py | 75 +++++++++++++++++++++++++- tests/serializers_regress/models.py | 15 ------ tests/serializers_regress/tests.py | 83 +++-------------------------- 4 files changed, 99 insertions(+), 94 deletions(-) diff --git a/tests/serializers/models.py b/tests/serializers/models.py index c3663b533909..078fa05722aa 100644 --- a/tests/serializers/models.py +++ b/tests/serializers/models.py @@ -159,6 +159,26 @@ def __str__(self): return '%s (%d) playing for %s' % (self.name, self.rank, self.team.to_string()) +class BaseModel(models.Model): + parent_data = models.IntegerField() + + +class ProxyBaseModel(BaseModel): + class Meta: + proxy = True + + +class ProxyProxyBaseModel(ProxyBaseModel): + class Meta: + proxy = True + + +class ComplexModel(models.Model): + field1 = models.CharField(max_length=10) + field2 = models.CharField(max_length=10) + field3 = models.CharField(max_length=10) + + # ******** Models for test_natural.py *********** class NaturalKeyAnchorManager(models.Manager): diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py index 2de9ce8796ae..7dc2513fed1c 100644 --- a/tests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -4,8 +4,10 @@ from datetime import datetime from django.core import serializers +from django.core.serializers import SerializerDoesNotExist from django.core.serializers.base import ProgressBar from django.db import connection, transaction +from django.http import HttpResponse from django.test import ( SimpleTestCase, mock, override_settings, skipUnlessDBFeature, ) @@ -14,8 +16,8 @@ from django.utils.six import StringIO from .models import ( - Actor, Article, Author, AuthorProfile, Category, Movie, Player, Score, - Team, + Actor, Article, Author, AuthorProfile, BaseModel, Category, ComplexModel, + Movie, Player, ProxyBaseModel, ProxyProxyBaseModel, Score, Team, ) @@ -51,6 +53,10 @@ def test_unregister(self): self.assertNotIn('xml', public_formats) self.assertIn('json3', public_formats) + def test_unregister_unknown_serializer(self): + with self.assertRaises(SerializerDoesNotExist): + serializers.unregister_serializer("nonsense") + def test_builtin_serializers(self): "Requesting a list of serializer formats popuates the registry" all_formats = set(serializers.get_serializer_formats()) @@ -65,8 +71,29 @@ def test_builtin_serializers(self): self.assertIn('python', all_formats) self.assertNotIn('python', public_formats) + def test_get_unknown_serializer(self): + """ + #15889: get_serializer('nonsense') raises a SerializerDoesNotExist + """ + with self.assertRaises(SerializerDoesNotExist): + serializers.get_serializer("nonsense") + + with self.assertRaises(KeyError): + serializers.get_serializer("nonsense") + + # SerializerDoesNotExist is instantiated with the nonexistent format + with self.assertRaises(SerializerDoesNotExist) as cm: + serializers.get_serializer("nonsense") + self.assertEqual(cm.exception.args, ("nonsense",)) + + def test_get_unknown_deserializer(self): + with self.assertRaises(SerializerDoesNotExist): + serializers.get_deserializer("nonsense") + class SerializersTestBase(object): + serializer_name = None # Set by subclasses to the serialization format name + @staticmethod def _comparison_value(value): return value @@ -108,6 +135,38 @@ def test_serializer_roundtrip(self): models = list(serializers.deserialize(self.serializer_name, serial_str)) self.assertEqual(len(models), 2) + def test_serialize_to_stream(self): + obj = ComplexModel(field1='first', field2='second', field3='third') + obj.save_base(raw=True) + + # Serialize the test database to a stream + for stream in (StringIO(), HttpResponse()): + serializers.serialize(self.serializer_name, [obj], indent=2, stream=stream) + + # Serialize normally for a comparison + string_data = serializers.serialize(self.serializer_name, [obj], indent=2) + + # Check that the two are the same + if isinstance(stream, StringIO): + self.assertEqual(string_data, stream.getvalue()) + else: + self.assertEqual(string_data, stream.content.decode('utf-8')) + + def test_serialize_specific_fields(self): + obj = ComplexModel(field1='first', field2='second', field3='third') + obj.save_base(raw=True) + + # Serialize then deserialize the test database + serialized_data = serializers.serialize( + self.serializer_name, [obj], indent=2, fields=('field1', 'field3') + ) + result = next(serializers.deserialize(self.serializer_name, serialized_data)) + + # Check that the deserialized object contains data in only the serialized fields. + self.assertEqual(result.object.field1, 'first') + self.assertEqual(result.object.field2, '') + self.assertEqual(result.object.field3, 'third') + def test_altering_serialized_output(self): """ Tests the ability to create new objects by @@ -294,6 +353,18 @@ def test_deserialize_force_insert(self): deserial_obj.save(force_insert=False) mock_model.save_base.assert_called_with(deserial_obj.object, raw=True, using=None, force_insert=False) + @skipUnlessDBFeature('can_defer_constraint_checks') + def test_serialize_proxy_model(self): + BaseModel.objects.create(parent_data=1) + base_objects = BaseModel.objects.all() + proxy_objects = ProxyBaseModel.objects.all() + proxy_proxy_objects = ProxyProxyBaseModel.objects.all() + base_data = serializers.serialize("json", base_objects) + proxy_data = serializers.serialize("json", proxy_objects) + proxy_proxy_data = serializers.serialize("json", proxy_proxy_objects) + self.assertEqual(base_data, proxy_data.replace('proxy', '')) + self.assertEqual(base_data, proxy_proxy_data.replace('proxy', '')) + class SerializersTransactionTestBase(object): diff --git a/tests/serializers_regress/models.py b/tests/serializers_regress/models.py index 56c5f4f3cd62..fb8aa9f4112e 100644 --- a/tests/serializers_regress/models.py +++ b/tests/serializers_regress/models.py @@ -261,11 +261,6 @@ class FKToUUID(models.Model): data = models.ForeignKey(UUIDData, models.CASCADE) -class ComplexModel(models.Model): - field1 = models.CharField(max_length=10) - field2 = models.CharField(max_length=10) - field3 = models.CharField(max_length=10) - # Tests for handling fields with pre_save functions, or # models with save functions that modify data @@ -314,16 +309,6 @@ class ExplicitInheritBaseModel(BaseModel): child_data = models.IntegerField() -class ProxyBaseModel(BaseModel): - class Meta: - proxy = True - - -class ProxyProxyBaseModel(ProxyBaseModel): - class Meta: - proxy = True - - class LengthModel(models.Model): data = models.IntegerField() diff --git a/tests/serializers_regress/tests.py b/tests/serializers_regress/tests.py index 81d4a9de0e1f..053e952e1584 100644 --- a/tests/serializers_regress/tests.py +++ b/tests/serializers_regress/tests.py @@ -13,16 +13,15 @@ import uuid from django.core import serializers -from django.core.serializers import SerializerDoesNotExist from django.db import connection, models from django.http import HttpResponse -from django.test import TestCase, skipUnlessDBFeature +from django.test import TestCase from django.utils import six from django.utils.functional import curry from .models import ( - Anchor, AutoNowDateTimeData, BaseModel, BigIntegerData, BinaryData, - BooleanData, BooleanPKData, CharData, CharPKData, ComplexModel, DateData, + Anchor, AutoNowDateTimeData, BigIntegerData, BinaryData, + BooleanData, BooleanPKData, CharData, CharPKData, DateData, DateTimeData, DecimalData, DecimalPKData, EmailData, EmailPKData, ExplicitInheritBaseModel, FileData, FilePathData, FilePathPKData, FKData, FKDataToField, FKDataToO2O, FKSelfData, FKToUUID, @@ -31,9 +30,8 @@ IntegerData, IntegerPKData, Intermediate, LengthModel, M2MData, M2MIntermediateData, M2MSelfData, ModifyingSaveData, NullBooleanData, O2OData, PositiveIntegerData, PositiveIntegerPKData, - PositiveSmallIntegerData, PositiveSmallIntegerPKData, ProxyBaseModel, - ProxyProxyBaseModel, SlugData, SlugPKData, SmallData, SmallPKData, Tag, - TextData, TimeData, UniqueAnchor, UUIDData, + PositiveSmallIntegerData, PositiveSmallIntegerPKData, SlugData, SlugPKData, + SmallData, SmallPKData, Tag, TextData, TimeData, UniqueAnchor, UUIDData, ) # A set of functions that can be used to recreate @@ -387,42 +385,8 @@ def inherited_compare(testcase, pk, klass, data): ]) -@skipUnlessDBFeature('can_defer_constraint_checks') class SerializerTests(TestCase): - def test_get_unknown_serializer(self): - """ - #15889: get_serializer('nonsense') raises a SerializerDoesNotExist - """ - with self.assertRaises(SerializerDoesNotExist): - serializers.get_serializer("nonsense") - - with self.assertRaises(KeyError): - serializers.get_serializer("nonsense") - - # SerializerDoesNotExist is instantiated with the nonexistent format - with self.assertRaises(SerializerDoesNotExist) as cm: - serializers.get_serializer("nonsense") - self.assertEqual(cm.exception.args, ("nonsense",)) - - def test_unregister_unknown_serializer(self): - with self.assertRaises(SerializerDoesNotExist): - serializers.unregister_serializer("nonsense") - - def test_get_unknown_deserializer(self): - with self.assertRaises(SerializerDoesNotExist): - serializers.get_deserializer("nonsense") - - def test_serialize_proxy_model(self): - BaseModel.objects.create(parent_data=1) - base_objects = BaseModel.objects.all() - proxy_objects = ProxyBaseModel.objects.all() - proxy_proxy_objects = ProxyProxyBaseModel.objects.all() - base_data = serializers.serialize("json", base_objects) - proxy_data = serializers.serialize("json", proxy_objects) - proxy_proxy_data = serializers.serialize("json", proxy_proxy_objects) - self.assertEqual(base_data, proxy_data.replace('proxy', '')) - self.assertEqual(base_data, proxy_proxy_data.replace('proxy', '')) - + pass def serializerTest(format, self): @@ -457,41 +421,6 @@ def serializerTest(format, self): self.assertEqual(count, klass.objects.count()) -def fieldsTest(format, self): - obj = ComplexModel(field1='first', field2='second', field3='third') - obj.save_base(raw=True) - - # Serialize then deserialize the test database - serialized_data = serializers.serialize(format, [obj], indent=2, fields=('field1', 'field3')) - result = next(serializers.deserialize(format, serialized_data)) - - # Check that the deserialized object contains data in only the serialized fields. - self.assertEqual(result.object.field1, 'first') - self.assertEqual(result.object.field2, '') - self.assertEqual(result.object.field3, 'third') - - -def streamTest(format, self): - obj = ComplexModel(field1='first', field2='second', field3='third') - obj.save_base(raw=True) - - # Serialize the test database to a stream - for stream in (six.StringIO(), HttpResponse()): - serializers.serialize(format, [obj], indent=2, stream=stream) - - # Serialize normally for a comparison - string_data = serializers.serialize(format, [obj], indent=2) - - # Check that the two are the same - if isinstance(stream, six.StringIO): - self.assertEqual(string_data, stream.getvalue()) - else: - self.assertEqual(string_data, stream.content.decode('utf-8')) - - for format in [f for f in serializers.get_serializer_formats() if not isinstance(serializers.get_serializer(f), serializers.BadSerializer) and not f == 'geojson']: setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format)) - setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format)) - if format != 'python': - setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format)) From df87de345d348bb43bec76b274e90c003ea5ed8f Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 Sep 2015 14:14:23 +0200 Subject: [PATCH 020/756] [1.9.x] Moved remaining of serializers_regress to serializers tests Backport of 4908222ac from master. --- tests/serializers/models.py | 310 +++++++++++++++++ .../tests.py => serializers/test_data.py} | 28 +- tests/serializers_regress/__init__.py | 0 tests/serializers_regress/models.py | 316 ------------------ 4 files changed, 323 insertions(+), 331 deletions(-) rename tests/{serializers_regress/tests.py => serializers/test_data.py} (93%) delete mode 100644 tests/serializers_regress/__init__.py delete mode 100644 tests/serializers_regress/models.py diff --git a/tests/serializers/models.py b/tests/serializers/models.py index 078fa05722aa..023a2e8f8537 100644 --- a/tests/serializers/models.py +++ b/tests/serializers/models.py @@ -9,6 +9,10 @@ from decimal import Decimal +from django.contrib.contenttypes.fields import ( + GenericForeignKey, GenericRelation, +) +from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils import six from django.utils.encoding import python_2_unicode_compatible @@ -198,3 +202,309 @@ def natural_key(self): class FKDataNaturalKey(models.Model): data = models.ForeignKey(NaturalKeyAnchor, models.SET_NULL, null=True) + + +# ******** Models for test_data.py *********** +# The following classes are for testing basic data marshalling, including +# NULL values, where allowed. +# The basic idea is to have a model for each Django data type. + +class BinaryData(models.Model): + data = models.BinaryField(null=True) + + +class BooleanData(models.Model): + data = models.BooleanField(default=False) + + +class CharData(models.Model): + data = models.CharField(max_length=30, null=True) + + +class DateData(models.Model): + data = models.DateField(null=True) + + +class DateTimeData(models.Model): + data = models.DateTimeField(null=True) + + +class DecimalData(models.Model): + data = models.DecimalField(null=True, decimal_places=3, max_digits=5) + + +class EmailData(models.Model): + data = models.EmailField(null=True) + + +class FileData(models.Model): + data = models.FileField(null=True, upload_to='/foo/bar') + + +class FilePathData(models.Model): + data = models.FilePathField(null=True) + + +class FloatData(models.Model): + data = models.FloatField(null=True) + + +class IntegerData(models.Model): + data = models.IntegerField(null=True) + + +class BigIntegerData(models.Model): + data = models.BigIntegerField(null=True) + +# class ImageData(models.Model): +# data = models.ImageField(null=True) + + +class GenericIPAddressData(models.Model): + data = models.GenericIPAddressField(null=True) + + +class NullBooleanData(models.Model): + data = models.NullBooleanField(null=True) + + +class PositiveIntegerData(models.Model): + data = models.PositiveIntegerField(null=True) + + +class PositiveSmallIntegerData(models.Model): + data = models.PositiveSmallIntegerField(null=True) + + +class SlugData(models.Model): + data = models.SlugField(null=True) + + +class SmallData(models.Model): + data = models.SmallIntegerField(null=True) + + +class TextData(models.Model): + data = models.TextField(null=True) + + +class TimeData(models.Model): + data = models.TimeField(null=True) + + +class Tag(models.Model): + """A tag on an item.""" + data = models.SlugField() + content_type = models.ForeignKey(ContentType, models.CASCADE) + object_id = models.PositiveIntegerField() + + content_object = GenericForeignKey() + + class Meta: + ordering = ["data"] + + +class GenericData(models.Model): + data = models.CharField(max_length=30) + + tags = GenericRelation(Tag) + +# The following test classes are all for validation +# of related objects; in particular, forward, backward, +# and self references. + + +class Anchor(models.Model): + """This is a model that can be used as + something for other models to point at""" + + data = models.CharField(max_length=30) + + class Meta: + ordering = ('id',) + + +class UniqueAnchor(models.Model): + """This is a model that can be used as + something for other models to point at""" + + data = models.CharField(unique=True, max_length=30) + + +class FKData(models.Model): + data = models.ForeignKey(Anchor, models.SET_NULL, null=True) + + +class M2MData(models.Model): + data = models.ManyToManyField(Anchor) + + +class O2OData(models.Model): + # One to one field can't be null here, since it is a PK. + data = models.OneToOneField(Anchor, models.CASCADE, primary_key=True) + + +class FKSelfData(models.Model): + data = models.ForeignKey('self', models.CASCADE, null=True) + + +class M2MSelfData(models.Model): + data = models.ManyToManyField('self', symmetrical=False) + + +class FKDataToField(models.Model): + data = models.ForeignKey(UniqueAnchor, models.SET_NULL, null=True, to_field='data') + + +class FKDataToO2O(models.Model): + data = models.ForeignKey(O2OData, models.SET_NULL, null=True) + + +class M2MIntermediateData(models.Model): + data = models.ManyToManyField(Anchor, through='Intermediate') + + +class Intermediate(models.Model): + left = models.ForeignKey(M2MIntermediateData, models.CASCADE) + right = models.ForeignKey(Anchor, models.CASCADE) + extra = models.CharField(max_length=30, blank=True, default="doesn't matter") + +# The following test classes are for validating the +# deserialization of objects that use a user-defined +# field as the primary key. +# Some of these data types have been commented out +# because they can't be used as a primary key on one +# or all database backends. + + +class BooleanPKData(models.Model): + data = models.BooleanField(primary_key=True, default=False) + + +class CharPKData(models.Model): + data = models.CharField(max_length=30, primary_key=True) + +# class DatePKData(models.Model): +# data = models.DateField(primary_key=True) + +# class DateTimePKData(models.Model): +# data = models.DateTimeField(primary_key=True) + + +class DecimalPKData(models.Model): + data = models.DecimalField(primary_key=True, decimal_places=3, max_digits=5) + + +class EmailPKData(models.Model): + data = models.EmailField(primary_key=True) + +# class FilePKData(models.Model): +# data = models.FileField(primary_key=True, upload_to='/foo/bar') + + +class FilePathPKData(models.Model): + data = models.FilePathField(primary_key=True) + + +class FloatPKData(models.Model): + data = models.FloatField(primary_key=True) + + +class IntegerPKData(models.Model): + data = models.IntegerField(primary_key=True) + +# class ImagePKData(models.Model): +# data = models.ImageField(primary_key=True) + + +class GenericIPAddressPKData(models.Model): + data = models.GenericIPAddressField(primary_key=True) + +# This is just a Boolean field with null=True, and we can't test a PK value of NULL. +# class NullBooleanPKData(models.Model): +# data = models.NullBooleanField(primary_key=True) + + +class PositiveIntegerPKData(models.Model): + data = models.PositiveIntegerField(primary_key=True) + + +class PositiveSmallIntegerPKData(models.Model): + data = models.PositiveSmallIntegerField(primary_key=True) + + +class SlugPKData(models.Model): + data = models.SlugField(primary_key=True) + + +class SmallPKData(models.Model): + data = models.SmallIntegerField(primary_key=True) + +# class TextPKData(models.Model): +# data = models.TextField(primary_key=True) + +# class TimePKData(models.Model): +# data = models.TimeField(primary_key=True) + + +class UUIDData(models.Model): + data = models.UUIDField(primary_key=True) + + +class FKToUUID(models.Model): + data = models.ForeignKey(UUIDData, models.CASCADE) + + +# Tests for handling fields with pre_save functions, or +# models with save functions that modify data + + +class AutoNowDateTimeData(models.Model): + data = models.DateTimeField(null=True, auto_now=True) + + +class ModifyingSaveData(models.Model): + data = models.IntegerField(null=True) + + def save(self, *args, **kwargs): + """ + A save method that modifies the data in the object. + Verifies that a user-defined save() method isn't called when objects + are deserialized (#4459). + """ + self.data = 666 + super(ModifyingSaveData, self).save(*args, **kwargs) + +# Tests for serialization of models using inheritance. +# Regression for #7202, #7350 + + +class AbstractBaseModel(models.Model): + parent_data = models.IntegerField() + + class Meta: + abstract = True + + +class InheritAbstractModel(AbstractBaseModel): + child_data = models.IntegerField() + + +class BaseModel(models.Model): + parent_data = models.IntegerField() + + +class InheritBaseModel(BaseModel): + child_data = models.IntegerField() + + +class ExplicitInheritBaseModel(BaseModel): + parent = models.OneToOneField(BaseModel, models.CASCADE) + child_data = models.IntegerField() + + +class LengthModel(models.Model): + data = models.IntegerField() + + def __len__(self): + return self.data diff --git a/tests/serializers_regress/tests.py b/tests/serializers/test_data.py similarity index 93% rename from tests/serializers_regress/tests.py rename to tests/serializers/test_data.py index 053e952e1584..5f0cd45643da 100644 --- a/tests/serializers_regress/tests.py +++ b/tests/serializers/test_data.py @@ -14,25 +14,24 @@ from django.core import serializers from django.db import connection, models -from django.http import HttpResponse from django.test import TestCase from django.utils import six -from django.utils.functional import curry from .models import ( - Anchor, AutoNowDateTimeData, BigIntegerData, BinaryData, - BooleanData, BooleanPKData, CharData, CharPKData, DateData, - DateTimeData, DecimalData, DecimalPKData, EmailData, EmailPKData, - ExplicitInheritBaseModel, FileData, FilePathData, FilePathPKData, FKData, - FKDataToField, FKDataToO2O, FKSelfData, FKToUUID, - FloatData, FloatPKData, GenericData, GenericIPAddressData, - GenericIPAddressPKData, InheritAbstractModel, InheritBaseModel, - IntegerData, IntegerPKData, Intermediate, LengthModel, M2MData, - M2MIntermediateData, M2MSelfData, ModifyingSaveData, + Anchor, AutoNowDateTimeData, BigIntegerData, BinaryData, BooleanData, + BooleanPKData, CharData, CharPKData, DateData, DateTimeData, DecimalData, + DecimalPKData, EmailData, EmailPKData, ExplicitInheritBaseModel, FileData, + FilePathData, FilePathPKData, FKData, FKDataToField, FKDataToO2O, + FKSelfData, FKToUUID, FloatData, FloatPKData, GenericData, + GenericIPAddressData, GenericIPAddressPKData, InheritAbstractModel, + InheritBaseModel, IntegerData, IntegerPKData, Intermediate, LengthModel, + M2MData, M2MIntermediateData, M2MSelfData, ModifyingSaveData, NullBooleanData, O2OData, PositiveIntegerData, PositiveIntegerPKData, PositiveSmallIntegerData, PositiveSmallIntegerPKData, SlugData, SlugPKData, SmallData, SmallPKData, Tag, TextData, TimeData, UniqueAnchor, UUIDData, ) +from .tests import register_tests + # A set of functions that can be used to recreate # test data objects of various kinds. @@ -385,9 +384,10 @@ def inherited_compare(testcase, pk, klass, data): ]) -class SerializerTests(TestCase): +class SerializerDataTests(TestCase): pass + def serializerTest(format, self): # Create all the objects defined in the test data @@ -421,6 +421,4 @@ def serializerTest(format, self): self.assertEqual(count, klass.objects.count()) -for format in [f for f in serializers.get_serializer_formats() - if not isinstance(serializers.get_serializer(f), serializers.BadSerializer) and not f == 'geojson']: - setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format)) +register_tests(SerializerDataTests, 'test_%s_serializer', serializerTest) diff --git a/tests/serializers_regress/__init__.py b/tests/serializers_regress/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/tests/serializers_regress/models.py b/tests/serializers_regress/models.py deleted file mode 100644 index fb8aa9f4112e..000000000000 --- a/tests/serializers_regress/models.py +++ /dev/null @@ -1,316 +0,0 @@ -""" -A test spanning all the capabilities of all the serializers. - -This class sets up a model for each model field type -(except for image types, because of the Pillow dependency). -""" -from django.contrib.contenttypes.fields import ( - GenericForeignKey, GenericRelation, -) -from django.contrib.contenttypes.models import ContentType -from django.db import models - - -# The following classes are for testing basic data -# marshalling, including NULL values, where allowed. - - -class BinaryData(models.Model): - data = models.BinaryField(null=True) - - -class BooleanData(models.Model): - data = models.BooleanField(default=False) - - -class CharData(models.Model): - data = models.CharField(max_length=30, null=True) - - -class DateData(models.Model): - data = models.DateField(null=True) - - -class DateTimeData(models.Model): - data = models.DateTimeField(null=True) - - -class DecimalData(models.Model): - data = models.DecimalField(null=True, decimal_places=3, max_digits=5) - - -class EmailData(models.Model): - data = models.EmailField(null=True) - - -class FileData(models.Model): - data = models.FileField(null=True, upload_to='/foo/bar') - - -class FilePathData(models.Model): - data = models.FilePathField(null=True) - - -class FloatData(models.Model): - data = models.FloatField(null=True) - - -class IntegerData(models.Model): - data = models.IntegerField(null=True) - - -class BigIntegerData(models.Model): - data = models.BigIntegerField(null=True) - -# class ImageData(models.Model): -# data = models.ImageField(null=True) - - -class GenericIPAddressData(models.Model): - data = models.GenericIPAddressField(null=True) - - -class NullBooleanData(models.Model): - data = models.NullBooleanField(null=True) - - -class PositiveIntegerData(models.Model): - data = models.PositiveIntegerField(null=True) - - -class PositiveSmallIntegerData(models.Model): - data = models.PositiveSmallIntegerField(null=True) - - -class SlugData(models.Model): - data = models.SlugField(null=True) - - -class SmallData(models.Model): - data = models.SmallIntegerField(null=True) - - -class TextData(models.Model): - data = models.TextField(null=True) - - -class TimeData(models.Model): - data = models.TimeField(null=True) - - -class Tag(models.Model): - """A tag on an item.""" - data = models.SlugField() - content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() - - content_object = GenericForeignKey() - - class Meta: - ordering = ["data"] - - -class GenericData(models.Model): - data = models.CharField(max_length=30) - - tags = GenericRelation(Tag) - -# The following test classes are all for validation -# of related objects; in particular, forward, backward, -# and self references. - - -class Anchor(models.Model): - """This is a model that can be used as - something for other models to point at""" - - data = models.CharField(max_length=30) - - class Meta: - ordering = ('id',) - - -class UniqueAnchor(models.Model): - """This is a model that can be used as - something for other models to point at""" - - data = models.CharField(unique=True, max_length=30) - - -class FKData(models.Model): - data = models.ForeignKey(Anchor, models.SET_NULL, null=True) - - -class M2MData(models.Model): - data = models.ManyToManyField(Anchor) - - -class O2OData(models.Model): - # One to one field can't be null here, since it is a PK. - data = models.OneToOneField(Anchor, models.CASCADE, primary_key=True) - - -class FKSelfData(models.Model): - data = models.ForeignKey('self', models.CASCADE, null=True) - - -class M2MSelfData(models.Model): - data = models.ManyToManyField('self', symmetrical=False) - - -class FKDataToField(models.Model): - data = models.ForeignKey(UniqueAnchor, models.SET_NULL, null=True, to_field='data') - - -class FKDataToO2O(models.Model): - data = models.ForeignKey(O2OData, models.SET_NULL, null=True) - - -class M2MIntermediateData(models.Model): - data = models.ManyToManyField(Anchor, through='Intermediate') - - -class Intermediate(models.Model): - left = models.ForeignKey(M2MIntermediateData, models.CASCADE) - right = models.ForeignKey(Anchor, models.CASCADE) - extra = models.CharField(max_length=30, blank=True, default="doesn't matter") - -# The following test classes are for validating the -# deserialization of objects that use a user-defined -# field as the primary key. -# Some of these data types have been commented out -# because they can't be used as a primary key on one -# or all database backends. - - -class BooleanPKData(models.Model): - data = models.BooleanField(primary_key=True, default=False) - - -class CharPKData(models.Model): - data = models.CharField(max_length=30, primary_key=True) - -# class DatePKData(models.Model): -# data = models.DateField(primary_key=True) - -# class DateTimePKData(models.Model): -# data = models.DateTimeField(primary_key=True) - - -class DecimalPKData(models.Model): - data = models.DecimalField(primary_key=True, decimal_places=3, max_digits=5) - - -class EmailPKData(models.Model): - data = models.EmailField(primary_key=True) - -# class FilePKData(models.Model): -# data = models.FileField(primary_key=True, upload_to='/foo/bar') - - -class FilePathPKData(models.Model): - data = models.FilePathField(primary_key=True) - - -class FloatPKData(models.Model): - data = models.FloatField(primary_key=True) - - -class IntegerPKData(models.Model): - data = models.IntegerField(primary_key=True) - -# class ImagePKData(models.Model): -# data = models.ImageField(primary_key=True) - - -class GenericIPAddressPKData(models.Model): - data = models.GenericIPAddressField(primary_key=True) - -# This is just a Boolean field with null=True, and we can't test a PK value of NULL. -# class NullBooleanPKData(models.Model): -# data = models.NullBooleanField(primary_key=True) - - -class PositiveIntegerPKData(models.Model): - data = models.PositiveIntegerField(primary_key=True) - - -class PositiveSmallIntegerPKData(models.Model): - data = models.PositiveSmallIntegerField(primary_key=True) - - -class SlugPKData(models.Model): - data = models.SlugField(primary_key=True) - - -class SmallPKData(models.Model): - data = models.SmallIntegerField(primary_key=True) - -# class TextPKData(models.Model): -# data = models.TextField(primary_key=True) - -# class TimePKData(models.Model): -# data = models.TimeField(primary_key=True) - - -class UUIDData(models.Model): - data = models.UUIDField(primary_key=True) - - -class FKToUUID(models.Model): - data = models.ForeignKey(UUIDData, models.CASCADE) - - -# Tests for handling fields with pre_save functions, or -# models with save functions that modify data - - -class AutoNowDateTimeData(models.Model): - data = models.DateTimeField(null=True, auto_now=True) - - -class ModifyingSaveData(models.Model): - data = models.IntegerField(null=True) - - def save(self, *args, **kwargs): - """ - A save method that modifies the data in the object. - Verifies that a user-defined save() method isn't called when objects - are deserialized (#4459). - """ - self.data = 666 - super(ModifyingSaveData, self).save(*args, **kwargs) - -# Tests for serialization of models using inheritance. -# Regression for #7202, #7350 - - -class AbstractBaseModel(models.Model): - parent_data = models.IntegerField() - - class Meta: - abstract = True - - -class InheritAbstractModel(AbstractBaseModel): - child_data = models.IntegerField() - - -class BaseModel(models.Model): - parent_data = models.IntegerField() - - -class InheritBaseModel(BaseModel): - child_data = models.IntegerField() - - -class ExplicitInheritBaseModel(BaseModel): - parent = models.OneToOneField(BaseModel, models.CASCADE) - child_data = models.IntegerField() - - -class LengthModel(models.Model): - data = models.IntegerField() - - def __len__(self): - return self.data From fb901c82bf60d40f17205828e758f4187198de49 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 Sep 2015 19:12:30 +0200 Subject: [PATCH 021/756] [1.9.x] Made tests/serializers/models.py a models package Thanks Tim Graham for the patch series review. Backport of 50acbf3ff6 from master. --- tests/serializers/models/__init__.py | 3 + tests/serializers/models/base.py | 178 +++++++++++++++ .../serializers/{models.py => models/data.py} | 208 +----------------- tests/serializers/models/natural.py | 21 ++ 4 files changed, 207 insertions(+), 203 deletions(-) create mode 100644 tests/serializers/models/__init__.py create mode 100644 tests/serializers/models/base.py rename tests/serializers/{models.py => models/data.py} (59%) create mode 100644 tests/serializers/models/natural.py diff --git a/tests/serializers/models/__init__.py b/tests/serializers/models/__init__.py new file mode 100644 index 000000000000..a786b54be95c --- /dev/null +++ b/tests/serializers/models/__init__.py @@ -0,0 +1,3 @@ +from .base import * # NOQA +from .data import * # NOQA +from .natural import * # NOQA diff --git a/tests/serializers/models/base.py b/tests/serializers/models/base.py new file mode 100644 index 000000000000..4889377e0981 --- /dev/null +++ b/tests/serializers/models/base.py @@ -0,0 +1,178 @@ +""" +Serialization + +``django.core.serializers`` provides interfaces to converting Django +``QuerySet`` objects to and from "flat" data (i.e. strings). +""" +from __future__ import unicode_literals + +from decimal import Decimal + +from django.db import models +from django.utils import six +from django.utils.encoding import python_2_unicode_compatible + + +class CategoryMetaDataManager(models.Manager): + + def get_by_natural_key(self, kind, name): + return self.get(kind=kind, name=name) + + +@python_2_unicode_compatible +class CategoryMetaData(models.Model): + kind = models.CharField(max_length=10) + name = models.CharField(max_length=10) + value = models.CharField(max_length=10) + objects = CategoryMetaDataManager() + + class Meta: + unique_together = (('kind', 'name'),) + + def __str__(self): + return '[%s:%s]=%s' % (self.kind, self.name, self.value) + + def natural_key(self): + return (self.kind, self.name) + + +@python_2_unicode_compatible +class Category(models.Model): + name = models.CharField(max_length=20) + meta_data = models.ForeignKey(CategoryMetaData, models.SET_NULL, null=True, default=None) + + class Meta: + ordering = ('name',) + + def __str__(self): + return self.name + + +@python_2_unicode_compatible +class Author(models.Model): + name = models.CharField(max_length=20) + + class Meta: + ordering = ('name',) + + def __str__(self): + return self.name + + +@python_2_unicode_compatible +class Article(models.Model): + author = models.ForeignKey(Author, models.CASCADE) + headline = models.CharField(max_length=50) + pub_date = models.DateTimeField() + categories = models.ManyToManyField(Category) + meta_data = models.ManyToManyField(CategoryMetaData) + + class Meta: + ordering = ('pub_date',) + + def __str__(self): + return self.headline + + +@python_2_unicode_compatible +class AuthorProfile(models.Model): + author = models.OneToOneField(Author, models.CASCADE, primary_key=True) + date_of_birth = models.DateField() + + def __str__(self): + return "Profile of %s" % self.author + + +@python_2_unicode_compatible +class Actor(models.Model): + name = models.CharField(max_length=20, primary_key=True) + + class Meta: + ordering = ('name',) + + def __str__(self): + return self.name + + +@python_2_unicode_compatible +class Movie(models.Model): + actor = models.ForeignKey(Actor, models.CASCADE) + title = models.CharField(max_length=50) + price = models.DecimalField(max_digits=6, decimal_places=2, default=Decimal('0.00')) + + class Meta: + ordering = ('title',) + + def __str__(self): + return self.title + + +class Score(models.Model): + score = models.FloatField() + + +@python_2_unicode_compatible +class Team(object): + def __init__(self, title): + self.title = title + + def __str__(self): + raise NotImplementedError("Not so simple") + + def to_string(self): + return "%s" % self.title + + +class TeamField(models.CharField): + + def __init__(self): + super(TeamField, self).__init__(max_length=100) + + def get_db_prep_save(self, value, connection): + return six.text_type(value.title) + + def to_python(self, value): + if isinstance(value, Team): + return value + return Team(value) + + def from_db_value(self, value, expression, connection, context): + return Team(value) + + def value_to_string(self, obj): + return self.value_from_object(obj).to_string() + + def deconstruct(self): + name, path, args, kwargs = super(TeamField, self).deconstruct() + del kwargs['max_length'] + return name, path, args, kwargs + + +@python_2_unicode_compatible +class Player(models.Model): + name = models.CharField(max_length=50) + rank = models.IntegerField() + team = TeamField() + + def __str__(self): + return '%s (%d) playing for %s' % (self.name, self.rank, self.team.to_string()) + + +class BaseModel(models.Model): + parent_data = models.IntegerField() + + +class ProxyBaseModel(BaseModel): + class Meta: + proxy = True + + +class ProxyProxyBaseModel(ProxyBaseModel): + class Meta: + proxy = True + + +class ComplexModel(models.Model): + field1 = models.CharField(max_length=10) + field2 = models.CharField(max_length=10) + field3 = models.CharField(max_length=10) diff --git a/tests/serializers/models.py b/tests/serializers/models/data.py similarity index 59% rename from tests/serializers/models.py rename to tests/serializers/models/data.py index 023a2e8f8537..98d59622d762 100644 --- a/tests/serializers/models.py +++ b/tests/serializers/models/data.py @@ -1,214 +1,20 @@ -# -*- coding: utf-8 -*- """ -Serialization - -``django.core.serializers`` provides interfaces to converting Django -``QuerySet`` objects to and from "flat" data (i.e. strings). +******** Models for test_data.py *********** +The following classes are for testing basic data marshalling, including +NULL values, where allowed. +The basic idea is to have a model for each Django data type. """ from __future__ import unicode_literals -from decimal import Decimal - from django.contrib.contenttypes.fields import ( GenericForeignKey, GenericRelation, ) from django.contrib.contenttypes.models import ContentType from django.db import models -from django.utils import six -from django.utils.encoding import python_2_unicode_compatible - - -class CategoryMetaDataManager(models.Manager): - - def get_by_natural_key(self, kind, name): - return self.get(kind=kind, name=name) - - -@python_2_unicode_compatible -class CategoryMetaData(models.Model): - kind = models.CharField(max_length=10) - name = models.CharField(max_length=10) - value = models.CharField(max_length=10) - objects = CategoryMetaDataManager() - - class Meta: - unique_together = (('kind', 'name'),) - - def __str__(self): - return '[%s:%s]=%s' % (self.kind, self.name, self.value) - - def natural_key(self): - return (self.kind, self.name) - - -@python_2_unicode_compatible -class Category(models.Model): - name = models.CharField(max_length=20) - meta_data = models.ForeignKey(CategoryMetaData, models.SET_NULL, null=True, default=None) - - class Meta: - ordering = ('name',) - - def __str__(self): - return self.name - - -@python_2_unicode_compatible -class Author(models.Model): - name = models.CharField(max_length=20) - - class Meta: - ordering = ('name',) - - def __str__(self): - return self.name - - -@python_2_unicode_compatible -class Article(models.Model): - author = models.ForeignKey(Author, models.CASCADE) - headline = models.CharField(max_length=50) - pub_date = models.DateTimeField() - categories = models.ManyToManyField(Category) - meta_data = models.ManyToManyField(CategoryMetaData) - - class Meta: - ordering = ('pub_date',) - - def __str__(self): - return self.headline - - -@python_2_unicode_compatible -class AuthorProfile(models.Model): - author = models.OneToOneField(Author, models.CASCADE, primary_key=True) - date_of_birth = models.DateField() - - def __str__(self): - return "Profile of %s" % self.author - - -@python_2_unicode_compatible -class Actor(models.Model): - name = models.CharField(max_length=20, primary_key=True) - - class Meta: - ordering = ('name',) - - def __str__(self): - return self.name - - -@python_2_unicode_compatible -class Movie(models.Model): - actor = models.ForeignKey(Actor, models.CASCADE) - title = models.CharField(max_length=50) - price = models.DecimalField(max_digits=6, decimal_places=2, default=Decimal('0.00')) - - class Meta: - ordering = ('title',) - - def __str__(self): - return self.title - -class Score(models.Model): - score = models.FloatField() +from .base import BaseModel -@python_2_unicode_compatible -class Team(object): - def __init__(self, title): - self.title = title - - def __str__(self): - raise NotImplementedError("Not so simple") - - def to_string(self): - return "%s" % self.title - - -class TeamField(models.CharField): - - def __init__(self): - super(TeamField, self).__init__(max_length=100) - - def get_db_prep_save(self, value, connection): - return six.text_type(value.title) - - def to_python(self, value): - if isinstance(value, Team): - return value - return Team(value) - - def from_db_value(self, value, expression, connection, context): - return Team(value) - - def value_to_string(self, obj): - return self.value_from_object(obj).to_string() - - def deconstruct(self): - name, path, args, kwargs = super(TeamField, self).deconstruct() - del kwargs['max_length'] - return name, path, args, kwargs - - -@python_2_unicode_compatible -class Player(models.Model): - name = models.CharField(max_length=50) - rank = models.IntegerField() - team = TeamField() - - def __str__(self): - return '%s (%d) playing for %s' % (self.name, self.rank, self.team.to_string()) - - -class BaseModel(models.Model): - parent_data = models.IntegerField() - - -class ProxyBaseModel(BaseModel): - class Meta: - proxy = True - - -class ProxyProxyBaseModel(ProxyBaseModel): - class Meta: - proxy = True - - -class ComplexModel(models.Model): - field1 = models.CharField(max_length=10) - field2 = models.CharField(max_length=10) - field3 = models.CharField(max_length=10) - - -# ******** Models for test_natural.py *********** - -class NaturalKeyAnchorManager(models.Manager): - def get_by_natural_key(self, data): - return self.get(data=data) - - -class NaturalKeyAnchor(models.Model): - objects = NaturalKeyAnchorManager() - - data = models.CharField(max_length=100, unique=True) - title = models.CharField(max_length=100, null=True) - - def natural_key(self): - return (self.data,) - - -class FKDataNaturalKey(models.Model): - data = models.ForeignKey(NaturalKeyAnchor, models.SET_NULL, null=True) - - -# ******** Models for test_data.py *********** -# The following classes are for testing basic data marshalling, including -# NULL values, where allowed. -# The basic idea is to have a model for each Django data type. - class BinaryData(models.Model): data = models.BinaryField(null=True) @@ -490,10 +296,6 @@ class InheritAbstractModel(AbstractBaseModel): child_data = models.IntegerField() -class BaseModel(models.Model): - parent_data = models.IntegerField() - - class InheritBaseModel(BaseModel): child_data = models.IntegerField() diff --git a/tests/serializers/models/natural.py b/tests/serializers/models/natural.py new file mode 100644 index 000000000000..b50956692efc --- /dev/null +++ b/tests/serializers/models/natural.py @@ -0,0 +1,21 @@ +"""Models for test_natural.py""" +from django.db import models + + +class NaturalKeyAnchorManager(models.Manager): + def get_by_natural_key(self, data): + return self.get(data=data) + + +class NaturalKeyAnchor(models.Model): + objects = NaturalKeyAnchorManager() + + data = models.CharField(max_length=100, unique=True) + title = models.CharField(max_length=100, null=True) + + def natural_key(self): + return (self.data,) + + +class FKDataNaturalKey(models.Model): + data = models.ForeignKey(NaturalKeyAnchor, models.SET_NULL, null=True) From 6a0b4faf43db70a35163dde0a5ea626ed4129fa7 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 25 Sep 2015 22:35:36 +0200 Subject: [PATCH 022/756] [1.9.x] Fixed #25421 -- Fixed test --keepdb option on Oracle. Backport of a3a6def867c61d24dfded6cbfef7f820409dc739 from master --- django/db/backends/oracle/creation.py | 52 ++++++++++++++++++--------- docs/releases/1.8.5.txt | 2 ++ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index 7f921085244d..01b4056b4b73 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -35,7 +35,7 @@ def _create_test_db(self, verbosity=1, autoclobber=False, keepdb=False): cursor = self._maindb_connection.cursor() if self._test_database_create(): try: - self._execute_test_db_creation(cursor, parameters, verbosity) + self._execute_test_db_creation(cursor, parameters, verbosity, keepdb) except Exception as e: # if we want to keep the db, then no need to do any of the below, # just return and skip it all. @@ -63,7 +63,7 @@ def _create_test_db(self, verbosity=1, autoclobber=False, keepdb=False): sys.stderr.write("Got an error destroying the old test database: %s\n" % e) sys.exit(2) try: - self._execute_test_db_creation(cursor, parameters, verbosity) + self._execute_test_db_creation(cursor, parameters, verbosity, keepdb) except Exception as e: sys.stderr.write("Got an error recreating the test database: %s\n" % e) sys.exit(2) @@ -75,8 +75,11 @@ def _create_test_db(self, verbosity=1, autoclobber=False, keepdb=False): if verbosity >= 1: print("Creating test user...") try: - self._create_test_user(cursor, parameters, verbosity) + self._create_test_user(cursor, parameters, verbosity, keepdb) except Exception as e: + # If we want to keep the db, then we want to also keep the user. + if keepdb: + return sys.stderr.write("Got an error creating the test user: %s\n" % e) if not autoclobber: confirm = input( @@ -89,7 +92,7 @@ def _create_test_db(self, verbosity=1, autoclobber=False, keepdb=False): self._destroy_test_user(cursor, parameters, verbosity) if verbosity >= 1: print("Creating test user...") - self._create_test_user(cursor, parameters, verbosity) + self._create_test_user(cursor, parameters, verbosity, keepdb) except Exception as e: sys.stderr.write("Got an error recreating the test user: %s\n" % e) sys.exit(2) @@ -184,7 +187,7 @@ def _destroy_test_db(self, test_database_name, verbosity=1): self._execute_test_db_destruction(cursor, parameters, verbosity) self._maindb_connection.close() - def _execute_test_db_creation(self, cursor, parameters, verbosity): + def _execute_test_db_creation(self, cursor, parameters, verbosity, keepdb=False): if verbosity >= 2: print("_create_test_db(): dbname = %s" % parameters['user']) statements = [ @@ -197,9 +200,11 @@ def _execute_test_db_creation(self, cursor, parameters, verbosity): REUSE AUTOEXTEND ON NEXT 10M MAXSIZE %(maxsize_tmp)s """, ] - self._execute_statements(cursor, statements, parameters, verbosity) + # Ignore "tablespace already exists" error when keepdb is on. + acceptable_ora_err = 'ORA-01543' if keepdb else None + self._execute_allow_fail_statements(cursor, statements, parameters, verbosity, acceptable_ora_err) - def _create_test_user(self, cursor, parameters, verbosity): + def _create_test_user(self, cursor, parameters, verbosity, keepdb=False): if verbosity >= 2: print("_create_test_user(): username = %s" % parameters['user']) statements = [ @@ -216,18 +221,14 @@ def _create_test_user(self, cursor, parameters, verbosity): CREATE TRIGGER TO %(user)s""", ] - self._execute_statements(cursor, statements, parameters, verbosity) + # Ignore "user already exists" error when keepdb is on + acceptable_ora_err = 'ORA-01920' if keepdb else None + self._execute_allow_fail_statements(cursor, statements, parameters, verbosity, acceptable_ora_err) # Most test-suites can be run without the create-view privilege. But some need it. extra = "GRANT CREATE VIEW TO %(user)s" - try: - self._execute_statements(cursor, [extra], parameters, verbosity, allow_quiet_fail=True) - except DatabaseError as err: - description = str(err) - if 'ORA-01031' in description: - if verbosity >= 2: - print("Failed to grant CREATE VIEW permission to test user. This may be ok.") - else: - raise + success = self._execute_allow_fail_statements(cursor, [extra], parameters, verbosity, 'ORA-01031') + if not success and verbosity >= 2: + print("Failed to grant CREATE VIEW permission to test user. This may be ok.") def _execute_test_db_destruction(self, cursor, parameters, verbosity): if verbosity >= 2: @@ -259,6 +260,23 @@ def _execute_statements(self, cursor, statements, parameters, verbosity, allow_q sys.stderr.write("Failed (%s)\n" % (err)) raise + def _execute_allow_fail_statements(self, cursor, statements, parameters, verbosity, acceptable_ora_err): + """ + Execute statements which are allowed to fail silently if the Oracle + error code given by `acceptable_ora_err` is raised. Return True if the + statements execute without an exception, or False otherwise. + """ + try: + # Statement can fail when acceptable_ora_err is not None + allow_quiet_fail = acceptable_ora_err is not None and len(acceptable_ora_err) > 0 + self._execute_statements(cursor, statements, parameters, verbosity, allow_quiet_fail=allow_quiet_fail) + return True + except DatabaseError as err: + description = str(err) + if acceptable_ora_err is None or acceptable_ora_err not in description: + raise + return False + def _get_test_db_params(self): return { 'dbname': self._test_database_name(), diff --git a/docs/releases/1.8.5.txt b/docs/releases/1.8.5.txt index d412c4b653a2..14893f4a7206 100644 --- a/docs/releases/1.8.5.txt +++ b/docs/releases/1.8.5.txt @@ -50,3 +50,5 @@ Bugfixes * Allowed using ORM write methods after disabling autocommit with :func:`set_autocommit(False) ` (:ticket:`24921`). + +* Fixed the ``manage.py test --keepdb`` option on Oracle (:ticket:`25421`). From 76aa4cc8a265ec36c4ae344eab7985193dcde5ee Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 26 Sep 2015 20:27:09 -0400 Subject: [PATCH 023/756] [1.9.x] Fixed #24323 -- Documented @admin.register can't be used with super(XXXAdmin in __init__(). Backport of 4cd2bdae07ac5561fdae0eede58c68df4dfcc780 from master --- docs/ref/contrib/admin/index.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 321c6d83ad3a..605601147fb3 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -125,6 +125,13 @@ The register decorator class PersonAdmin(admin.ModelAdmin): pass + You can't use this decorator if you have to reference your model admin + class in its ``__init__()`` method, e.g. + ``super(PersonAdmin, self).__init__(*args, **kwargs)``. If you are using + Python 3 and don't have to worry about supporting Python 2, you can + use ``super().__init__(*args, **kwargs)`` . Otherwise, you'll have to use + ``admin.site.register()`` instead of this decorator. + Discovery of admin files ------------------------ From 0094fb6be8e0f0831e2f3e3e1b36900cf60f6785 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 28 Sep 2015 14:08:17 -0400 Subject: [PATCH 024/756] [1.9.x] Documented auth's login/logout function parameters. Backport of c14b6b52ff131db263bf06a02f903a73390975da from master --- docs/topics/auth/default.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index 8da0e78c4728..d0e446faad46 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -323,7 +323,7 @@ How to log a user in If you have an authenticated user you want to attach to the current session - this is done with a :func:`~django.contrib.auth.login` function. -.. function:: login() +.. function:: login(request, user) To log a user in, from a view, use :func:`~django.contrib.auth.login()`. It takes an :class:`~django.http.HttpRequest` object and a @@ -371,7 +371,7 @@ If you have an authenticated user you want to attach to the current session How to log a user out --------------------- -.. function:: logout() +.. function:: logout(request) To log out a user who has been logged in via :func:`django.contrib.auth.login()`, use From db88e40312dfba8d32203a0990792a4a9840f6a7 Mon Sep 17 00:00:00 2001 From: Antoine Catton Date: Fri, 25 Sep 2015 15:32:23 -0600 Subject: [PATCH 025/756] [1.9.x] Refs #16860 -- Fixed password help text when there aren't any validators. This avoids creating an empty list which is invalid HTML 4. Backport of 53ccffdb8c8e47a4d4304df453d8c79a9be295ab from master --- django/contrib/auth/password_validation.py | 2 +- tests/auth_tests/test_validators.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/django/contrib/auth/password_validation.py b/django/contrib/auth/password_validation.py index efad0d72a665..332d76c8450a 100644 --- a/django/contrib/auth/password_validation.py +++ b/django/contrib/auth/password_validation.py @@ -84,7 +84,7 @@ def password_validators_help_text_html(password_validators=None): """ help_texts = password_validators_help_texts(password_validators) help_items = [format_html('
  • {}
  • ', help_text) for help_text in help_texts] - return '
      %s
    ' % ''.join(help_items) + return '
      %s
    ' % ''.join(help_items) if help_items else '' class MinimumLengthValidator(object): diff --git a/tests/auth_tests/test_validators.py b/tests/auth_tests/test_validators.py index e70feb727d64..a9f47eac1f74 100644 --- a/tests/auth_tests/test_validators.py +++ b/tests/auth_tests/test_validators.py @@ -68,6 +68,10 @@ def test_password_validators_help_text_html(self): self.assertEqual(help_text.count('
  • '), 2) self.assertIn('12 characters', help_text) + @override_settings(AUTH_PASSWORD_VALIDATORS=[]) + def test_empty_password_validator_help_text_html(self): + self.assertEqual(password_validators_help_text_html(), '') + class MinimumLengthValidatorTest(TestCase): def test_validate(self): From e21c6f34ded8a2b0a9b5a80ab069eb2e1b15067b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 25 Sep 2015 15:27:41 -0400 Subject: [PATCH 026/756] [1.9.x] Fixed #25376 -- Required virtualenv in installation instructions. Thanks Anjul Tyagi for some of the draft text. Backport of a523d94b4581ec41c86f5bbaaffdf5bb6b819c2e from master --- docs/topics/install.txt | 147 ++++++---------------------------------- 1 file changed, 19 insertions(+), 128 deletions(-) diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 027dd5db31cb..2c4a422689c6 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -119,9 +119,9 @@ database queries, Django will need permission to create a test database. .. _MySQL: http://www.mysql.com/ .. _psycopg2: http://initd.org/psycopg/ .. _SQLite: http://www.sqlite.org/ -.. _pysqlite: http://trac.edgewall.org/wiki/PySqlite .. _cx_Oracle: http://cx-oracle.sourceforge.net/ .. _Oracle: http://www.oracle.com/ + .. _removing-old-versions-of-django: Remove any old versions of Django @@ -144,7 +144,6 @@ following at your shell prompt (not the interactive Python prompt): $ python -c "import django; print(django.__path__)" - .. _install-django-code: Install the Django code @@ -168,58 +167,20 @@ This is the recommended way to install Django. it's outdated. (If it's outdated, you'll know because installation won't work.) -2. (optional) Take a look at virtualenv_ and virtualenvwrapper_. These tools - provide isolated Python environments, which are more practical than - installing packages systemwide. They also allow installing packages - without administrator privileges. It's up to you to decide if you want to - learn and use them. - -3. If you're using Linux, Mac OS X or some other flavor of Unix, enter the - command ``sudo pip install Django`` at the shell prompt. If you're using - Windows, start a command shell with administrator privileges and run - the command ``pip install Django``. This will install Django in your Python - installation's ``site-packages`` directory. +2. Take a look at virtualenv_ and virtualenvwrapper_. These tools provide + isolated Python environments, which are more practical than installing + packages systemwide. They also allow installing packages without + administrator privileges. The :doc:`contributing tutorial + ` walks through how to create a virtualenv on Python 3. - If you're using a virtualenv, you don't need ``sudo`` or administrator - privileges, and this will install Django in the virtualenv's - ``site-packages`` directory. +3. After you've created and activated a virtual environment, enter the command + ``pip install Django`` at the shell prompt. .. _pip: https://pip.pypa.io/ .. _virtualenv: http://www.virtualenv.org/ .. _virtualenvwrapper: http://virtualenvwrapper.readthedocs.org/en/latest/ .. _standalone pip installer: https://pip.pypa.io/en/latest/installing.html#install-pip -Installing an official release manually -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -1. Download the latest release from our `download page`_. - -2. Untar the downloaded file (e.g. ``tar xzvf Django-X.Y.tar.gz``, - where ``X.Y`` is the version number of the latest release). - If you're using Windows, you can download the command-line tool - bsdtar_ to do this, or you can use a GUI-based tool such as 7-zip_. - -3. Change into the directory created in step 2 (e.g. ``cd Django-X.Y``). - -4. If you're using Linux, Mac OS X or some other flavor of Unix, enter the - command ``sudo python setup.py install`` at the shell prompt. If you're - using Windows, start a command shell with administrator privileges and - run the command ``python setup.py install``. This will install Django in - your Python installation's ``site-packages`` directory. - - .. admonition:: Removing an old version - - If you use this installation technique, it is particularly important - that you :ref:`remove any existing - installations` of Django - first. Otherwise, you can end up with a broken installation that - includes files from previous versions that have since been removed from - Django. - -.. _download page: https://www.djangoproject.com/download/ -.. _bsdtar: http://gnuwin32.sourceforge.net/packages/bsdtar.htm -.. _7-zip: http://www.7-zip.org/ - Installing a distribution-specific package ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -253,101 +214,31 @@ latest bug fixes and improvements, follow these instructions: 1. Make sure that you have Git_ installed and that you can run its commands from a shell. (Enter ``git help`` at a shell prompt to test this.) -2. Check out Django's main development branch (the 'trunk' or 'master') like - so: +2. Check out Django's main development branch like so: .. code-block:: console - $ git clone git://github.com/django/django.git django-trunk + $ git clone git://github.com/django/django.git - This will create a directory ``django-trunk`` in your current directory. + This will create a directory ``django`` in your current directory. 3. Make sure that the Python interpreter can load Django's code. The most - convenient way to do this is via pip_. Run the following command: + convenient way to do this is to use virtualenv_, virtualenvwrapper_, and + pip_. The :doc:`contributing tutorial ` walks through + how to create a virtualenv on Python 3. - .. code-block:: console +4. After setting up and activating the virtualenv, run the following command: - $ sudo pip install -e django-trunk/ + .. code-block:: console - (If using a virtualenv_ you can omit ``sudo``.) + $ pip install -e django/ This will make Django's code importable, and will also make the ``django-admin`` utility command available. In other words, you're all set! - If you don't have pip_ available, see the alternative instructions for - `installing the development version without pip`_. - -.. warning:: - - Don't run ``sudo python setup.py install``, because you've already - carried out the equivalent actions in step 3. - When you want to update your copy of the Django source code, just run the -command ``git pull`` from within the ``django-trunk`` directory. When you do -this, Git will automatically download any changes. +command ``git pull`` from within the ``django`` directory. When you do this, +Git will automatically download any changes. .. _Git: http://git-scm.com/ -.. _`modify Python's search path`: https://docs.python.org/install/index.html#modifying-python-s-search-path -.. _installing-the-development-version-without-pip: - -Installing the development version without pip ----------------------------------------------- - -If you don't have pip_, you can instead manually `modify Python's search -path`_. - -First follow steps 1 and 2 above, so that you have a ``django-trunk`` directory -with a checkout of Django's latest code in it. Then add a ``.pth`` file -containing the full path to the ``django-trunk`` directory to your system's -``site-packages`` directory. For example, on a Unix-like system: - -.. code-block:: console - - $ echo WORKING-DIR/django-trunk > SITE-PACKAGES-DIR/django.pth - -In the above line, change ``WORKING-DIR/django-trunk`` to match the full path -to your new ``django-trunk`` directory, and change ``SITE-PACKAGES-DIR`` to -match the location of your system's ``site-packages`` directory. - -The location of the ``site-packages`` directory depends on the operating -system, and the location in which Python was installed. To find your system's -``site-packages`` location, execute the following: - -.. code-block:: console - - $ python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())" - -(Note that this should be run from a shell prompt, not a Python interactive -prompt.) - -Some Debian-based Linux distributions have separate ``site-packages`` -directories for user-installed packages, such as when installing Django from -a downloaded tarball. The command listed above will give you the system's -``site-packages``, the user's directory can be found in ``/usr/local/lib/`` -instead of ``/usr/lib/``. - -Next you need to make the ``django-admin.py`` utility available in your -shell PATH. - -On Unix-like systems, create a symbolic link to the file -``django-trunk/django/bin/django-admin`` in a directory on your system -path, such as ``/usr/local/bin``. For example: - -.. code-block:: console - - $ ln -s WORKING-DIR/django-trunk/django/bin/django-admin.py /usr/local/bin/ - -(In the above line, change WORKING-DIR to match the full path to your new -``django-trunk`` directory.) - -This simply lets you type ``django-admin.py`` from within any directory, -rather than having to qualify the command with the full path to the file. - -On Windows systems, the same result can be achieved by copying the file -``django-trunk/django/bin/django-admin.py`` to somewhere on your system -path, for example ``C:\Python27\Scripts``. - -Note that the rest of the documentation assumes this utility is installed -as ``django-admin``. You'll have to substitute ``django-admin.py`` if you use -this method. From 60fe6efe503d40f64ff597ab049952883dec8402 Mon Sep 17 00:00:00 2001 From: David Sanders Date: Tue, 29 Sep 2015 15:29:59 +1000 Subject: [PATCH 027/756] =?UTF-8?q?[1.9.x]=20Improved=20"=E2=80=9Cstandalo?= =?UTF-8?q?ne=E2=80=9D=20Django=20usage"=20example.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport of ae9f9dc37f39afeaa45c646cf6eef81beffcf021 from master --- docs/topics/settings.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/topics/settings.txt b/docs/topics/settings.txt index 2d44a843647f..55e8d221c3d8 100644 --- a/docs/topics/settings.txt +++ b/docs/topics/settings.txt @@ -277,13 +277,15 @@ After you've either set :envvar:`DJANGO_SETTINGS_MODULE` or called ``configure()``, you'll need to call :func:`django.setup()` to load your settings and populate Django's application registry. For example:: + import django from django.conf import settings from myapp import myapp_defaults settings.configure(default_settings=myapp_defaults, DEBUG=True) django.setup() - # Now this script can use any part of Django it needs. + # Now this script or any imported module can use any part of Django it needs. + from myapp import models Note that calling ``django.setup()`` is only necessary if your code is truly standalone. When invoked by your Web server, or through :doc:`django-admin From 59027a4cae8c5abbd0e9308d95dd6608be9199cd Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 26 Sep 2015 13:38:04 -0400 Subject: [PATCH 028/756] [1.9.x] Fixed #25466 -- Added backwards compatibility aliases for LoaderOrigin and StringOrigin. Thanks Simon Charette for the DeprecationInstanceCheck class. Backport of 8d1a001ef6dcbbe8053da05cdb3ec99965b0953f from master --- django/template/__init__.py | 3 ++- django/template/base.py | 10 +++++++++- django/template/loader.py | 12 +++++++++++- django/utils/deprecation.py | 9 +++++++++ docs/internals/deprecation.txt | 4 ++++ docs/releases/1.9.txt | 8 +++++++- tests/deprecation/tests.py | 17 ++++++++++++++++- 7 files changed, 58 insertions(+), 5 deletions(-) diff --git a/django/template/__init__.py b/django/template/__init__.py index 63b3d7ba4d78..092bd5663ca1 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -58,7 +58,8 @@ # Template parts from .base import ( # NOQA isort:skip - Context, Node, NodeList, Origin, RequestContext, Template, Variable, + Context, Node, NodeList, Origin, RequestContext, StringOrigin, Template, + Variable, ) # Deprecated in Django 1.8, will be removed in Django 1.10. diff --git a/django/template/base.py b/django/template/base.py index 52586e7ab506..530b60a3a913 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -60,7 +60,10 @@ BaseContext, Context, ContextPopException, RequestContext, ) from django.utils import six -from django.utils.deprecation import RemovedInDjango110Warning +from django.utils.deprecation import ( + DeprecationInstanceCheck, RemovedInDjango20Warning, + RemovedInDjango110Warning, +) from django.utils.encoding import ( force_str, force_text, python_2_unicode_compatible, ) @@ -158,6 +161,11 @@ def loader_name(self): ) +class StringOrigin(six.with_metaclass(DeprecationInstanceCheck, Origin)): + alternative = 'django.template.Origin' + deprecation_warning = RemovedInDjango20Warning + + class Template(object): def __init__(self, template_string, origin=None, name=None, engine=None): try: diff --git a/django/template/loader.py b/django/template/loader.py index ed3039b471e7..8a344cd98373 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -1,9 +1,14 @@ import warnings -from django.utils.deprecation import RemovedInDjango110Warning +from django.utils import six +from django.utils.deprecation import ( + DeprecationInstanceCheck, RemovedInDjango20Warning, + RemovedInDjango110Warning, +) from . import engines from .backends.django import DjangoTemplates +from .base import Origin from .engine import ( _context_instance_undefined, _dictionary_undefined, _dirs_undefined, ) @@ -147,3 +152,8 @@ def __init__(self, *args, **kwargs): "django.template.loaders.base.Loader.", RemovedInDjango110Warning, stacklevel=2) super(BaseLoader, self).__init__(*args, **kwargs) + + +class LoaderOrigin(six.with_metaclass(DeprecationInstanceCheck, Origin)): + alternative = 'django.template.Origin' + deprecation_warning = RemovedInDjango20Warning diff --git a/django/utils/deprecation.py b/django/utils/deprecation.py index df2f701b71aa..75270a81ef15 100644 --- a/django/utils/deprecation.py +++ b/django/utils/deprecation.py @@ -73,3 +73,12 @@ def __new__(cls, name, bases, attrs): setattr(base, old_method_name, wrapper(new_method)) return new_class + + +class DeprecationInstanceCheck(type): + def __instancecheck__(self, instance): + warnings.warn( + "`%s` is deprecated, use `%s` instead." % (self.__name__, self.alternative), + self.deprecation_warning, 2 + ) + return super(DeprecationInstanceCheck, self).__instancecheck__(instance) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 8b9ab3254c2f..d356ecb2f32c 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -100,6 +100,10 @@ details on these changes. * The ``enclosure`` keyword argument to ``SyndicationFeed.add_item()`` will be removed. +* The ``django.template.loader.LoaderOrigin`` and + ``django.template.base.StringOrigin`` aliases for + ``django.template.base.Origin`` will be removed. + .. _deprecation-removed-in-1.10: 1.10 diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index c63ae1c351ed..1f8a127c4f45 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -818,7 +818,9 @@ debug as ``True``, an instance of ``django.template.loader.LoaderOrigin`` or ``django.template.base.StringOrigin`` was set as the origin attribute on the template object. These classes have been combined into :class:`~django.template.base.Origin` and is now always set regardless of the -engine debug setting. +engine debug setting. For a minimal level of backwards compatibility, the old +class names will be kept as aliases to the new ``Origin`` class until +Django 2.0. .. _default-logging-changes-19: @@ -1335,6 +1337,10 @@ Miscellaneous deprecated. Use the new ``enclosures`` argument which accepts a list of ``Enclosure`` objects instead of a single one. +* The ``django.template.loader.LoaderOrigin`` and + ``django.template.base.StringOrigin`` aliases for + ``django.template.base.Origin`` are deprecated. + .. _removed-features-1.9: Features removed in 1.9 diff --git a/tests/deprecation/tests.py b/tests/deprecation/tests.py index 8f4ccf4eb5ef..6fa53a7fa03d 100644 --- a/tests/deprecation/tests.py +++ b/tests/deprecation/tests.py @@ -7,7 +7,9 @@ from django.test import SimpleTestCase from django.test.utils import reset_warning_registry from django.utils import six -from django.utils.deprecation import RenameMethodsBase +from django.utils.deprecation import ( + DeprecationInstanceCheck, RemovedInNextVersionWarning, RenameMethodsBase, +) from django.utils.encoding import force_text @@ -197,3 +199,16 @@ def test(self): "SimpleTestCase.urls is deprecated and will be removed in " "Django 1.10. Use @override_settings(ROOT_URLCONF=...) " "in TempTestCase instead.") + + +class DeprecationInstanceCheckTest(SimpleTestCase): + def test_warning(self): + class Manager(six.with_metaclass(DeprecationInstanceCheck)): + alternative = 'fake.path.Foo' + deprecation_warning = RemovedInNextVersionWarning + + msg = '`Manager` is deprecated, use `fake.path.Foo` instead.' + with warnings.catch_warnings(): + warnings.simplefilter('error', category=RemovedInNextVersionWarning) + with self.assertRaisesMessage(RemovedInNextVersionWarning, msg): + isinstance(object, Manager) From 00270ec7f8c7c20b3babb97ee807a00588d0a7b7 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 29 Sep 2015 19:36:08 -0400 Subject: [PATCH 029/756] [1.9.x] Made cosmetic cleanups to docs/ref/contrib/contenttypes.txt Backport of b47552b445547e60cc89213f79e02333cb63f270 from master --- docs/ref/contrib/contenttypes.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index d30503981198..539bba961b3d 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -103,8 +103,6 @@ created with the following values: * :attr:`~django.contrib.contenttypes.models.ContentType.model` will be set to ``'site'``. -.. _the verbose_name attribute: ../model-api/#verbose_name - Methods on ``ContentType`` instances ==================================== @@ -131,8 +129,7 @@ For example, we could look up the :class:`~django.contrib.auth.models.User` model:: >>> from django.contrib.contenttypes.models import ContentType - >>> user_type = ContentType.objects.get(app_label="auth", model="user") - >>> user_type + >>> ContentType.objects.get(app_label="auth", model="user") And then use it to query for a particular @@ -229,8 +226,7 @@ want to go to the trouble of obtaining the model's metadata to perform a manual lookup:: >>> from django.contrib.auth.models import User - >>> user_type = ContentType.objects.get_for_model(User) - >>> user_type + >>> ContentType.objects.get_for_model(User) .. module:: django.contrib.contenttypes.fields @@ -374,6 +370,9 @@ Reverse generic relations If you know which models you'll be using most often, you can also add a "reverse" generic relationship to enable an additional API. For example:: + from django.db import models + from django.contrib.contenttypes.fields import GenericRelation + class Bookmark(models.Model): url = models.URLField() tags = GenericRelation(TaggedItem) @@ -469,7 +468,6 @@ to the queryset to ensure the correct content type, but the into account. For now, if you need aggregates on generic relations, you'll need to calculate them without using the aggregation API. - .. module:: django.contrib.contenttypes.forms Generic relation in forms From d6632f21113eae0d537dd406a2369938ffc93490 Mon Sep 17 00:00:00 2001 From: David Sanders Date: Tue, 29 Sep 2015 12:09:57 +1000 Subject: [PATCH 030/756] [1.9.x] Clarified that Model.delete() isn't called as a result of a cascading delete. Backport of 5c6316dc3467fd7b669bea9157f591ac5fd00ec4 from master --- docs/topics/db/models.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 53d2396f0385..99945d428774 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -835,9 +835,11 @@ code will automatically support those arguments when they are added. Note that the :meth:`~Model.delete()` method for an object is not necessarily called when :ref:`deleting objects in bulk using a - QuerySet`. To ensure customized delete logic - gets executed, you can use :data:`~django.db.models.signals.pre_delete` - and/or :data:`~django.db.models.signals.post_delete` signals. + QuerySet ` or as a result of a :attr:`cascading + delete `. To ensure customized + delete logic gets executed, you can use + :data:`~django.db.models.signals.pre_delete` and/or + :data:`~django.db.models.signals.post_delete` signals. Unfortunately, there isn't a workaround when :meth:`creating` or From 3569e9d4a1e03b46b2649ca1d4fcdf9b72c552d1 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 30 Sep 2015 18:46:11 -0400 Subject: [PATCH 031/756] [1.9.x] Refs #24505 -- Forwardported 1.8.5 release note. Backport of 061801e3dfb3f88550cdaeef1a6dd1c24c13d53d from master --- docs/releases/1.8.5.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/releases/1.8.5.txt b/docs/releases/1.8.5.txt index 14893f4a7206..b951ff98d814 100644 --- a/docs/releases/1.8.5.txt +++ b/docs/releases/1.8.5.txt @@ -52,3 +52,7 @@ Bugfixes (:ticket:`24921`). * Fixed the ``manage.py test --keepdb`` option on Oracle (:ticket:`25421`). + +* Fixed incorrect queries with multiple many-to-many fields on a model with the + same 'to' model and with ``related_name`` set to '+' (:ticket:`24505`, + :ticket:`25486`). From 05968192f74d1bd36c3eb778a4a3274dd78dd531 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Oct 2015 14:51:22 -0400 Subject: [PATCH 032/756] [1.9.x] Removed an old note (Django 1.4-era) from the tutorial. Backport of 1515b6a8b375612addc0019d537fcbf0349d942c from master --- docs/intro/tutorial02.txt | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index a92b75727920..fbf15e8d1b49 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -643,19 +643,6 @@ Since :doc:`translation ` is turned on by default, the login screen may be displayed in your own language, depending on your browser's settings and if Django has a translation for this language. -.. admonition:: Doesn't match what you see? - - If at this point, instead of the above login page, you get an error - page reporting something like:: - - ImportError at /admin/ - cannot import name patterns - ... - - then you're probably using a version of Django that doesn't match this - tutorial version. You'll want to either switch to the older tutorial or the - newer Django version. - Enter the admin site -------------------- From 1084b421f1b2db1e3a0bcc04ca86ecf4823c9c5b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Oct 2015 15:17:39 -0400 Subject: [PATCH 033/756] [1.9.x] Made a couple tutorial corrections for Django 1.9. Backport of 8a5a002f2f94eec2a6d01e3004c3e6a2ddccd432 from master --- docs/intro/tutorial01.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index dcf0a3fe092c..c7774397afd7 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -227,6 +227,7 @@ That'll create a directory :file:`polls`, which is laid out like this:: polls/ __init__.py admin.py + apps.py migrations/ __init__.py models.py @@ -261,6 +262,7 @@ Your app directory should now look like:: polls/ __init__.py admin.py + apps.py migrations/ __init__.py models.py @@ -282,8 +284,8 @@ In the ``polls/urls.py`` file include the following code: ] The next step is to point the root URLconf at the ``polls.urls`` module. In -``mysite/urls.py`` insert an :func:`~django.conf.urls.include`, leaving you -with: +``mysite/urls.py``, add an import for ``django.conf.urls.include`` and insert +an :func:`~django.conf.urls.include` in the ``urlpatterns`` list, so you have: .. snippet:: :filename: mysite/urls.py From aebe7800e809343d3c1a478628a25aebbad76df3 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 2 Oct 2015 10:32:46 -0400 Subject: [PATCH 034/756] [1.9.x] Refs #13637 -- Removed unused code in GenericRelatedObjectManager Appears unused since 585b7acaa359fc1df07269c1a4b4756bdb6703f7. Backport of 37a5a363215d7c0360ff0a8f57856d373e8c1629 from master --- django/contrib/contenttypes/fields.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py index 9d5c47da9a15..bb2565653b51 100644 --- a/django/contrib/contenttypes/fields.py +++ b/django/contrib/contenttypes/fields.py @@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType from django.core import checks from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist -from django.db import DEFAULT_DB_ALIAS, connection, models, router, transaction +from django.db import DEFAULT_DB_ALIAS, models, router, transaction from django.db.models import DO_NOTHING, signals from django.db.models.base import ModelBase, make_foreign_order_accessors from django.db.models.fields.related import ( @@ -468,12 +468,6 @@ def __init__(self, instance=None): content_type = ContentType.objects.db_manager(instance._state.db).get_for_model( instance, for_concrete_model=rel.field.for_concrete_model) self.content_type = content_type - - qn = connection.ops.quote_name - join_cols = rel.field.get_joining_columns(reverse_join=True)[0] - self.source_col_name = qn(join_cols[0]) - self.target_col_name = qn(join_cols[1]) - self.content_type_field_name = rel.field.content_type_field_name self.object_id_field_name = rel.field.object_id_field_name self.prefetch_cache_name = rel.field.attname From 1147b55e8b901971bf8caf54c729c9f81496cb7d Mon Sep 17 00:00:00 2001 From: Nick Williams Date: Fri, 2 Oct 2015 10:11:51 -0500 Subject: [PATCH 035/756] [1.9.x] Fixed link to the MySQL Connector/Python Django Backend documentation. Backport of 3f342d74e9196da475dd312cf43eaffec13af964 from master --- docs/ref/databases.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 6e0dc534df19..de54552bd05b 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -266,7 +266,7 @@ In addition to a DB API driver, Django needs an adapter to access the database drivers from its ORM. Django provides an adapter for MySQLdb/mysqlclient while MySQL Connector/Python includes `its own`_. -.. _its own: http://dev.mysql.com/doc/refman/5.6/en/connector-python-info.html +.. _its own: http://dev.mysql.com/doc/connector-python/en/connector-python-django-backend.html MySQLdb ~~~~~~~ From 6a582d35f2781db9e39fed92a3e6a9c0ccdaee60 Mon Sep 17 00:00:00 2001 From: John Moses Date: Fri, 2 Oct 2015 09:56:25 -0500 Subject: [PATCH 036/756] [1.9.x] Fixed #25481 -- Added field.help_text to "Looping over a form's fields" docs. Backport of ac09d22f7913ff09f98001ff51b783932290e378 from master --- AUTHORS | 1 + docs/topics/forms/index.txt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/AUTHORS b/AUTHORS index db08e662ccc4..d51c84d3d6f7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -353,6 +353,7 @@ answer newbie questions, and generally made Django that much better: john@calixto.net John D'Agostino John Huddleston + John Moses John Paulett John Shaffer Jökull Sólberg Auðunsson diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index c9cce5121f66..0217b022d166 100644 --- a/docs/topics/forms/index.txt +++ b/docs/topics/forms/index.txt @@ -635,6 +635,9 @@ loop:
    {{ field.errors }} {{ field.label_tag }} {{ field }} + {% if field.help_text %} +

    {{ field.help_text|safe }}

    + {% endif %}
    {% endfor %} From 0633d2285e26d844104f944b95c4fca3e5137651 Mon Sep 17 00:00:00 2001 From: Andrew Artajos Date: Sat, 3 Oct 2015 15:58:29 +1000 Subject: [PATCH 037/756] [1.9.x] Fixed #25398 -- Revised instances of os.path.join() Replaced occurrences of os.path.join(BASE_DIR, 'folder/subfolder') to os.path.join(BASE_DIR, 'folder', 'subfolder') Backport of d2ef521a53 from master. --- docs/ref/contrib/gis/tutorial.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt index 5d5fb6e770be..bf2bc28d9b82 100644 --- a/docs/ref/contrib/gis/tutorial.txt +++ b/docs/ref/contrib/gis/tutorial.txt @@ -327,7 +327,7 @@ tutorial, then you can determine its path using Python's built-in >>> import os >>> import world >>> world_shp = os.path.abspath(os.path.join(os.path.dirname(world.__file__), - ... 'data/TM_WORLD_BORDERS-0.3.shp')) + ... 'data', 'TM_WORLD_BORDERS-0.3.shp')) Now, open the world borders shapefile using GeoDjango's :class:`~django.contrib.gis.gdal.DataSource` interface:: @@ -452,7 +452,7 @@ with the following code:: 'mpoly' : 'MULTIPOLYGON', } - world_shp = os.path.abspath(os.path.join(os.path.dirname(__file__), 'data/TM_WORLD_BORDERS-0.3.shp')) + world_shp = os.path.abspath(os.path.join(os.path.dirname(__file__), 'data', 'TM_WORLD_BORDERS-0.3.shp')) def run(verbose=True): lm = LayerMapping(WorldBorder, world_shp, world_mapping, From 63a1e912a78a4a18ca675ecc0db5be6f48985af5 Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Fri, 11 Sep 2015 23:06:25 -0700 Subject: [PATCH 038/756] [1.9.x] Fixed #25389 -- Fixed pickling a SimpleLazyObject wrapping a model. Pickling a `SimpleLazyObject` wrapping a model did not work correctly; in particular it did not add the `_django_version` attribute added in 42736ac8. Now it will handle this and other custom `__reduce__` methods correctly. Backport of 35355a4ffedb2aeed52d5fe3034380ffc6a438db from master --- django/utils/functional.py | 56 +++++++++-------- docs/releases/1.8.5.txt | 2 + tests/utils_tests/models.py | 4 ++ tests/utils_tests/test_lazyobject.py | 92 ++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 25 deletions(-) diff --git a/django/utils/functional.py b/django/utils/functional.py index ee0a1953ddfd..c487cd8517ec 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -3,7 +3,6 @@ from functools import total_ordering, wraps from django.utils import six -from django.utils.six.moves import copyreg # You can't trivially replace this with `functools.partial` because this binds @@ -247,32 +246,30 @@ def _setup(self): raise NotImplementedError('subclasses of LazyObject must provide a _setup() method') # Because we have messed with __class__ below, we confuse pickle as to what - # class we are pickling. It also appears to stop __reduce__ from being - # called. So, we define __getstate__ in a way that cooperates with the way - # that pickle interprets this class. This fails when the wrapped class is - # a builtin, but it is better than nothing. - def __getstate__(self): + # class we are pickling. We're going to have to initialize the wrapped + # object to successfully pickle it, so we might as well just pickle the + # wrapped object since they're supposed to act the same way. + # + # Unfortunately, if we try to simply act like the wrapped object, the ruse + # will break down when pickle gets our id(). Thus we end up with pickle + # thinking, in effect, that we are a distinct object from the wrapped + # object, but with the same __dict__. This can cause problems (see #25389). + # + # So instead, we define our own __reduce__ method and custom unpickler. We + # pickle the wrapped object as the unpickler's argument, so that pickle + # will pickle it normally, and then the unpickler simply returns its + # argument. + def __reduce__(self): if self._wrapped is empty: self._setup() - return self._wrapped.__dict__ - - # Python 3 will call __reduce__ when pickling; this method is needed - # to serialize and deserialize correctly. - @classmethod - def __newobj__(cls, *args): - return cls.__new__(cls, *args) - - def __reduce_ex__(self, proto): - if proto >= 2: - # On Py3, since the default protocol is 3, pickle uses the - # ``__newobj__`` method (& more efficient opcodes) for writing. - return (self.__newobj__, (self.__class__,), self.__getstate__()) - else: - # On Py2, the default protocol is 0 (for back-compat) & the above - # code fails miserably (see regression test). Instead, we return - # exactly what's returned if there's no ``__reduce__`` method at - # all. - return (copyreg._reconstructor, (self.__class__, object, None), self.__getstate__()) + return (unpickle_lazyobject, (self._wrapped,)) + + # We have to explicitly override __getstate__ so that older versions of + # pickle don't try to pickle the __dict__ (which in the case of a + # SimpleLazyObject may contain a lambda). The value will end up being + # ignored by our __reduce__ and custom unpickler. + def __getstate__(self): + return {} def __deepcopy__(self, memo): if self._wrapped is empty: @@ -311,6 +308,15 @@ def __deepcopy__(self, memo): __contains__ = new_method_proxy(operator.contains) +def unpickle_lazyobject(wrapped): + """ + Used to unpickle lazy objects. Just return its argument, which will be the + wrapped object. + """ + return wrapped +unpickle_lazyobject.__safe_for_unpickling__ = True + + # Workaround for http://bugs.python.org/issue12370 _super = super diff --git a/docs/releases/1.8.5.txt b/docs/releases/1.8.5.txt index b951ff98d814..93a5ec2ec0f4 100644 --- a/docs/releases/1.8.5.txt +++ b/docs/releases/1.8.5.txt @@ -56,3 +56,5 @@ Bugfixes * Fixed incorrect queries with multiple many-to-many fields on a model with the same 'to' model and with ``related_name`` set to '+' (:ticket:`24505`, :ticket:`25486`). + +* Fixed pickling a ``SimpleLazyObject`` wrapping a model (:ticket:`25389`). diff --git a/tests/utils_tests/models.py b/tests/utils_tests/models.py index 700fcfcaf42e..97e9a97ef857 100644 --- a/tests/utils_tests/models.py +++ b/tests/utils_tests/models.py @@ -11,3 +11,7 @@ def next(self): class Thing(models.Model): name = models.CharField(max_length=100) category = models.ForeignKey(Category, models.CASCADE) + + +class CategoryInfo(models.Model): + category = models.OneToOneField(Category, models.CASCADE) diff --git a/tests/utils_tests/test_lazyobject.py b/tests/utils_tests/test_lazyobject.py index ce2b66f3cb0c..e0f043318c97 100644 --- a/tests/utils_tests/test_lazyobject.py +++ b/tests/utils_tests/test_lazyobject.py @@ -3,11 +3,14 @@ import copy import pickle import sys +import warnings from unittest import TestCase from django.utils import six from django.utils.functional import LazyObject, SimpleLazyObject, empty +from .models import Category, CategoryInfo + class Foo(object): """ @@ -284,3 +287,92 @@ def test_list_set(self): self.assertNotIn(6, lazy_set) self.assertEqual(len(lazy_list), 5) self.assertEqual(len(lazy_set), 4) + + +class BaseBaz(object): + """ + A base class with a funky __reduce__ method, meant to simulate the + __reduce__ method of Model, which sets self._django_version. + """ + def __init__(self): + self.baz = 'wrong' + + def __reduce__(self): + self.baz = 'right' + return super(BaseBaz, self).__reduce__() + + def __eq__(self, other): + if self.__class__ != other.__class__: + return False + for attr in ['bar', 'baz', 'quux']: + if hasattr(self, attr) != hasattr(other, attr): + return False + elif getattr(self, attr, None) != getattr(other, attr, None): + return False + return True + + +class Baz(BaseBaz): + """ + A class that inherits from BaseBaz and has its own __reduce_ex__ method. + """ + def __init__(self, bar): + self.bar = bar + super(Baz, self).__init__() + + def __reduce_ex__(self, proto): + self.quux = 'quux' + return super(Baz, self).__reduce_ex__(proto) + + +class BazProxy(Baz): + """ + A class that acts as a proxy for Baz. It does some scary mucking about with + dicts, which simulates some crazy things that people might do with + e.g. proxy models. + """ + def __init__(self, baz): + self.__dict__ = baz.__dict__ + self._baz = baz + super(BaseBaz, self).__init__() + + +class SimpleLazyObjectPickleTestCase(TestCase): + """ + Regression test for pickling a SimpleLazyObject wrapping a model (#25389). + Also covers other classes with a custom __reduce__ method. + """ + def test_pickle_with_reduce(self): + """ + Test in a fairly synthetic setting. + """ + # Test every pickle protocol available + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): + lazy_objs = [ + SimpleLazyObject(lambda: BaseBaz()), + SimpleLazyObject(lambda: Baz(1)), + SimpleLazyObject(lambda: BazProxy(Baz(2))), + ] + for obj in lazy_objs: + pickled = pickle.dumps(obj, protocol) + unpickled = pickle.loads(pickled) + self.assertEqual(unpickled, obj) + self.assertEqual(unpickled.baz, 'right') + + def test_pickle_model(self): + """ + Test on an actual model, based on the report in #25426. + """ + category = Category.objects.create(name="thing1") + CategoryInfo.objects.create(category=category) + # Test every pickle protocol available + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): + lazy_category = SimpleLazyObject(lambda: category) + # Test both if we accessed a field on the model and if we didn't. + lazy_category.categoryinfo + lazy_category_2 = SimpleLazyObject(lambda: category) + with warnings.catch_warnings(record=True) as recorded: + self.assertEqual(pickle.loads(pickle.dumps(lazy_category, protocol)), category) + self.assertEqual(pickle.loads(pickle.dumps(lazy_category_2, protocol)), category) + # Assert that there were no warnings. + self.assertEqual(len(recorded), 0) From c5268b1a74ab78c71131eac6fe8d9b8bef00bac6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 3 Oct 2015 19:31:45 -0400 Subject: [PATCH 039/756] [1.9.x] Added release date for 1.8.5. Backport of 2eb5589a721488b592ed05d6790d9ad7d2b9dafd from master --- docs/releases/1.8.5.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.8.5.txt b/docs/releases/1.8.5.txt index 93a5ec2ec0f4..5444bed90206 100644 --- a/docs/releases/1.8.5.txt +++ b/docs/releases/1.8.5.txt @@ -2,7 +2,7 @@ Django 1.8.5 release notes ========================== -*Under development* +*October 3, 2015* Django 1.8.5 fixes several bugs in 1.8.4. From 9b91c7d99d84d0eb4931d03a3bd98ceb502fa4a6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 3 Oct 2015 19:58:02 -0400 Subject: [PATCH 040/756] [1.9.x] Added stub release notes for 1.8.6. Backport of 58195f0b16999245ada6bd010b71c9c5352ae608 from master --- docs/releases/1.8.6.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.8.6.txt diff --git a/docs/releases/1.8.6.txt b/docs/releases/1.8.6.txt new file mode 100644 index 000000000000..6268bb3bbfde --- /dev/null +++ b/docs/releases/1.8.6.txt @@ -0,0 +1,12 @@ +========================== +Django 1.8.6 release notes +========================== + +*Under Development* + +Django 1.8.6 fixes several bugs in 1.8.5. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 059859c8fee7..0c7e7badc2a4 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -32,6 +32,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.8.6 1.8.5 1.8.4 1.8.3 From 4b95f1b0fba1fd9f184de247f96703e664f7f77c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 5 Oct 2015 08:25:49 -0400 Subject: [PATCH 041/756] [1.9.x] Removed SimpleLazyObject workaround for a Python bug. Backport of 4accffd8fdaa59597dc40b47be031e7116c5b2a5 from master --- django/utils/functional.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/django/utils/functional.py b/django/utils/functional.py index c487cd8517ec..3df8c2f5c816 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -317,10 +317,6 @@ def unpickle_lazyobject(wrapped): unpickle_lazyobject.__safe_for_unpickling__ = True -# Workaround for http://bugs.python.org/issue12370 -_super = super - - class SimpleLazyObject(LazyObject): """ A lazy object initialized from any function. @@ -338,7 +334,7 @@ def __init__(self, func): value. """ self.__dict__['_setupfunc'] = func - _super(SimpleLazyObject, self).__init__() + super(SimpleLazyObject, self).__init__() def _setup(self): self._wrapped = self._setupfunc() From 9f31439e67989bfbe4ccdecf79b379b28351ceee Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 5 Oct 2015 09:16:53 -0400 Subject: [PATCH 042/756] [1.9.x] Fixed #25502 -- Added supported Python versions in 1.7, 1.8 release notes. Backport of b215a3ab63d1c34746855594656e988561484fe3 from master --- docs/releases/1.7.txt | 8 +++++--- docs/releases/1.8.txt | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index ff6043a3328f..9ccbd815e107 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -20,9 +20,11 @@ and some features have reached the end of their deprecation process and Python compatibility ==================== -Django 1.7 requires Python 2.7 or above, though we **highly recommend** -the latest minor release. Support for Python 2.6 has been dropped and support -for Python 3.4 has been added. +Django 1.7 requires Python 2.7, 3.2, 3.3, or 3.4. We **highly recommend** and +only officially support the latest release of each series. + +Since Django 1.6, support for Python 2.6 has been dropped and support for +Python 3.4 has been added. This change should affect only a small number of Django users, as most operating-system vendors today are shipping Python 2.7 or newer as their default diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index a2d98656b6c2..79d387fc2f55 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -25,8 +25,8 @@ release date of Django 1.8. Python compatibility ==================== -Like Django 1.7, Django 1.8 requires Python 2.7 or above, though we -**highly recommend** the latest minor release. +Like Django 1.7, Django 1.8 requires Python 2.7, 3.2, 3.3, or 3.4. We **highly +recommend** and only officially support the latest release of each series. What's new in Django 1.8 ======================== From 3429dfe11df8c7ac59255320c6d34609570345c3 Mon Sep 17 00:00:00 2001 From: Pindi Albert Date: Sun, 4 Oct 2015 10:50:18 -0700 Subject: [PATCH 043/756] [1.9.x] Fixed #25423 -- Made error message for unknown template tag more helpful. Backport of 9f2881deb127593e8e0fa25e978aad9029d7b562 from master --- django/template/base.py | 9 +++++++-- tests/template_tests/tests.py | 17 ++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index 530b60a3a913..9e8f5fa7adc9 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -555,13 +555,18 @@ def invalid_block_tag(self, token, command, parse_until=None): if parse_until: raise self.error( token, - "Invalid block tag on line %d: '%s', expected %s" % ( + "Invalid block tag on line %d: '%s', expected %s. Did you " + "forget to register or load this tag?" % ( token.lineno, command, get_text_list(["'%s'" % p for p in parse_until]), ), ) - raise self.error(token, "Invalid block tag on line %d: '%s'" % (token.lineno, command)) + raise self.error( + token, + "Invalid block tag on line %d: '%s'. Did you forget to register " + "or load this tag?" % (token.lineno, command) + ) def unclosed_block_tag(self, parse_until): command, token = self.command_stack.pop() diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py index a6aac5b81a7e..38d7eaa8bb95 100644 --- a/tests/template_tests/tests.py +++ b/tests/template_tests/tests.py @@ -68,14 +68,21 @@ def test_invalid_block_suggestion(self): #7876 -- Error messages should include the unexpected block name. """ engine = Engine() - - with self.assertRaises(TemplateSyntaxError) as e: + msg = ( + "Invalid block tag on line 1: 'endblock', expected 'elif', 'else' " + "or 'endif'. Did you forget to register or load this tag?" + ) + with self.assertRaisesMessage(TemplateSyntaxError, msg): engine.from_string("{% if 1 %}lala{% endblock %}{% endif %}") - self.assertEqual( - e.exception.args[0], - "Invalid block tag on line 1: 'endblock', expected 'elif', 'else' or 'endif'", + def test_unknown_block_tag(self): + engine = Engine() + msg = ( + "Invalid block tag on line 1: 'foobar'. Did you forget to " + "register or load this tag?" ) + with self.assertRaisesMessage(TemplateSyntaxError, msg): + engine.from_string("lala{% foobar %}") def test_compile_filter_expression_error(self): """ From 6bc8bdf55ae71482ee3cd6c1ab157404c2e4ec5c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 5 Oct 2015 18:21:56 -0400 Subject: [PATCH 044/756] [1.9.x] Fixed #25496 -- Made ModelChoiceField respect prefetch_related(). Backport of 6afa6818fcf25665bbf61f0921c8c8c6fa8f223e from master --- django/forms/models.py | 4 +++- docs/releases/1.8.6.txt | 3 ++- tests/model_forms/tests.py | 23 +++++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/django/forms/models.py b/django/forms/models.py index ac8bd1c8608f..938535bf6aae 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -1102,7 +1102,9 @@ def __init__(self, field): def __iter__(self): if self.field.empty_label is not None: yield ("", self.field.empty_label) - for obj in self.queryset.iterator(): + # Can't use iterator() when queryset uses prefetch_related() + method = 'all' if self.queryset._prefetch_related_lookups else 'iterator' + for obj in getattr(self.queryset, method)(): yield self.choice(obj) def __len__(self): diff --git a/docs/releases/1.8.6.txt b/docs/releases/1.8.6.txt index 6268bb3bbfde..9292d7835bfe 100644 --- a/docs/releases/1.8.6.txt +++ b/docs/releases/1.8.6.txt @@ -9,4 +9,5 @@ Django 1.8.6 fixes several bugs in 1.8.5. Bugfixes ======== -* ... +* Fixed a regression causing ``ModelChoiceField`` to ignore + ``prefetch_related()`` on its queryset (:ticket:`25496`). diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index 98b9c1ad5258..12401759bd00 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -2285,6 +2285,29 @@ def test_choices_type(self): with self.assertRaises(ValidationError): f.fields['status'].clean('z') + def test_prefetch_related_queryset(self): + """ + ModelChoiceField should respect a prefetch_related() on its queryset. + """ + blue = Colour.objects.create(name='blue') + red = Colour.objects.create(name='red') + multicolor_item = ColourfulItem.objects.create() + multicolor_item.colours.add(blue, red) + red_item = ColourfulItem.objects.create() + red_item.colours.add(red) + + class ColorModelChoiceField(forms.ModelChoiceField): + def label_from_instance(self, obj): + return ', '.join(c.name for c in obj.colours.all()) + + field = ColorModelChoiceField(ColourfulItem.objects.prefetch_related('colours')) + with self.assertNumQueries(4): # would be 5 if prefetch is ignored + self.assertEqual(tuple(field.choices), ( + ('', '---------'), + (multicolor_item.pk, 'blue, red'), + (red_item.pk, 'red'), + )) + def test_foreignkeys_which_use_to_field(self): apple = Inventory.objects.create(barcode=86, name='Apple') Inventory.objects.create(barcode=22, name='Pear') From 6f653f759ac2ba735bf8fa3fbb634a431288c041 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 5 Oct 2015 19:25:06 +0200 Subject: [PATCH 045/756] [1.9.x] Refs #12118 -- Allowed "mode=memory" in SQLite test database names. Backport of 3543fec3b739864c52de0a116dde3b0e5e885799 from master --- django/db/backends/sqlite3/creation.py | 8 +++++--- docs/releases/1.8.6.txt | 3 +++ tests/backends/tests.py | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py index a6e3155aa81d..9f1598e3ee2a 100644 --- a/django/db/backends/sqlite3/creation.py +++ b/django/db/backends/sqlite3/creation.py @@ -11,14 +11,16 @@ class DatabaseCreation(BaseDatabaseCreation): def _get_test_db_name(self): test_database_name = self.connection.settings_dict['TEST']['NAME'] + can_share_in_memory_db = self.connection.features.can_share_in_memory_db if test_database_name and test_database_name != ':memory:': - if 'mode=memory' in test_database_name: + if 'mode=memory' in test_database_name and not can_share_in_memory_db: raise ImproperlyConfigured( - "Using `mode=memory` parameter in the database name is not allowed, " + "Using a shared memory database with `mode=memory` in the " + "database name is not supported in your environment, " "use `:memory:` instead." ) return test_database_name - if self.connection.features.can_share_in_memory_db: + if can_share_in_memory_db: return 'file:memorydb_%s?mode=memory&cache=shared' % self.connection.alias return ':memory:' diff --git a/docs/releases/1.8.6.txt b/docs/releases/1.8.6.txt index 9292d7835bfe..15b85a66c8da 100644 --- a/docs/releases/1.8.6.txt +++ b/docs/releases/1.8.6.txt @@ -11,3 +11,6 @@ Bugfixes * Fixed a regression causing ``ModelChoiceField`` to ignore ``prefetch_related()`` on its queryset (:ticket:`25496`). + +* Allowed "mode=memory" in SQLite test database name if supported + (:ticket:`12118`). diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 39a2654a7781..6237e860fb2e 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -142,6 +142,30 @@ def test_aggregation(self): models.Item.objects.all().aggregate, **{'complex': aggregate('last_modified') + aggregate('last_modified')}) + def test_memory_db_test_name(self): + """ + A named in-memory db should be allowed where supported. + """ + from django.db.backends.sqlite3.base import DatabaseWrapper + settings_dict = { + 'TEST': { + 'NAME': 'file:memorydb_test?mode=memory&cache=shared', + } + } + wrapper = DatabaseWrapper(settings_dict) + creation = wrapper.creation + if creation.connection.features.can_share_in_memory_db: + expected = creation.connection.settings_dict['TEST']['NAME'] + self.assertEqual(creation._get_test_db_name(), expected) + else: + msg = ( + "Using a shared memory database with `mode=memory` in the " + "database name is not supported in your environment, " + "use `:memory:` instead." + ) + with self.assertRaisesMessage(ImproperlyConfigured, msg): + creation._get_test_db_name() + @unittest.skipUnless(connection.vendor == 'postgresql', "Test only for PostgreSQL") class PostgreSQLTests(TestCase): From 2f9bc9933417d4a2b4b0196cfd9e7e886500e98d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 Oct 2015 13:25:14 -0400 Subject: [PATCH 046/756] [1.9.x] Fixed #25516 -- Documented that parallel test excution doesn't work with pdb. Backport of cb15ceb5555de0799210a0ce37e744fd4f284b36 from master --- docs/ref/django-admin.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 18e8ec1ca05f..492ec50b30bf 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1300,6 +1300,10 @@ correctly: This feature isn't available on Windows. It doesn't work with the Oracle database backend either. +If you want to use :mod:`pdb` while debugging tests, you must disable parallel +execution (``--parallel=1``). You'll see something like ``bdb.BdbQuit`` if you +don't. + .. warning:: When test parallelization is enabled and a test fails, Django may be From d81b0812d43b8c068c8e65b19c1fa9dbdcb1da47 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 Oct 2015 15:08:15 -0400 Subject: [PATCH 047/756] [1.9.x] Fixed #25515 -- Documented the return value of BaseCommand.handle() Backport of 3f766d44c5ea000ecf47829ca565b1921dab2ac1 from master --- docs/howto/custom-management-commands.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index d5dd800a9475..833bc3840d88 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -349,6 +349,9 @@ the :meth:`~BaseCommand.handle` method must be implemented. The actual logic of the command. Subclasses must implement this method. + It may return a Unicode string which will be printed to ``stdout`` (wrapped + by ``BEGIN;`` and ``COMMIT;`` if :attr:`output_transaction` is ``True``). + .. method:: BaseCommand.check(app_configs=None, tags=None, display_num_errors=False) Uses the system check framework to inspect the entire Django project for From fbe89307455e939bc5c8b42133a3140aefd1f1f2 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 5 Oct 2015 22:29:23 +0200 Subject: [PATCH 048/756] [1.9.x] Fixed #25503 -- Fixed system check crash on ForeignKey to abstract model. Backport of 914167abf19d16ac97c0f1f6ae1b08cb377c8f3a from master --- AUTHORS | 1 + django/db/models/fields/related.py | 5 +- docs/releases/1.8.6.txt | 3 + .../test_relative_fields.py | 56 ++++++++++--------- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/AUTHORS b/AUTHORS index d51c84d3d6f7..1f72b2583667 100644 --- a/AUTHORS +++ b/AUTHORS @@ -449,6 +449,7 @@ answer newbie questions, and generally made Django that much better: Marian Andre Marijn Vriens Mario Gonzalez + Mariusz Felisiak Mark Biggers mark@junklight.com Mark Lavin diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 6b563360f544..d3f5874409d5 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -470,6 +470,9 @@ def _check_unique_target(self): except exceptions.FieldDoesNotExist: return [] + if not self.foreign_related_fields: + return [] + has_unique_field = any(rel_field.unique for rel_field in self.foreign_related_fields) if not has_unique_field and len(self.foreign_related_fields) > 1: @@ -572,7 +575,7 @@ def local_related_fields(self): @property def foreign_related_fields(self): - return tuple(rhs_field for lhs_field, rhs_field in self.related_fields) + return tuple(rhs_field for lhs_field, rhs_field in self.related_fields if rhs_field) def get_local_related_value(self, instance): return self.get_instance_value_for_fields(instance, self.local_related_fields) diff --git a/docs/releases/1.8.6.txt b/docs/releases/1.8.6.txt index 15b85a66c8da..b972ee014870 100644 --- a/docs/releases/1.8.6.txt +++ b/docs/releases/1.8.6.txt @@ -14,3 +14,6 @@ Bugfixes * Allowed "mode=memory" in SQLite test database name if supported (:ticket:`12118`). + +* Fixed system check crash on ``ForeignKey`` to abstract model + (:ticket:`25503`). diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py index 04dca8b35e63..ad1c25da5237 100644 --- a/tests/invalid_models_tests/test_relative_fields.py +++ b/tests/invalid_models_tests/test_relative_fields.py @@ -386,25 +386,27 @@ class Relationship(models.Model): self.assertEqual(errors, expected) def test_foreign_key_to_abstract_model(self): - class Model(models.Model): - foreign_key = models.ForeignKey('AbstractModel', models.CASCADE) - class AbstractModel(models.Model): class Meta: abstract = True - field = Model._meta.get_field('foreign_key') - errors = field.check() - expected = [ - Error( - "Field defines a relation with model 'AbstractModel', " - "which is either not installed, or is abstract.", - hint=None, - obj=field, - id='fields.E300', - ), + class Model(models.Model): + rel_string_foreign_key = models.ForeignKey('AbstractModel', models.CASCADE) + rel_class_foreign_key = models.ForeignKey(AbstractModel, models.CASCADE) + + fields = [ + Model._meta.get_field('rel_string_foreign_key'), + Model._meta.get_field('rel_class_foreign_key'), ] - self.assertEqual(errors, expected) + expected_error = Error( + "Field defines a relation with model 'AbstractModel', " + "which is either not installed, or is abstract.", + id='fields.E300', + ) + for field in fields: + expected_error.obj = field + errors = field.check() + self.assertEqual(errors, [expected_error]) def test_m2m_to_abstract_model(self): class AbstractModel(models.Model): @@ -412,20 +414,22 @@ class Meta: abstract = True class Model(models.Model): - m2m = models.ManyToManyField('AbstractModel') + rel_string_m2m = models.ManyToManyField('AbstractModel') + rel_class_m2m = models.ManyToManyField(AbstractModel) - field = Model._meta.get_field('m2m') - errors = field.check(from_model=Model) - expected = [ - Error( - "Field defines a relation with model 'AbstractModel', " - "which is either not installed, or is abstract.", - hint=None, - obj=field, - id='fields.E300', - ), + fields = [ + Model._meta.get_field('rel_string_m2m'), + Model._meta.get_field('rel_class_m2m'), ] - self.assertEqual(errors, expected) + expected_error = Error( + "Field defines a relation with model 'AbstractModel', " + "which is either not installed, or is abstract.", + id='fields.E300', + ) + for field in fields: + expected_error.obj = field + errors = field.check(from_model=Model) + self.assertEqual(errors, [expected_error]) def test_unique_m2m(self): class Person(models.Model): From c894f215e243d9526ccc116b3ce5ec7bce3b4055 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 7 Oct 2015 10:46:08 +0200 Subject: [PATCH 049/756] [1.9.x] Added some function links in translation docs Backport of 9a6fc9606c from master. --- docs/ref/utils.txt | 6 ++++++ docs/topics/i18n/translation.txt | 11 ++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index c24551b9ec7c..1e55875150d7 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -1128,6 +1128,12 @@ For a complete discussion on the usage of the following see the ``override`` is now usable as a function decorator. +.. function:: check_for_language(lang_code) + + Checks whether there is a global language file for the given language + code (e.g. 'fr', 'pt_BR'). This is used to decide whether a user-provided + language is available. + .. function:: get_language() Returns the currently selected language code. Returns ``None`` if diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 9b46dfe830f7..6c63a2b71a5d 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1888,14 +1888,15 @@ For example:: Calling this function with the value 'de' will give you ``"Willkommen"``, regardless of :setting:`LANGUAGE_CODE` and language set by middleware. -Functions of particular interest are ``django.utils.translation.get_language()`` -which returns the language used in the current thread, -``django.utils.translation.activate()`` which activates a translation catalog -for the current thread, and ``django.utils.translation.check_for_language()`` +Functions of particular interest are +:func:`django.utils.translation.get_language()` which returns the language used +in the current thread, :func:`django.utils.translation.activate()` which +activates a translation catalog for the current thread, and +:func:`django.utils.translation.check_for_language()` which checks if the given language is supported by Django. To help write more concise code, there is also a context manager -``django.utils.translation.override()`` that stores the current language on +:func:`django.utils.translation.override()` that stores the current language on enter and restores it on exit. With it, the above example becomes:: from django.utils import translation From 8a9872543ca4f8d017c2ccccd49d9a202dd3d390 Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Wed, 7 Oct 2015 17:01:19 -0400 Subject: [PATCH 050/756] [1.9.x] Fixed #25525 -- Added instructions about importing include() in urls.py Backport of 7ce6b10e9464995a038289e00169ffa85d29e3a5 from master --- django/conf/project_template/project_name/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django/conf/project_template/project_name/urls.py b/django/conf/project_template/project_name/urls.py index c0cfc4e2a3b2..0fc13c962024 100644 --- a/django/conf/project_template/project_name/urls.py +++ b/django/conf/project_template/project_name/urls.py @@ -11,7 +11,8 @@ 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Add an import: from blog import urls as blog_urls - 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) + 2. Import the include() function: from django.conf.urls import url, include + 3. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) """ from django.conf.urls import url from django.contrib import admin From 2c11002fec389e5ee4e2c7a4ba64709a34c05c64 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 7 Oct 2015 19:35:18 -0400 Subject: [PATCH 051/756] [1.9.x] Refs #25527 -- Removed redundant doc heading link that leads to a warning when generating PDF. Backport of 25f287f6b2f677213605364c33bb37f33feac953 from master --- docs/topics/performance.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/performance.txt b/docs/topics/performance.txt index cb391c2fddb4..931d2d6fa4b6 100644 --- a/docs/topics/performance.txt +++ b/docs/topics/performance.txt @@ -275,8 +275,8 @@ warning in :class:`~django.middleware.gzip.GZipMiddleware` for more information. Sessions -------- -:ref:`Using cached sessions ` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Using cached sessions +^^^^^^^^^^^^^^^^^^^^^ :ref:`Using cached sessions ` may be a way to increase performance by eliminating the need to load session data from a slower storage From 4326ac687ed9d38d92999157f57df95380baa989 Mon Sep 17 00:00:00 2001 From: Daniel Wiesmann Date: Fri, 9 Oct 2015 12:53:11 +0100 Subject: [PATCH 052/756] [1.9.x] Fixed #25533 -- Changed datatype mapping for GDALRasters Backport of 5d8985005e from master. --- django/contrib/gis/gdal/raster/const.py | 4 ++-- tests/gis_tests/gdal_tests/test_raster.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/django/contrib/gis/gdal/raster/const.py b/django/contrib/gis/gdal/raster/const.py index dfe43673b502..38f19e294f8e 100644 --- a/django/contrib/gis/gdal/raster/const.py +++ b/django/contrib/gis/gdal/raster/const.py @@ -2,7 +2,7 @@ GDAL - Constant definitions """ from ctypes import ( - c_byte, c_double, c_float, c_int16, c_int32, c_uint16, c_uint32, + c_double, c_float, c_int16, c_int32, c_ubyte, c_uint16, c_uint32, ) # See http://www.gdal.org/gdal_8h.html#a22e22ce0a55036a96f652765793fb7a4 @@ -29,7 +29,7 @@ # or to hold the space for data to be read into. The lookup below helps # selecting the right ctypes object for a given gdal pixel type. GDAL_TO_CTYPES = [ - None, c_byte, c_uint16, c_int16, c_uint32, c_int32, + None, c_ubyte, c_uint16, c_int16, c_uint32, c_int32, c_float, c_double, None, None, None, None ] diff --git a/tests/gis_tests/gdal_tests/test_raster.py b/tests/gis_tests/gdal_tests/test_raster.py index aabec068ace1..257296a07d5a 100644 --- a/tests/gis_tests/gdal_tests/test_raster.py +++ b/tests/gis_tests/gdal_tests/test_raster.py @@ -121,6 +121,27 @@ def test_rs_bands(self): self.assertEqual(len(self.rs.bands), 1) self.assertIsInstance(self.rs.bands[0], GDALBand) + def test_memory_based_raster_creation(self): + # Create uint8 raster with full pixel data range (0-255) + rast = GDALRaster({ + 'datatype': 1, + 'width': 16, + 'height': 16, + 'srid': 4326, + 'bands': [{ + 'data': range(256), + 'nodata_value': 255, + }], + }) + + # Get array from raster + result = rast.bands[0].data() + if numpy: + result = result.flatten().tolist() + + # Assert data is same as original input + self.assertEqual(result, list(range(256))) + def test_file_based_raster_creation(self): # Prepare tempfile rstfile = tempfile.NamedTemporaryFile(suffix='.tif') From b646fbe4a77c10314ffcac31ff2f10a42e69c220 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 8 Oct 2015 18:48:57 -0400 Subject: [PATCH 053/756] [1.9.x] Fixed #14368 -- Allowed setting a reverse OneToOne relation to None. Backport of 384ddbec1b73a4636f234da3894fde8f8420bb63 from master --- .../db/models/fields/related_descriptors.py | 46 +++++++++++-------- tests/one_to_one/tests.py | 16 +++++++ 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py index 2398b166bba7..e9caa2680f98 100644 --- a/django/db/models/fields/related_descriptors.py +++ b/django/db/models/fields/related_descriptors.py @@ -376,14 +376,24 @@ def __set__(self, instance, value): # If null=True, we can assign null here, but otherwise the value needs # to be an instance of the related class. - if value is None and self.related.field.null is False: - raise ValueError( - 'Cannot assign None: "%s.%s" does not allow null values.' % ( - instance._meta.object_name, - self.related.get_accessor_name(), + if value is None: + if self.related.field.null: + # Update the cached related instance (if any) & clear the cache. + try: + rel_obj = getattr(instance, self.cache_name) + except AttributeError: + pass + else: + delattr(instance, self.cache_name) + setattr(rel_obj, self.related.field.name, None) + else: + raise ValueError( + 'Cannot assign None: "%s.%s" does not allow null values.' % ( + instance._meta.object_name, + self.related.get_accessor_name(), + ) ) - ) - elif value is not None and not isinstance(value, self.related.related_model): + elif not isinstance(value, self.related.related_model): raise ValueError( 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % ( value, @@ -392,7 +402,7 @@ def __set__(self, instance, value): self.related.related_model._meta.object_name, ) ) - elif value is not None: + else: if instance._state.db is None: instance._state.db = router.db_for_write(instance.__class__, instance=value) elif value._state.db is None: @@ -401,18 +411,18 @@ def __set__(self, instance, value): if not router.allow_relation(value, instance): raise ValueError('Cannot assign "%r": the current database router prevents this relation.' % value) - related_pk = tuple(getattr(instance, field.attname) for field in self.related.field.foreign_related_fields) - # Set the value of the related field to the value of the related object's related field - for index, field in enumerate(self.related.field.local_related_fields): - setattr(value, field.attname, related_pk[index]) + related_pk = tuple(getattr(instance, field.attname) for field in self.related.field.foreign_related_fields) + # Set the value of the related field to the value of the related object's related field + for index, field in enumerate(self.related.field.local_related_fields): + setattr(value, field.attname, related_pk[index]) - # Set the related instance cache used by __get__ to avoid a SQL query - # when accessing the attribute we just set. - setattr(instance, self.cache_name, value) + # Set the related instance cache used by __get__ to avoid a SQL query + # when accessing the attribute we just set. + setattr(instance, self.cache_name, value) - # Set the forward accessor cache on the related object to the current - # instance to avoid an extra SQL query if it's accessed later on. - setattr(value, self.related.field.get_cache_name(), instance) + # Set the forward accessor cache on the related object to the current + # instance to avoid an extra SQL query if it's accessed later on. + setattr(value, self.related.field.get_cache_name(), instance) class ReverseManyToOneDescriptor(object): diff --git a/tests/one_to_one/tests.py b/tests/one_to_one/tests.py index 25d20dc2c9e7..ee5d7415e3af 100644 --- a/tests/one_to_one/tests.py +++ b/tests/one_to_one/tests.py @@ -186,6 +186,22 @@ def test_reverse_object_cache(self): self.assertEqual(self.p1.restaurant, self.r1) self.assertEqual(self.p1.bar, self.b1) + def test_assign_none_reverse_relation(self): + p = Place.objects.get(name="Demon Dogs") + # Assigning None succeeds if field is null=True. + ug_bar = UndergroundBar.objects.create(place=p, serves_cocktails=False) + p.undergroundbar = None + self.assertIsNone(ug_bar.place) + ug_bar.save() + ug_bar.refresh_from_db() + self.assertIsNone(ug_bar.place) + + def test_assign_none_null_reverse_relation(self): + p = Place.objects.get(name="Demon Dogs") + # Assigning None doesn't throw AttributeError if there isn't a related + # UndergroundBar. + p.undergroundbar = None + def test_related_object_cache(self): """ Regression test for #6886 (the related-object cache) """ From f717cb2ab4accd9e808449bb0a59b6c5b35059f1 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 9 Oct 2015 18:00:06 +0200 Subject: [PATCH 054/756] [1.9.x] Updated translation catalogs --- django/conf/locale/en/LC_MESSAGES/django.po | 370 +++++++++--------- .../admin/locale/en/LC_MESSAGES/django.po | 198 +++++----- .../admin/locale/en/LC_MESSAGES/djangojs.po | 76 ++-- .../admindocs/locale/en/LC_MESSAGES/django.po | 98 ++--- .../auth/locale/en/LC_MESSAGES/django.po | 187 +++++---- .../flatpages/locale/en/LC_MESSAGES/django.po | 8 +- .../gis/locale/en/LC_MESSAGES/django.po | 37 +- .../postgres/locale/en/LC_MESSAGES/django.po | 27 +- .../redirects/locale/en/LC_MESSAGES/django.po | 8 +- 9 files changed, 547 insertions(+), 462 deletions(-) diff --git a/django/conf/locale/en/LC_MESSAGES/django.po b/django/conf/locale/en/LC_MESSAGES/django.po index ad4ab8b06686..9de4200e660a 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: 2015-03-18 09:16+0100\n" +"POT-Creation-Date: 2015-10-09 17:42+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -341,11 +341,11 @@ msgstr "" msgid "Vietnamese" msgstr "" -#: conf/global_settings.py:133 conf/global_settings.py:134 +#: conf/global_settings.py:133 msgid "Simplified Chinese" msgstr "" -#: conf/global_settings.py:135 conf/global_settings.py:136 +#: conf/global_settings.py:134 msgid "Traditional Chinese" msgstr "" @@ -357,71 +357,77 @@ msgstr "" msgid "Site Maps" msgstr "" -#: contrib/staticfiles/apps.py:8 +#: contrib/staticfiles/apps.py:7 msgid "Static Files" msgstr "" -#: contrib/syndication/apps.py:8 +#: contrib/syndication/apps.py:7 msgid "Syndication" msgstr "" -#: contrib/webdesign/apps.py:8 +#: contrib/webdesign/apps.py:7 msgid "Web Design" msgstr "" -#: core/validators.py:21 +#: core/validators.py:33 msgid "Enter a valid value." msgstr "" -#: core/validators.py:88 forms/fields.py:708 +#: core/validators.py:98 forms/fields.py:677 msgid "Enter a valid URL." msgstr "" -#: core/validators.py:134 +#: core/validators.py:141 msgid "Enter a valid integer." msgstr "" -#: core/validators.py:139 +#: core/validators.py:152 msgid "Enter a valid email address." msgstr "" -#: core/validators.py:213 +#: core/validators.py:225 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" -#: core/validators.py:218 core/validators.py:237 +#: core/validators.py:232 +msgid "" +"Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " +"hyphens." +msgstr "" + +#: core/validators.py:237 core/validators.py:256 msgid "Enter a valid IPv4 address." msgstr "" -#: core/validators.py:223 core/validators.py:238 +#: core/validators.py:242 core/validators.py:257 msgid "Enter a valid IPv6 address." msgstr "" -#: core/validators.py:233 core/validators.py:236 +#: core/validators.py:252 core/validators.py:255 msgid "Enter a valid IPv4 or IPv6 address." msgstr "" -#: core/validators.py:261 db/models/fields/__init__.py:1133 +#: core/validators.py:284 db/models/fields/__init__.py:1147 msgid "Enter only digits separated by commas." msgstr "" -#: core/validators.py:270 +#: core/validators.py:292 #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." msgstr "" -#: core/validators.py:296 +#: core/validators.py:318 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "" -#: core/validators.py:303 +#: core/validators.py:325 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "" -#: core/validators.py:312 +#: core/validators.py:334 #, python-format msgid "" "Ensure this value has at least %(limit_value)d character (it has " @@ -432,7 +438,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: core/validators.py:323 +#: core/validators.py:345 #, python-format msgid "" "Ensure this value has at most %(limit_value)d character (it has " @@ -443,308 +449,314 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: db/models/base.py:1089 forms/models.py:721 +#: core/validators.py:359 +#, 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] "" + +#: core/validators.py:364 +#, 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] "" + +#: core/validators.py:369 +#, python-format +msgid "" +"Ensure that there are no more than %(max)s digit before the decimal point." +msgid_plural "" +"Ensure that there are no more than %(max)s digits before the decimal point." +msgstr[0] "" +msgstr[1] "" + +#: db/models/base.py:1095 forms/models.py:727 msgid "and" msgstr "" -#: db/models/base.py:1091 +#: db/models/base.py:1097 #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." msgstr "" -#: db/models/fields/__init__.py:107 +#: db/models/fields/__init__.py:110 #, python-format msgid "Value %(value)r is not a valid choice." msgstr "" -#: db/models/fields/__init__.py:108 +#: db/models/fields/__init__.py:111 msgid "This field cannot be null." msgstr "" -#: db/models/fields/__init__.py:109 +#: db/models/fields/__init__.py:112 msgid "This field cannot be blank." msgstr "" -#: db/models/fields/__init__.py:110 +#: db/models/fields/__init__.py:113 #, python-format msgid "%(model_name)s with this %(field_label)s already exists." msgstr "" #. Translators: The 'lookup_type' is one of 'date', 'year' or 'month'. #. Eg: "Title must be unique for pub_date year" -#: db/models/fields/__init__.py:114 +#: db/models/fields/__init__.py:117 #, python-format msgid "" "%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." msgstr "" -#: db/models/fields/__init__.py:132 +#: db/models/fields/__init__.py:134 #, python-format msgid "Field of type: %(field_type)s" msgstr "" -#: db/models/fields/__init__.py:911 db/models/fields/__init__.py:1814 +#: db/models/fields/__init__.py:930 db/models/fields/__init__.py:1834 msgid "Integer" msgstr "" -#: db/models/fields/__init__.py:915 db/models/fields/__init__.py:1812 +#: db/models/fields/__init__.py:934 db/models/fields/__init__.py:1832 #, python-format msgid "'%(value)s' value must be an integer." msgstr "" -#: db/models/fields/__init__.py:990 +#: db/models/fields/__init__.py:1009 #, python-format msgid "'%(value)s' value must be either True or False." msgstr "" -#: db/models/fields/__init__.py:992 +#: db/models/fields/__init__.py:1011 msgid "Boolean (Either True or False)" msgstr "" -#: db/models/fields/__init__.py:1067 +#: db/models/fields/__init__.py:1086 #, python-format msgid "String (up to %(max_length)s)" msgstr "" -#: db/models/fields/__init__.py:1128 +#: db/models/fields/__init__.py:1142 msgid "Comma-separated integers" msgstr "" -#: db/models/fields/__init__.py:1177 +#: db/models/fields/__init__.py:1191 #, python-format msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" -#: db/models/fields/__init__.py:1179 db/models/fields/__init__.py:1329 +#: db/models/fields/__init__.py:1193 db/models/fields/__init__.py:1336 #, python-format msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" -#: db/models/fields/__init__.py:1182 +#: db/models/fields/__init__.py:1196 msgid "Date (without time)" msgstr "" -#: db/models/fields/__init__.py:1327 +#: db/models/fields/__init__.py:1334 #, python-format msgid "" "'%(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:1331 +#: db/models/fields/__init__.py:1338 #, python-format msgid "" "'%(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:1335 +#: db/models/fields/__init__.py:1342 msgid "Date (with time)" msgstr "" -#: db/models/fields/__init__.py:1487 +#: db/models/fields/__init__.py:1494 #, python-format msgid "'%(value)s' value must be a decimal number." msgstr "" -#: db/models/fields/__init__.py:1489 +#: db/models/fields/__init__.py:1496 msgid "Decimal number" msgstr "" -#: db/models/fields/__init__.py:1640 +#: db/models/fields/__init__.py:1653 #, python-format msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" -#: db/models/fields/__init__.py:1643 +#: db/models/fields/__init__.py:1656 msgid "Duration" msgstr "" -#: db/models/fields/__init__.py:1687 +#: db/models/fields/__init__.py:1707 msgid "Email address" msgstr "" -#: db/models/fields/__init__.py:1711 +#: db/models/fields/__init__.py:1731 msgid "File path" msgstr "" -#: db/models/fields/__init__.py:1778 +#: db/models/fields/__init__.py:1798 #, python-format msgid "'%(value)s' value must be a float." msgstr "" -#: db/models/fields/__init__.py:1780 +#: db/models/fields/__init__.py:1800 msgid "Floating point number" msgstr "" -#: db/models/fields/__init__.py:1881 +#: db/models/fields/__init__.py:1901 msgid "Big (8 byte) integer" msgstr "" -#: db/models/fields/__init__.py:1896 +#: db/models/fields/__init__.py:1916 msgid "IPv4 address" msgstr "" -#: db/models/fields/__init__.py:1932 +#: db/models/fields/__init__.py:1947 msgid "IP address" msgstr "" -#: db/models/fields/__init__.py:2011 +#: db/models/fields/__init__.py:2031 #, python-format msgid "'%(value)s' value must be either None, True or False." msgstr "" -#: db/models/fields/__init__.py:2013 +#: db/models/fields/__init__.py:2033 msgid "Boolean (Either True, False or None)" msgstr "" -#: db/models/fields/__init__.py:2073 +#: db/models/fields/__init__.py:2093 msgid "Positive integer" msgstr "" -#: db/models/fields/__init__.py:2085 +#: db/models/fields/__init__.py:2105 msgid "Positive small integer" msgstr "" -#: db/models/fields/__init__.py:2098 +#: db/models/fields/__init__.py:2118 #, python-format msgid "Slug (up to %(max_length)s)" msgstr "" -#: db/models/fields/__init__.py:2127 +#: db/models/fields/__init__.py:2152 msgid "Small integer" msgstr "" -#: db/models/fields/__init__.py:2134 +#: db/models/fields/__init__.py:2159 msgid "Text" msgstr "" -#: db/models/fields/__init__.py:2157 +#: db/models/fields/__init__.py:2185 #, python-format msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" -#: db/models/fields/__init__.py:2159 +#: db/models/fields/__init__.py:2187 #, python-format msgid "" "'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." msgstr "" -#: db/models/fields/__init__.py:2162 +#: db/models/fields/__init__.py:2190 msgid "Time" msgstr "" -#: db/models/fields/__init__.py:2290 +#: db/models/fields/__init__.py:2318 msgid "URL" msgstr "" -#: db/models/fields/__init__.py:2313 +#: db/models/fields/__init__.py:2341 msgid "Raw binary data" msgstr "" -#: db/models/fields/__init__.py:2357 +#: db/models/fields/__init__.py:2385 #, python-format msgid "'%(value)s' is not a valid UUID." msgstr "" -#: db/models/fields/files.py:239 +#: db/models/fields/files.py:237 msgid "File" msgstr "" -#: db/models/fields/files.py:389 +#: db/models/fields/files.py:392 msgid "Image" msgstr "" -#: db/models/fields/related.py:1798 +#: db/models/fields/related.py:723 #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." msgstr "" -#: db/models/fields/related.py:1800 +#: db/models/fields/related.py:725 msgid "Foreign Key (type determined by related field)" msgstr "" -#: db/models/fields/related.py:2013 +#: db/models/fields/related.py:983 msgid "One-to-one relationship" msgstr "" -#: db/models/fields/related.py:2103 +#: db/models/fields/related.py:1100 msgid "Many-to-many relationship" msgstr "" -#: forms/fields.py:64 +#. Translators: If found as last label character, these punctuation +#. characters will prevent the default label_suffix to be appended to the label +#: forms/boundfield.py:167 +msgid ":?.!" +msgstr "" + +#: forms/fields.py:65 msgid "This field is required." msgstr "" -#: forms/fields.py:237 +#: forms/fields.py:254 msgid "Enter a whole number." msgstr "" -#: forms/fields.py:280 forms/fields.py:317 +#: forms/fields.py:299 forms/fields.py:336 msgid "Enter a number." msgstr "" -#: forms/fields.py:319 -#, 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:323 -#, 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:327 -#, python-format -msgid "" -"Ensure that there are no more than %(max)s digit before the decimal point." -msgid_plural "" -"Ensure that there are no more than %(max)s digits before the decimal point." -msgstr[0] "" -msgstr[1] "" - -#: forms/fields.py:438 forms/fields.py:1189 +#: forms/fields.py:414 forms/fields.py:1158 msgid "Enter a valid date." msgstr "" -#: forms/fields.py:462 forms/fields.py:1190 +#: forms/fields.py:438 forms/fields.py:1159 msgid "Enter a valid time." msgstr "" -#: forms/fields.py:484 +#: forms/fields.py:460 msgid "Enter a valid date/time." msgstr "" -#: forms/fields.py:525 +#: forms/fields.py:489 msgid "Enter a valid duration." msgstr "" -#: forms/fields.py:589 +#: forms/fields.py:556 msgid "No file was submitted. Check the encoding type on the form." msgstr "" -#: forms/fields.py:590 +#: forms/fields.py:557 msgid "No file was submitted." msgstr "" -#: forms/fields.py:591 +#: forms/fields.py:558 msgid "The submitted file is empty." msgstr "" -#: forms/fields.py:593 +#: forms/fields.py:560 #, python-format msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." msgid_plural "" @@ -752,197 +764,191 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: forms/fields.py:596 +#: forms/fields.py:563 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" -#: forms/fields.py:658 +#: forms/fields.py:625 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" -#: forms/fields.py:823 forms/fields.py:917 forms/models.py:1220 +#: forms/fields.py:792 forms/fields.py:886 forms/models.py:1230 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" -#: forms/fields.py:918 forms/fields.py:1033 forms/models.py:1219 +#: forms/fields.py:887 forms/fields.py:1002 forms/models.py:1229 msgid "Enter a list of values." msgstr "" -#: forms/fields.py:1034 +#: forms/fields.py:1003 msgid "Enter a complete value." msgstr "" -#: forms/fields.py:1260 +#: forms/fields.py:1217 msgid "Enter a valid UUID." msgstr "" #. Translators: This is the default suffix added to form field labels -#: forms/forms.py:125 +#: forms/forms.py:84 msgid ":" msgstr "" -#: forms/forms.py:207 +#: forms/forms.py:191 #, python-format 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:652 -msgid ":?.!" -msgstr "" - -#: forms/formsets.py:95 +#: forms/formsets.py:97 msgid "ManagementForm data is missing or has been tampered with" msgstr "" -#: forms/formsets.py:332 +#: forms/formsets.py:345 #, python-format msgid "Please submit %d or fewer forms." msgid_plural "Please submit %d or fewer forms." msgstr[0] "" msgstr[1] "" -#: forms/formsets.py:339 +#: forms/formsets.py:352 #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." msgstr[0] "" msgstr[1] "" -#: forms/formsets.py:367 forms/formsets.py:369 +#: forms/formsets.py:380 forms/formsets.py:382 msgid "Order" msgstr "" -#: forms/formsets.py:371 +#: forms/formsets.py:384 msgid "Delete" msgstr "" -#: forms/models.py:715 +#: forms/models.py:721 #, python-format msgid "Please correct the duplicate data for %(field)s." msgstr "" -#: forms/models.py:719 +#: forms/models.py:725 #, python-format msgid "Please correct the duplicate data for %(field)s, which must be unique." msgstr "" -#: forms/models.py:725 +#: forms/models.py:731 #, 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:733 +#: forms/models.py:739 msgid "Please correct the duplicate values below." msgstr "" -#: forms/models.py:1039 +#: forms/models.py:1063 msgid "The inline foreign key did not match the parent instance primary key." msgstr "" -#: forms/models.py:1105 +#: forms/models.py:1123 msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" -#: forms/models.py:1222 +#: forms/models.py:1232 #, python-format msgid "\"%(pk)s\" is not a valid value for a primary key." msgstr "" -#: forms/utils.py:165 +#: forms/utils.py:172 #, 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:343 +#: forms/widgets.py:353 msgid "Currently" msgstr "" -#: forms/widgets.py:344 +#: forms/widgets.py:354 msgid "Change" msgstr "" -#: forms/widgets.py:345 +#: forms/widgets.py:355 msgid "Clear" msgstr "" -#: forms/widgets.py:553 +#: forms/widgets.py:570 msgid "Unknown" msgstr "" -#: forms/widgets.py:554 +#: forms/widgets.py:571 msgid "Yes" msgstr "" -#: forms/widgets.py:555 +#: forms/widgets.py:572 msgid "No" msgstr "" -#: template/defaultfilters.py:859 +#: template/defaultfilters.py:867 msgid "yes,no,maybe" msgstr "" -#: template/defaultfilters.py:888 template/defaultfilters.py:900 +#: template/defaultfilters.py:896 template/defaultfilters.py:908 #, python-format msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "" msgstr[1] "" -#: template/defaultfilters.py:902 +#: template/defaultfilters.py:910 #, python-format msgid "%s KB" msgstr "" -#: template/defaultfilters.py:904 +#: template/defaultfilters.py:912 #, python-format msgid "%s MB" msgstr "" -#: template/defaultfilters.py:906 +#: template/defaultfilters.py:914 #, python-format msgid "%s GB" msgstr "" -#: template/defaultfilters.py:908 +#: template/defaultfilters.py:916 #, python-format msgid "%s TB" msgstr "" -#: template/defaultfilters.py:910 +#: template/defaultfilters.py:918 #, python-format msgid "%s PB" msgstr "" -#: utils/dateformat.py:59 +#: utils/dateformat.py:61 msgid "p.m." msgstr "" -#: utils/dateformat.py:60 +#: utils/dateformat.py:62 msgid "a.m." msgstr "" -#: utils/dateformat.py:65 +#: utils/dateformat.py:67 msgid "PM" msgstr "" -#: utils/dateformat.py:66 +#: utils/dateformat.py:68 msgid "AM" msgstr "" -#: utils/dateformat.py:149 +#: utils/dateformat.py:151 msgid "midnight" msgstr "" -#: utils/dateformat.py:151 +#: utils/dateformat.py:153 msgid "noon" msgstr "" @@ -1222,76 +1228,76 @@ msgstr "" msgid "This is not a valid IPv6 address." msgstr "" -#: utils/text.py:79 +#: utils/text.py:77 #, python-format msgctxt "String to return when truncating text" msgid "%(truncated_text)s..." msgstr "" -#: utils/text.py:248 +#: utils/text.py:246 msgid "or" msgstr "" #. Translators: This string is used as a separator between list elements -#: utils/text.py:267 utils/timesince.py:57 +#: utils/text.py:265 utils/timesince.py:63 msgid ", " msgstr "" -#: utils/timesince.py:25 +#: utils/timesince.py:11 #, python-format msgid "%d year" msgid_plural "%d years" msgstr[0] "" msgstr[1] "" -#: utils/timesince.py:26 +#: utils/timesince.py:12 #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "" msgstr[1] "" -#: utils/timesince.py:27 +#: utils/timesince.py:13 #, python-format msgid "%d week" msgid_plural "%d weeks" msgstr[0] "" msgstr[1] "" -#: utils/timesince.py:28 +#: utils/timesince.py:14 #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "" msgstr[1] "" -#: utils/timesince.py:29 +#: utils/timesince.py:15 #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "" msgstr[1] "" -#: utils/timesince.py:30 +#: utils/timesince.py:16 #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" -#: utils/timesince.py:46 +#: utils/timesince.py:52 msgid "0 minutes" msgstr "" -#: views/csrf.py:106 +#: views/csrf.py:107 msgid "Forbidden" msgstr "" -#: views/csrf.py:107 +#: views/csrf.py:108 msgid "CSRF verification failed. Request aborted." msgstr "" -#: views/csrf.py:111 +#: views/csrf.py:112 msgid "" "You are seeing this message because this HTTPS site requires a 'Referer " "header' to be sent by your Web browser, but none was sent. This header is " @@ -1299,83 +1305,83 @@ msgid "" "hijacked by third parties." msgstr "" -#: views/csrf.py:116 +#: views/csrf.py:117 msgid "" "If you have configured your browser to disable 'Referer' headers, please re-" "enable them, at least for this site, or for HTTPS connections, or for 'same-" "origin' requests." msgstr "" -#: views/csrf.py:121 +#: views/csrf.py:122 msgid "" "You are seeing this message because this site requires a CSRF cookie when " "submitting forms. This cookie is required for security reasons, to ensure " "that your browser is not being hijacked by third parties." msgstr "" -#: views/csrf.py:126 +#: views/csrf.py:127 msgid "" "If you have configured your browser to disable cookies, please re-enable " "them, at least for this site, or for 'same-origin' requests." msgstr "" -#: views/csrf.py:131 +#: views/csrf.py:132 msgid "More information is available with DEBUG=True." msgstr "" -#: views/debug.py:582 +#: views/debug.py:508 msgid "Welcome to Django" msgstr "" -#: views/debug.py:583 +#: views/debug.py:509 msgid "It worked!" msgstr "" -#: views/debug.py:584 +#: views/debug.py:510 msgid "Congratulations on your first Django-powered page." msgstr "" -#: views/debug.py:585 +#: views/debug.py:511 msgid "" "Of course, you haven't actually done any work yet. Next, start your first " "app by running python manage.py startapp [app_label]." msgstr "" -#: views/debug.py:587 +#: views/debug.py:513 msgid "" "You're seeing this message because you have DEBUG = True in " "your Django settings file and you haven't configured any URLs. Get to work!" msgstr "" -#: views/generic/dates.py:43 +#: views/generic/dates.py:48 msgid "No year specified" msgstr "" -#: views/generic/dates.py:99 +#: views/generic/dates.py:104 msgid "No month specified" msgstr "" -#: views/generic/dates.py:158 +#: views/generic/dates.py:163 msgid "No day specified" msgstr "" -#: views/generic/dates.py:214 +#: views/generic/dates.py:219 msgid "No week specified" msgstr "" -#: views/generic/dates.py:373 views/generic/dates.py:401 +#: views/generic/dates.py:378 views/generic/dates.py:406 #, python-format msgid "No %(verbose_name_plural)s available" msgstr "" -#: views/generic/dates.py:655 +#: views/generic/dates.py:660 #, python-format msgid "" "Future %(verbose_name_plural)s not available because %(class_name)s." "allow_future is False." msgstr "" -#: views/generic/dates.py:689 +#: views/generic/dates.py:694 #, python-format msgid "Invalid date string '%(datestr)s' given format '%(format)s'" msgstr "" @@ -1399,16 +1405,16 @@ msgstr "" msgid "Empty list and '%(class_name)s.allow_empty' is False." msgstr "" -#: views/static.py:56 +#: views/static.py:58 msgid "Directory indexes are not allowed here." msgstr "" -#: views/static.py:58 +#: views/static.py:60 #, python-format msgid "\"%(path)s\" does not exist" msgstr "" -#: views/static.py:98 +#: views/static.py:100 #, python-format msgid "Index of %(directory)s" msgstr "" diff --git a/django/contrib/admin/locale/en/LC_MESSAGES/django.po b/django/contrib/admin/locale/en/LC_MESSAGES/django.po index 8bce0976762a..0fe256158d01 100644 --- a/django/contrib/admin/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/admin/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: 2015-01-17 11:07+0100\n" +"POT-Creation-Date: 2015-10-09 17:42+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -18,12 +18,12 @@ msgstr "" msgid "Successfully deleted %(count)d %(items)s." msgstr "" -#: contrib/admin/actions.py:62 contrib/admin/options.py:1722 +#: contrib/admin/actions.py:62 contrib/admin/options.py:1641 #, python-format msgid "Cannot delete %(name)s" msgstr "" -#: contrib/admin/actions.py:64 contrib/admin/options.py:1724 +#: contrib/admin/actions.py:64 contrib/admin/options.py:1643 msgid "Are you sure?" msgstr "" @@ -36,268 +36,281 @@ msgstr "" msgid "Administration" msgstr "" -#: contrib/admin/filters.py:105 contrib/admin/filters.py:203 -#: contrib/admin/filters.py:243 contrib/admin/filters.py:280 -#: contrib/admin/filters.py:396 +#: contrib/admin/filters.py:105 contrib/admin/filters.py:205 +#: contrib/admin/filters.py:241 contrib/admin/filters.py:278 +#: contrib/admin/filters.py:386 msgid "All" msgstr "" -#: contrib/admin/filters.py:244 +#: contrib/admin/filters.py:242 msgid "Yes" msgstr "" -#: contrib/admin/filters.py:245 +#: contrib/admin/filters.py:243 msgid "No" msgstr "" -#: contrib/admin/filters.py:259 +#: contrib/admin/filters.py:257 msgid "Unknown" msgstr "" -#: contrib/admin/filters.py:319 +#: contrib/admin/filters.py:317 msgid "Any date" msgstr "" -#: contrib/admin/filters.py:320 +#: contrib/admin/filters.py:318 msgid "Today" msgstr "" -#: contrib/admin/filters.py:324 +#: contrib/admin/filters.py:322 msgid "Past 7 days" msgstr "" -#: contrib/admin/filters.py:328 +#: contrib/admin/filters.py:326 msgid "This month" msgstr "" -#: contrib/admin/filters.py:332 +#: contrib/admin/filters.py:330 msgid "This year" msgstr "" -#: contrib/admin/forms.py:14 +#: contrib/admin/forms.py:13 #, python-format msgid "" "Please enter the correct %(username)s and password for a staff account. Note " "that both fields may be case-sensitive." msgstr "" -#: contrib/admin/helpers.py:27 +#: contrib/admin/helpers.py:30 msgid "Action:" msgstr "" -#: contrib/admin/models.py:30 +#: contrib/admin/models.py:34 msgid "action time" msgstr "" -#: contrib/admin/models.py:33 +#: contrib/admin/models.py:41 +msgid "user" +msgstr "" + +#: contrib/admin/models.py:46 +msgid "content type" +msgstr "" + +#: contrib/admin/models.py:49 msgid "object id" msgstr "" -#: contrib/admin/models.py:34 +#: contrib/admin/models.py:50 msgid "object repr" msgstr "" -#: contrib/admin/models.py:35 +#: contrib/admin/models.py:51 msgid "action flag" msgstr "" -#: contrib/admin/models.py:36 +#: contrib/admin/models.py:52 msgid "change message" msgstr "" -#: contrib/admin/models.py:41 +#: contrib/admin/models.py:57 msgid "log entry" msgstr "" -#: contrib/admin/models.py:42 +#: contrib/admin/models.py:58 msgid "log entries" msgstr "" -#: contrib/admin/models.py:51 +#: contrib/admin/models.py:67 #, python-format msgid "Added \"%(object)s\"." msgstr "" -#: contrib/admin/models.py:53 +#: contrib/admin/models.py:69 #, python-format msgid "Changed \"%(object)s\" - %(changes)s" msgstr "" -#: contrib/admin/models.py:58 +#: contrib/admin/models.py:74 #, python-format msgid "Deleted \"%(object)s.\"" msgstr "" -#: contrib/admin/models.py:60 +#: contrib/admin/models.py:76 msgid "LogEntry Object" msgstr "" -#: contrib/admin/options.py:228 contrib/admin/options.py:257 +#: contrib/admin/options.py:192 contrib/admin/options.py:221 msgid "None" msgstr "" -#: contrib/admin/options.py:293 +#: contrib/admin/options.py:257 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" -#: contrib/admin/options.py:1021 +#: contrib/admin/options.py:931 +msgid "Added." +msgstr "" + +#: contrib/admin/options.py:933 #, python-format msgid "Changed %s." msgstr "" -#: contrib/admin/options.py:1021 contrib/admin/options.py:1031 -#: contrib/admin/options.py:1926 +#: contrib/admin/options.py:933 contrib/admin/options.py:943 +#: contrib/admin/options.py:1848 msgid "and" msgstr "" -#: contrib/admin/options.py:1026 +#: contrib/admin/options.py:938 #, python-format msgid "Added %(name)s \"%(object)s\"." msgstr "" -#: contrib/admin/options.py:1030 +#: contrib/admin/options.py:942 #, python-format msgid "Changed %(list)s for %(name)s \"%(object)s\"." msgstr "" -#: contrib/admin/options.py:1035 +#: contrib/admin/options.py:947 #, python-format msgid "Deleted %(name)s \"%(object)s\"." msgstr "" -#: contrib/admin/options.py:1039 +#: contrib/admin/options.py:951 msgid "No fields changed." msgstr "" -#: contrib/admin/options.py:1165 contrib/admin/options.py:1221 +#: contrib/admin/options.py:1076 contrib/admin/options.py:1132 #, python-format msgid "" "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." msgstr "" -#: contrib/admin/options.py:1179 +#: contrib/admin/options.py:1090 #, python-format msgid "" "The %(name)s \"%(obj)s\" was added successfully. You may add another " "%(name)s below." msgstr "" -#: contrib/admin/options.py:1186 +#: contrib/admin/options.py:1097 #, python-format msgid "The %(name)s \"%(obj)s\" was added successfully." msgstr "" -#: contrib/admin/options.py:1214 +#: contrib/admin/options.py:1125 #, python-format msgid "" "The %(name)s \"%(obj)s\" was changed successfully. You may edit it again " "below." msgstr "" -#: contrib/admin/options.py:1231 +#: contrib/admin/options.py:1142 #, python-format msgid "" "The %(name)s \"%(obj)s\" was changed successfully. You may add another " "%(name)s below." msgstr "" -#: contrib/admin/options.py:1240 +#: contrib/admin/options.py:1151 #, python-format msgid "The %(name)s \"%(obj)s\" was changed successfully." msgstr "" -#: contrib/admin/options.py:1323 contrib/admin/options.py:1579 +#: contrib/admin/options.py:1234 contrib/admin/options.py:1498 msgid "" "Items must be selected in order to perform actions on them. No items have " "been changed." msgstr "" -#: contrib/admin/options.py:1342 +#: contrib/admin/options.py:1253 msgid "No action selected." msgstr "" -#: contrib/admin/options.py:1360 +#: contrib/admin/options.py:1271 #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "" -#: contrib/admin/options.py:1447 contrib/admin/options.py:1697 +#: contrib/admin/options.py:1358 contrib/admin/options.py:1616 +#: contrib/admin/options.py:1672 #, python-format msgid "%(name)s object with primary key %(key)r does not exist." msgstr "" -#: contrib/admin/options.py:1497 +#: contrib/admin/options.py:1409 #, python-format msgid "Add %s" msgstr "" -#: contrib/admin/options.py:1497 +#: contrib/admin/options.py:1409 #, python-format msgid "Change %s" msgstr "" -#: contrib/admin/options.py:1558 +#: contrib/admin/options.py:1477 msgid "Database error" msgstr "" -#: contrib/admin/options.py:1621 +#: contrib/admin/options.py:1540 #, python-format msgid "%(count)s %(name)s was changed successfully." msgid_plural "%(count)s %(name)s were changed successfully." msgstr[0] "" msgstr[1] "" -#: contrib/admin/options.py:1648 +#: contrib/admin/options.py:1567 #, python-format msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" msgstr[0] "" msgstr[1] "" -#: contrib/admin/options.py:1654 +#: contrib/admin/options.py:1573 #, python-format msgid "0 of %(cnt)s selected" msgstr "" -#: contrib/admin/options.py:1765 +#: contrib/admin/options.py:1689 #, python-format msgid "Change history: %s" msgstr "" #. Translators: Model verbose name and instance representation, #. suitable to be an item in a list. -#: contrib/admin/options.py:1920 +#: contrib/admin/options.py:1842 #, python-format msgid "%(class_name)s %(instance)s" msgstr "" -#: contrib/admin/options.py:1927 +#: contrib/admin/options.py:1849 #, python-format msgid "" "Deleting %(class_name)s %(instance)s would require deleting the following " "protected related objects: %(related_objects)s" msgstr "" -#: contrib/admin/sites.py:39 contrib/admin/templates/admin/base_site.html:3 +#: contrib/admin/sites.py:40 contrib/admin/templates/admin/base_site.html.py:3 msgid "Django site admin" msgstr "" -#: contrib/admin/sites.py:42 contrib/admin/templates/admin/base_site.html:6 +#: contrib/admin/sites.py:43 contrib/admin/templates/admin/base_site.html.py:6 msgid "Django administration" msgstr "" -#: contrib/admin/sites.py:45 +#: contrib/admin/sites.py:46 msgid "Site administration" msgstr "" -#: contrib/admin/sites.py:373 contrib/admin/templates/admin/login.html:49 +#: contrib/admin/sites.py:393 contrib/admin/templates/admin/login.html.py:61 #: contrib/admin/templates/registration/password_reset_complete.html:18 -#: contrib/admin/tests.py:113 +#: contrib/admin/tests.py:120 msgid "Log in" msgstr "" -#: contrib/admin/sites.py:505 +#: contrib/admin/sites.py:520 #, python-format msgid "%(app)s administration" msgstr "" @@ -314,7 +327,7 @@ msgstr "" #: contrib/admin/templates/admin/500.html:6 #: contrib/admin/templates/admin/app_index.html:9 #: contrib/admin/templates/admin/auth/user/change_password.html:13 -#: contrib/admin/templates/admin/base.html:58 +#: contrib/admin/templates/admin/base.html:56 #: contrib/admin/templates/admin/change_form.html:18 #: contrib/admin/templates/admin/change_list.html:40 #: contrib/admin/templates/admin/delete_confirmation.html:8 @@ -382,7 +395,7 @@ msgstr "" #: contrib/admin/templates/admin/auth/user/change_password.html:17 #: contrib/admin/templates/admin/auth/user/change_password.html:54 -#: contrib/admin/templates/admin/base.html:46 +#: contrib/admin/templates/admin/base.html:44 #: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_form.html:4 msgid "Change password" @@ -391,7 +404,7 @@ msgstr "" #: contrib/admin/templates/admin/auth/user/change_password.html:27 #: contrib/admin/templates/admin/change_form.html:47 #: contrib/admin/templates/admin/change_list.html:67 -#: contrib/admin/templates/admin/login.html:19 +#: contrib/admin/templates/admin/login.html:21 #: contrib/admin/templates/registration/password_change_form.html:21 msgid "Please correct the error below." msgstr "" @@ -399,7 +412,7 @@ msgstr "" #: contrib/admin/templates/admin/auth/user/change_password.html:27 #: contrib/admin/templates/admin/change_form.html:47 #: contrib/admin/templates/admin/change_list.html:67 -#: contrib/admin/templates/admin/login.html:19 +#: contrib/admin/templates/admin/login.html:21 #: contrib/admin/templates/registration/password_change_form.html:21 msgid "Please correct the errors below." msgstr "" @@ -409,21 +422,21 @@ msgstr "" msgid "Enter a new password for the user %(username)s." msgstr "" -#: contrib/admin/templates/admin/base.html:32 +#: contrib/admin/templates/admin/base.html:30 msgid "Welcome," msgstr "" -#: contrib/admin/templates/admin/base.html:37 +#: contrib/admin/templates/admin/base.html:35 msgid "View site" msgstr "" -#: contrib/admin/templates/admin/base.html:42 +#: contrib/admin/templates/admin/base.html:40 #: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_form.html:4 msgid "Documentation" msgstr "" -#: contrib/admin/templates/admin/base.html:48 +#: contrib/admin/templates/admin/base.html:46 #: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_form.html:4 msgid "Log out" @@ -431,7 +444,7 @@ msgstr "" #: contrib/admin/templates/admin/change_form.html:21 #: contrib/admin/templates/admin/index.html:31 -#: contrib/admin/templates/admin/related_widget_wrapper.html:18 +#: contrib/admin/templates/admin/related_widget_wrapper.html:16 msgid "Add" msgstr "" @@ -469,7 +482,7 @@ msgid "Toggle sorting" msgstr "" #: contrib/admin/templates/admin/delete_confirmation.html:12 -#: contrib/admin/templates/admin/related_widget_wrapper.html:26 +#: contrib/admin/templates/admin/related_widget_wrapper.html:23 #: contrib/admin/templates/admin/submit_line.html:6 msgid "Delete" msgstr "" @@ -540,17 +553,17 @@ msgstr "" #: contrib/admin/templates/admin/edit_inline/stacked.html:8 #: contrib/admin/templates/admin/edit_inline/tabular.html:31 #: contrib/admin/templates/admin/index.html:37 -#: contrib/admin/templates/admin/related_widget_wrapper.html:10 +#: contrib/admin/templates/admin/related_widget_wrapper.html:9 msgid "Change" msgstr "" -#: contrib/admin/templates/admin/edit_inline/stacked.html:27 -#: contrib/admin/templates/admin/edit_inline/tabular.html:81 +#: contrib/admin/templates/admin/edit_inline/stacked.html:26 +#: contrib/admin/templates/admin/edit_inline/tabular.html:80 msgid "Remove" msgstr "" -#: contrib/admin/templates/admin/edit_inline/stacked.html:28 -#: contrib/admin/templates/admin/edit_inline/tabular.html:80 +#: contrib/admin/templates/admin/edit_inline/stacked.html:27 +#: contrib/admin/templates/admin/edit_inline/tabular.html:79 #, python-format msgid "Add another %(verbose_name)s" msgstr "" @@ -600,7 +613,14 @@ msgid "" "the appropriate user." msgstr "" -#: contrib/admin/templates/admin/login.html:45 +#: contrib/admin/templates/admin/login.html:37 +#, python-format +msgid "" +"You are authenticated as %(username)s, but are not authorized to access this " +"page. Would you like to login to a different account?" +msgstr "" + +#: contrib/admin/templates/admin/login.html:57 msgid "Forgotten your password or username?" msgstr "" @@ -637,12 +657,12 @@ msgstr "" msgid "Change selected %(model)s" msgstr "" -#: contrib/admin/templates/admin/related_widget_wrapper.html:16 +#: contrib/admin/templates/admin/related_widget_wrapper.html:15 #, python-format msgid "Add another %(model)s" msgstr "" -#: contrib/admin/templates/admin/related_widget_wrapper.html:24 +#: contrib/admin/templates/admin/related_widget_wrapper.html:22 #, python-format msgid "Delete selected %(model)s" msgstr "" @@ -787,40 +807,36 @@ msgstr "" msgid "Reset my password" msgstr "" -#: contrib/admin/templatetags/admin_list.py:382 +#: contrib/admin/templatetags/admin_list.py:389 msgid "All dates" msgstr "" -#: contrib/admin/views/main.py:33 -msgid "(None)" -msgstr "" - -#: contrib/admin/views/main.py:80 +#: contrib/admin/views/main.py:81 #, python-format msgid "Select %s" msgstr "" -#: contrib/admin/views/main.py:82 +#: contrib/admin/views/main.py:83 #, python-format msgid "Select %s to change" msgstr "" -#: contrib/admin/widgets.py:93 +#: contrib/admin/widgets.py:95 msgid "Date:" msgstr "" -#: contrib/admin/widgets.py:94 +#: contrib/admin/widgets.py:96 msgid "Time:" msgstr "" -#: contrib/admin/widgets.py:176 +#: contrib/admin/widgets.py:178 msgid "Lookup" msgstr "" -#: contrib/admin/widgets.py:365 +#: contrib/admin/widgets.py:366 msgid "Currently:" msgstr "" -#: contrib/admin/widgets.py:366 +#: contrib/admin/widgets.py:367 msgid "Change:" msgstr "" diff --git a/django/contrib/admin/locale/en/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/en/LC_MESSAGES/djangojs.po index 47af03bbca77..a5d3b3b71b61 100644 --- a/django/contrib/admin/locale/en/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/en/LC_MESSAGES/djangojs.po @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-01-17 11:07+0100\n" +"POT-Creation-Date: 2015-10-09 17:42+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -13,80 +13,80 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: contrib/admin/static/admin/js/SelectFilter2.js:45 +#: contrib/admin/static/admin/js/SelectFilter2.js:47 #, javascript-format msgid "Available %s" msgstr "" -#: contrib/admin/static/admin/js/SelectFilter2.js:46 +#: contrib/admin/static/admin/js/SelectFilter2.js:53 #, javascript-format msgid "" "This is the list of available %s. You may choose some by selecting them in " "the box below and then clicking the \"Choose\" arrow between the two boxes." msgstr "" -#: contrib/admin/static/admin/js/SelectFilter2.js:53 +#: contrib/admin/static/admin/js/SelectFilter2.js:69 #, javascript-format msgid "Type into this box to filter down the list of available %s." msgstr "" -#: contrib/admin/static/admin/js/SelectFilter2.js:57 +#: contrib/admin/static/admin/js/SelectFilter2.js:74 msgid "Filter" msgstr "" -#: contrib/admin/static/admin/js/SelectFilter2.js:61 +#: contrib/admin/static/admin/js/SelectFilter2.js:78 msgid "Choose all" msgstr "" -#: contrib/admin/static/admin/js/SelectFilter2.js:61 +#: contrib/admin/static/admin/js/SelectFilter2.js:78 #, javascript-format msgid "Click to choose all %s at once." msgstr "" -#: contrib/admin/static/admin/js/SelectFilter2.js:67 +#: contrib/admin/static/admin/js/SelectFilter2.js:84 msgid "Choose" msgstr "" -#: contrib/admin/static/admin/js/SelectFilter2.js:69 +#: contrib/admin/static/admin/js/SelectFilter2.js:86 msgid "Remove" msgstr "" -#: contrib/admin/static/admin/js/SelectFilter2.js:75 +#: contrib/admin/static/admin/js/SelectFilter2.js:92 #, javascript-format msgid "Chosen %s" msgstr "" -#: contrib/admin/static/admin/js/SelectFilter2.js:76 +#: contrib/admin/static/admin/js/SelectFilter2.js:98 #, javascript-format msgid "" "This is the list of chosen %s. You may remove some by selecting them in the " "box below and then clicking the \"Remove\" arrow between the two boxes." msgstr "" -#: contrib/admin/static/admin/js/SelectFilter2.js:80 +#: contrib/admin/static/admin/js/SelectFilter2.js:108 msgid "Remove all" msgstr "" -#: contrib/admin/static/admin/js/SelectFilter2.js:80 +#: contrib/admin/static/admin/js/SelectFilter2.js:108 #, javascript-format msgid "Click to remove all chosen %s at once." msgstr "" -#: contrib/admin/static/admin/js/actions.js:22 -#: contrib/admin/static/admin/js/actions.min.js:1 +#: contrib/admin/static/admin/js/actions.js:47 +#: contrib/admin/static/admin/js/actions.min.js:2 msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "" msgstr[1] "" -#: contrib/admin/static/admin/js/actions.js:114 +#: contrib/admin/static/admin/js/actions.js:116 #: contrib/admin/static/admin/js/actions.min.js:4 msgid "" "You have unsaved changes on individual editable fields. If you run an " "action, your unsaved changes will be lost." msgstr "" -#: contrib/admin/static/admin/js/actions.js:126 +#: contrib/admin/static/admin/js/actions.js:128 #: contrib/admin/static/admin/js/actions.min.js:5 msgid "" "You have selected an action, but you haven't saved your changes to " @@ -94,7 +94,7 @@ msgid "" "action." msgstr "" -#: contrib/admin/static/admin/js/actions.js:128 +#: contrib/admin/static/admin/js/actions.js:130 #: contrib/admin/static/admin/js/actions.min.js:5 msgid "" "You have selected an action, and you haven't made any changes on individual " @@ -102,27 +102,27 @@ msgid "" "button." msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:79 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:74 #, javascript-format msgid "Note: You are %s hour ahead of server time." msgid_plural "Note: You are %s hours ahead of server time." msgstr[0] "" msgstr[1] "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:82 #, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "" msgstr[1] "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:114 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:109 #: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:149 msgid "Now" msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:118 -msgid "Clock" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:116 +msgid "Choose a Time" msgstr "" #: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:146 @@ -141,45 +141,49 @@ msgstr "" msgid "Noon" msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:156 -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:276 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:153 +msgid "6 p.m." +msgstr "" + +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:157 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:281 msgid "Cancel" msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:216 -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:269 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:217 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:274 msgid "Today" msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:220 -msgid "Calendar" +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:224 +msgid "Choose a Date" msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:267 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:272 msgid "Yesterday" msgstr "" -#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:271 +#: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:276 msgid "Tomorrow" msgstr "" -#: contrib/admin/static/admin/js/calendar.js:8 +#: contrib/admin/static/admin/js/calendar.js:11 msgid "" "January February March April May June July August September October November " "December" msgstr "" -#: contrib/admin/static/admin/js/calendar.js:9 +#: contrib/admin/static/admin/js/calendar.js:12 msgid "S M T W T F S" msgstr "" -#: contrib/admin/static/admin/js/collapse.js:8 -#: contrib/admin/static/admin/js/collapse.js:19 +#: contrib/admin/static/admin/js/collapse.js:10 +#: contrib/admin/static/admin/js/collapse.js:21 #: contrib/admin/static/admin/js/collapse.min.js:1 msgid "Show" msgstr "" -#: contrib/admin/static/admin/js/collapse.js:16 +#: contrib/admin/static/admin/js/collapse.js:18 #: contrib/admin/static/admin/js/collapse.min.js:1 msgid "Hide" msgstr "" diff --git a/django/contrib/admindocs/locale/en/LC_MESSAGES/django.po b/django/contrib/admindocs/locale/en/LC_MESSAGES/django.po index 5bb2a8f7deeb..4b7e8489a864 100644 --- a/django/contrib/admindocs/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/admindocs/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: 2015-01-17 11:07+0100\n" +"POT-Creation-Date: 2015-10-09 17:42+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -13,7 +13,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: contrib/admindocs/apps.py:8 +#: contrib/admindocs/apps.py:7 msgid "Administrative Documentation" msgstr "" @@ -60,48 +60,19 @@ msgid "" "\n" "

    To install bookmarklets, drag the link to your bookmarks\n" "toolbar, or right-click the link and add it to your bookmarks. Now you can\n" -"select the bookmarklet from any page in the site. Note that some of these\n" -"bookmarklets require you to be viewing the site from a computer designated\n" -"as \"internal\" (talk to your system administrator if you aren't sure if\n" -"your computer is \"internal\").

    \n" +"select the bookmarklet from any page in the site.

    \n" msgstr "" -#: contrib/admindocs/templates/admin_doc/bookmarklets.html:25 +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:22 msgid "Documentation for this page" msgstr "" -#: contrib/admindocs/templates/admin_doc/bookmarklets.html:26 +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:23 msgid "" "Jumps you from any page to the documentation for the view that generates " "that page." msgstr "" -#: contrib/admindocs/templates/admin_doc/bookmarklets.html:28 -msgid "Show object ID" -msgstr "" - -#: contrib/admindocs/templates/admin_doc/bookmarklets.html:29 -msgid "" -"Shows the content-type and unique ID for pages that represent a single " -"object." -msgstr "" - -#: contrib/admindocs/templates/admin_doc/bookmarklets.html:31 -msgid "Edit this object (current window)" -msgstr "" - -#: contrib/admindocs/templates/admin_doc/bookmarklets.html:32 -msgid "Jumps to the admin page for pages that represent a single object." -msgstr "" - -#: contrib/admindocs/templates/admin_doc/bookmarklets.html:34 -msgid "Edit this object (new window)" -msgstr "" - -#: contrib/admindocs/templates/admin_doc/bookmarklets.html:35 -msgid "As above, but opens the admin page in a new window." -msgstr "" - #: contrib/admindocs/templates/admin_doc/index.html:17 #: contrib/admindocs/templates/admin_doc/template_tag_index.html:9 msgid "Tags" @@ -176,19 +147,36 @@ msgstr "" msgid "Model: %(name)s" msgstr "" -#: contrib/admindocs/templates/admin_doc/model_detail.html:34 -msgid "Field" +#: contrib/admindocs/templates/admin_doc/model_detail.html:30 +msgid "Fields" msgstr "" #: contrib/admindocs/templates/admin_doc/model_detail.html:35 -msgid "Type" +msgid "Field" msgstr "" #: contrib/admindocs/templates/admin_doc/model_detail.html:36 +msgid "Type" +msgstr "" + +#: contrib/admindocs/templates/admin_doc/model_detail.html:37 +#: contrib/admindocs/templates/admin_doc/model_detail.html:60 msgid "Description" msgstr "" -#: contrib/admindocs/templates/admin_doc/model_detail.html:51 +#: contrib/admindocs/templates/admin_doc/model_detail.html:53 +msgid "Methods with arguments" +msgstr "" + +#: contrib/admindocs/templates/admin_doc/model_detail.html:58 +msgid "Method" +msgstr "" + +#: contrib/admindocs/templates/admin_doc/model_detail.html:59 +msgid "Arguments" +msgstr "" + +#: contrib/admindocs/templates/admin_doc/model_detail.html:76 msgid "Back to Model Documentation" msgstr "" @@ -322,59 +310,59 @@ msgstr "" msgid "Field of type: %(field_type)s" msgstr "" -#: contrib/admindocs/views.py:71 contrib/admindocs/views.py:73 -#: contrib/admindocs/views.py:75 +#: contrib/admindocs/views.py:75 contrib/admindocs/views.py:77 +#: contrib/admindocs/views.py:79 msgid "tag:" msgstr "" -#: contrib/admindocs/views.py:104 contrib/admindocs/views.py:106 -#: contrib/admindocs/views.py:108 +#: contrib/admindocs/views.py:109 contrib/admindocs/views.py:111 +#: contrib/admindocs/views.py:113 msgid "filter:" msgstr "" -#: contrib/admindocs/views.py:156 contrib/admindocs/views.py:158 -#: contrib/admindocs/views.py:160 +#: contrib/admindocs/views.py:158 contrib/admindocs/views.py:160 +#: contrib/admindocs/views.py:162 msgid "view:" msgstr "" -#: contrib/admindocs/views.py:188 +#: contrib/admindocs/views.py:190 #, python-format msgid "App %(app_label)r not found" msgstr "" -#: contrib/admindocs/views.py:192 +#: contrib/admindocs/views.py:194 #, python-format msgid "Model %(model_name)r not found in app %(app_label)r" msgstr "" -#: contrib/admindocs/views.py:198 contrib/admindocs/views.py:200 -#: contrib/admindocs/views.py:215 contrib/admindocs/views.py:238 -#: contrib/admindocs/views.py:243 contrib/admindocs/views.py:257 -#: contrib/admindocs/views.py:274 contrib/admindocs/views.py:279 +#: contrib/admindocs/views.py:200 contrib/admindocs/views.py:202 +#: contrib/admindocs/views.py:217 contrib/admindocs/views.py:240 +#: contrib/admindocs/views.py:245 contrib/admindocs/views.py:260 +#: contrib/admindocs/views.py:295 contrib/admindocs/views.py:300 msgid "model:" msgstr "" -#: contrib/admindocs/views.py:211 +#: contrib/admindocs/views.py:213 #, python-format msgid "the related `%(app_label)s.%(data_type)s` object" msgstr "" -#: contrib/admindocs/views.py:231 contrib/admindocs/views.py:266 +#: contrib/admindocs/views.py:233 contrib/admindocs/views.py:287 #, python-format msgid "related `%(app_label)s.%(object_name)s` objects" msgstr "" -#: contrib/admindocs/views.py:238 contrib/admindocs/views.py:274 +#: contrib/admindocs/views.py:240 contrib/admindocs/views.py:295 #, python-format msgid "all %s" msgstr "" -#: contrib/admindocs/views.py:243 contrib/admindocs/views.py:279 +#: contrib/admindocs/views.py:245 contrib/admindocs/views.py:300 #, python-format msgid "number of %s" msgstr "" -#: contrib/admindocs/views.py:389 +#: contrib/admindocs/views.py:388 #, python-format msgid "%s does not appear to be a urlpattern object" msgstr "" diff --git a/django/contrib/auth/locale/en/LC_MESSAGES/django.po b/django/contrib/auth/locale/en/LC_MESSAGES/django.po index 3812af08319d..fdfde63b7787 100644 --- a/django/contrib/auth/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/auth/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: 2015-03-18 09:16+0100\n" +"POT-Creation-Date: 2015-10-09 17:42+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -13,28 +13,28 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: contrib/auth/admin.py:46 +#: contrib/auth/admin.py:49 msgid "Personal info" msgstr "" -#: contrib/auth/admin.py:47 +#: contrib/auth/admin.py:50 msgid "Permissions" msgstr "" -#: contrib/auth/admin.py:49 +#: contrib/auth/admin.py:52 msgid "Important dates" msgstr "" -#: contrib/auth/admin.py:129 +#: contrib/auth/admin.py:136 #, python-format msgid "%(name)s object with primary key %(key)r does not exist." msgstr "" -#: contrib/auth/admin.py:139 +#: contrib/auth/admin.py:146 msgid "Password changed successfully." msgstr "" -#: contrib/auth/admin.py:150 +#: contrib/auth/admin.py:165 #, python-format msgid "Change password: %s" msgstr "" @@ -43,6 +43,14 @@ msgstr "" msgid "Authentication and Authorization" msgstr "" +#: contrib/auth/base_user.py:50 +msgid "password" +msgstr "" + +#: contrib/auth/base_user.py:51 +msgid "last login" +msgstr "" + #: contrib/auth/forms.py:30 msgid "No password set." msgstr "" @@ -51,13 +59,13 @@ msgstr "" msgid "Invalid password format or unknown hashing algorithm." msgstr "" -#: contrib/auth/forms.py:69 contrib/auth/forms.py:265 -#: contrib/auth/forms.py:329 +#: contrib/auth/forms.py:69 contrib/auth/forms.py:268 +#: contrib/auth/forms.py:332 msgid "The two password fields didn't match." msgstr "" -#: contrib/auth/forms.py:71 contrib/auth/forms.py:100 -#: contrib/auth/forms.py:128 contrib/auth/forms.py:333 +#: contrib/auth/forms.py:71 contrib/auth/forms.py:102 +#: contrib/auth/forms.py:130 contrib/auth/forms.py:336 msgid "Password" msgstr "" @@ -65,56 +73,56 @@ msgstr "" msgid "Password confirmation" msgstr "" -#: contrib/auth/forms.py:75 contrib/auth/forms.py:339 -msgid "Enter the same password as above, for verification." +#: contrib/auth/forms.py:75 contrib/auth/forms.py:343 +msgid "Enter the same password as before, for verification." msgstr "" -#: contrib/auth/forms.py:101 +#: contrib/auth/forms.py:103 msgid "" "Raw passwords are not stored, so there is no way to see this user's " -"password, but you can change the password using this " -"form." +"password, but you can change the password using this form." msgstr "" -#: contrib/auth/forms.py:131 +#: contrib/auth/forms.py:133 #, python-format msgid "" "Please enter a correct %(username)s and password. Note that both fields may " "be case-sensitive." msgstr "" -#: contrib/auth/forms.py:133 +#: contrib/auth/forms.py:135 msgid "This account is inactive." msgstr "" -#: contrib/auth/forms.py:196 +#: contrib/auth/forms.py:198 msgid "Email" msgstr "" -#: contrib/auth/forms.py:267 +#: contrib/auth/forms.py:270 msgid "New password" msgstr "" -#: contrib/auth/forms.py:269 +#: contrib/auth/forms.py:273 msgid "New password confirmation" msgstr "" -#: contrib/auth/forms.py:300 +#: contrib/auth/forms.py:306 msgid "Your old password was entered incorrectly. Please enter it again." msgstr "" -#: contrib/auth/forms.py:303 +#: contrib/auth/forms.py:309 msgid "Old password" msgstr "" -#: contrib/auth/forms.py:337 +#: contrib/auth/forms.py:341 msgid "Password (again)" msgstr "" #: contrib/auth/hashers.py:251 contrib/auth/hashers.py:334 -#: contrib/auth/hashers.py:382 contrib/auth/hashers.py:410 -#: contrib/auth/hashers.py:443 contrib/auth/hashers.py:476 -#: contrib/auth/hashers.py:510 +#: contrib/auth/hashers.py:386 contrib/auth/hashers.py:414 +#: contrib/auth/hashers.py:447 contrib/auth/hashers.py:480 +#: contrib/auth/hashers.py:514 msgid "algorithm" msgstr "" @@ -123,14 +131,14 @@ msgid "iterations" msgstr "" #: contrib/auth/hashers.py:253 contrib/auth/hashers.py:336 -#: contrib/auth/hashers.py:383 contrib/auth/hashers.py:411 -#: contrib/auth/hashers.py:511 +#: contrib/auth/hashers.py:387 contrib/auth/hashers.py:415 +#: contrib/auth/hashers.py:515 msgid "salt" msgstr "" -#: contrib/auth/hashers.py:254 contrib/auth/hashers.py:384 -#: contrib/auth/hashers.py:412 contrib/auth/hashers.py:444 -#: contrib/auth/hashers.py:477 contrib/auth/hashers.py:512 +#: contrib/auth/hashers.py:254 contrib/auth/hashers.py:388 +#: contrib/auth/hashers.py:416 contrib/auth/hashers.py:448 +#: contrib/auth/hashers.py:481 contrib/auth/hashers.py:516 msgid "hash" msgstr "" @@ -142,155 +150,188 @@ msgstr "" msgid "checksum" msgstr "" -#: contrib/auth/models.py:64 contrib/auth/models.py:115 +#: contrib/auth/models.py:61 contrib/auth/models.py:116 msgid "name" msgstr "" -#: contrib/auth/models.py:66 +#: contrib/auth/models.py:65 +msgid "content type" +msgstr "" + +#: contrib/auth/models.py:67 msgid "codename" msgstr "" -#: contrib/auth/models.py:70 +#: contrib/auth/models.py:71 msgid "permission" msgstr "" -#: contrib/auth/models.py:71 contrib/auth/models.py:117 +#: contrib/auth/models.py:72 contrib/auth/models.py:119 msgid "permissions" msgstr "" -#: contrib/auth/models.py:122 +#: contrib/auth/models.py:126 msgid "group" msgstr "" -#: contrib/auth/models.py:123 contrib/auth/models.py:311 +#: contrib/auth/models.py:127 contrib/auth/models.py:222 msgid "groups" msgstr "" -#: contrib/auth/models.py:196 -msgid "password" -msgstr "" - -#: contrib/auth/models.py:197 -msgid "last login" -msgstr "" - -#: contrib/auth/models.py:308 +#: contrib/auth/models.py:213 msgid "superuser status" msgstr "" -#: contrib/auth/models.py:309 +#: contrib/auth/models.py:216 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." msgstr "" -#: contrib/auth/models.py:312 +#: contrib/auth/models.py:225 msgid "" "The groups this user belongs to. A user will get all permissions granted to " "each of their groups." msgstr "" -#: contrib/auth/models.py:317 +#: contrib/auth/models.py:233 msgid "user permissions" msgstr "" -#: contrib/auth/models.py:318 +#: contrib/auth/models.py:235 msgid "Specific permissions for this user." msgstr "" -#: contrib/auth/models.py:385 +#: contrib/auth/models.py:305 msgid "username" msgstr "" -#: contrib/auth/models.py:386 +#: contrib/auth/models.py:308 msgid "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only." msgstr "" -#: contrib/auth/models.py:390 +#: contrib/auth/models.py:312 msgid "" "Enter a valid username. This value may contain only letters, numbers and @/./" "+/-/_ characters." msgstr "" -#: contrib/auth/models.py:395 +#: contrib/auth/models.py:317 msgid "A user with that username already exists." msgstr "" -#: contrib/auth/models.py:397 +#: contrib/auth/models.py:320 msgid "first name" msgstr "" -#: contrib/auth/models.py:398 +#: contrib/auth/models.py:321 msgid "last name" msgstr "" -#: contrib/auth/models.py:399 +#: contrib/auth/models.py:322 msgid "email address" msgstr "" -#: contrib/auth/models.py:400 +#: contrib/auth/models.py:324 msgid "staff status" msgstr "" -#: contrib/auth/models.py:401 +#: contrib/auth/models.py:326 msgid "Designates whether the user can log into this admin site." msgstr "" -#: contrib/auth/models.py:403 +#: contrib/auth/models.py:329 msgid "active" msgstr "" -#: contrib/auth/models.py:404 +#: contrib/auth/models.py:332 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." msgstr "" -#: contrib/auth/models.py:406 +#: contrib/auth/models.py:336 msgid "date joined" msgstr "" -#: contrib/auth/models.py:414 +#: contrib/auth/models.py:344 msgid "user" msgstr "" -#: contrib/auth/models.py:415 +#: contrib/auth/models.py:345 msgid "users" msgstr "" +#: contrib/auth/password_validation.py:100 +#, python-format +msgid "" +"This password is too short. It must contain at least %(min_length)d " +"characters." +msgstr "" + +#: contrib/auth/password_validation.py:106 +#, python-format +msgid "Your password must contain at least %(min_length)d characters." +msgstr "" + +#: contrib/auth/password_validation.py:139 +#, python-format +msgid "The password is too similar to the %(verbose_name)s." +msgstr "" + +#: contrib/auth/password_validation.py:145 +msgid "Your password can't be too similar to your other personal information." +msgstr "" + +#: contrib/auth/password_validation.py:172 +msgid "This password is too common." +msgstr "" + +#: contrib/auth/password_validation.py:177 +msgid "Your password can't be a commonly used password." +msgstr "" + +#: contrib/auth/password_validation.py:187 +msgid "This password is entirely numeric." +msgstr "" + +#: contrib/auth/password_validation.py:192 +msgid "Your password can't be entirely numeric." +msgstr "" + #: contrib/auth/templates/registration/password_reset_subject.txt:2 #, python-format msgid "Password reset on %(site_name)s" msgstr "" -#: contrib/auth/views.py:102 +#: contrib/auth/views.py:125 msgid "Logged out" msgstr "" -#: contrib/auth/views.py:188 +#: contrib/auth/views.py:211 msgid "Password reset" msgstr "" -#: contrib/auth/views.py:203 +#: contrib/auth/views.py:224 msgid "Password reset sent" msgstr "" -#: contrib/auth/views.py:242 +#: contrib/auth/views.py:261 msgid "Enter new password" msgstr "" -#: contrib/auth/views.py:253 +#: contrib/auth/views.py:272 msgid "Password reset unsuccessful" msgstr "" -#: contrib/auth/views.py:273 +#: contrib/auth/views.py:290 msgid "Password reset complete" msgstr "" -#: contrib/auth/views.py:310 +#: contrib/auth/views.py:325 msgid "Password change" msgstr "" -#: contrib/auth/views.py:326 +#: contrib/auth/views.py:339 msgid "Password change successful" msgstr "" diff --git a/django/contrib/flatpages/locale/en/LC_MESSAGES/django.po b/django/contrib/flatpages/locale/en/LC_MESSAGES/django.po index bab242cb9e1f..4e226e416dbe 100644 --- a/django/contrib/flatpages/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/flatpages/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: 2015-03-18 09:16+0100\n" +"POT-Creation-Date: 2015-10-09 17:42+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -13,7 +13,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: contrib/flatpages/admin.py:11 +#: contrib/flatpages/admin.py:12 msgid "Advanced options" msgstr "" @@ -79,6 +79,10 @@ msgstr "" msgid "If this is checked, only logged-in users will be able to view the page." msgstr "" +#: contrib/flatpages/models.py:25 +msgid "sites" +msgstr "" + #: contrib/flatpages/models.py:29 msgid "flat page" msgstr "" diff --git a/django/contrib/gis/locale/en/LC_MESSAGES/django.po b/django/contrib/gis/locale/en/LC_MESSAGES/django.po index d1048f72f833..997f5f440531 100644 --- a/django/contrib/gis/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/gis/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: 2015-03-18 09:16+0100\n" +"POT-Creation-Date: 2015-10-09 17:42+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -18,54 +18,63 @@ msgid "GIS" msgstr "" #: contrib/gis/db/models/fields.py:78 -msgid "The base GIS field -- maps to the OpenGIS Specification Geometry type." +msgid "The base GIS field." msgstr "" -#: contrib/gis/db/models/fields.py:325 +#: contrib/gis/db/models/fields.py:165 +msgid "" +"The base Geometry field -- maps to the OpenGIS Specification Geometry type." +msgstr "" + +#: contrib/gis/db/models/fields.py:352 msgid "Point" msgstr "" -#: contrib/gis/db/models/fields.py:331 +#: contrib/gis/db/models/fields.py:358 msgid "Line string" msgstr "" -#: contrib/gis/db/models/fields.py:337 +#: contrib/gis/db/models/fields.py:364 msgid "Polygon" msgstr "" -#: contrib/gis/db/models/fields.py:343 +#: contrib/gis/db/models/fields.py:370 msgid "Multi-point" msgstr "" -#: contrib/gis/db/models/fields.py:349 +#: contrib/gis/db/models/fields.py:376 msgid "Multi-line string" msgstr "" -#: contrib/gis/db/models/fields.py:355 +#: contrib/gis/db/models/fields.py:382 msgid "Multi polygon" msgstr "" -#: contrib/gis/db/models/fields.py:361 +#: contrib/gis/db/models/fields.py:388 msgid "Geometry collection" msgstr "" -#: contrib/gis/db/models/fields.py:367 +#: contrib/gis/db/models/fields.py:394 msgid "Extent Aggregate Field" msgstr "" -#: contrib/gis/forms/fields.py:22 +#: contrib/gis/db/models/fields.py:405 +msgid "Raster Field" +msgstr "" + +#: contrib/gis/forms/fields.py:20 msgid "No geometry value provided." msgstr "" -#: contrib/gis/forms/fields.py:23 +#: contrib/gis/forms/fields.py:21 msgid "Invalid geometry value." msgstr "" -#: contrib/gis/forms/fields.py:24 +#: contrib/gis/forms/fields.py:22 msgid "Invalid geometry type." msgstr "" -#: contrib/gis/forms/fields.py:25 +#: contrib/gis/forms/fields.py:23 msgid "" "An error occurred when transforming the geometry to the SRID of the geometry " "form field." diff --git a/django/contrib/postgres/locale/en/LC_MESSAGES/django.po b/django/contrib/postgres/locale/en/LC_MESSAGES/django.po index 724a5b955eec..66384de934a3 100644 --- a/django/contrib/postgres/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/postgres/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: 2015-03-18 09:16+0100\n" +"POT-Creation-Date: 2015-10-09 17:42+0200\n" "PO-Revision-Date: 2015-01-18 20:56+0100\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -18,13 +18,13 @@ msgstr "" msgid "PostgreSQL extensions" msgstr "" -#: contrib/postgres/fields/array.py:22 contrib/postgres/forms/array.py:15 +#: contrib/postgres/fields/array.py:19 contrib/postgres/forms/array.py:15 #: contrib/postgres/forms/array.py:145 #, python-format msgid "Item %(nth)s in the array did not validate: " msgstr "" -#: contrib/postgres/fields/array.py:23 +#: contrib/postgres/fields/array.py:20 msgid "Nested arrays must have the same length." msgstr "" @@ -37,10 +37,23 @@ msgstr "" msgid "The value of \"%(key)s\" is not a string." msgstr "" +#: contrib/postgres/fields/jsonb.py:15 +msgid "A JSON object" +msgstr "" + +#: contrib/postgres/fields/jsonb.py:17 +msgid "Value must be valid JSON." +msgstr "" + #: contrib/postgres/forms/hstore.py:15 msgid "Could not load JSON data." msgstr "" +#: contrib/postgres/forms/jsonb.py:11 +#, python-format +msgid "'%(value)s' value must be valid JSON." +msgstr "" + #: contrib/postgres/forms/ranges.py:13 msgid "Enter two valid values." msgstr "" @@ -49,19 +62,19 @@ msgstr "" msgid "The start of the range must not exceed the end of the range." msgstr "" -#: contrib/postgres/forms/ranges.py:52 +#: contrib/postgres/forms/ranges.py:59 msgid "Enter two whole numbers." msgstr "" -#: contrib/postgres/forms/ranges.py:58 +#: contrib/postgres/forms/ranges.py:65 msgid "Enter two numbers." msgstr "" -#: contrib/postgres/forms/ranges.py:64 +#: contrib/postgres/forms/ranges.py:71 msgid "Enter two valid date/times." msgstr "" -#: contrib/postgres/forms/ranges.py:70 +#: contrib/postgres/forms/ranges.py:77 msgid "Enter two valid dates." msgstr "" diff --git a/django/contrib/redirects/locale/en/LC_MESSAGES/django.po b/django/contrib/redirects/locale/en/LC_MESSAGES/django.po index 2b73f2d16183..eb33e60d348a 100644 --- a/django/contrib/redirects/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/redirects/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: 2015-01-17 11:07+0100\n" +"POT-Creation-Date: 2015-10-09 17:42+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -13,10 +13,14 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: contrib/redirects/apps.py:8 +#: contrib/redirects/apps.py:7 msgid "Redirects" msgstr "" +#: contrib/redirects/models.py:9 +msgid "site" +msgstr "" + #: contrib/redirects/models.py:10 msgid "redirect from" msgstr "" From 7cd299584dd9b152c736854c6558670e57512d40 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 8 Oct 2015 21:14:24 -0400 Subject: [PATCH 055/756] [1.9.x] Fixed #22705 -- Fixed QuerySet.bulk_create() on models without any fields on Oracle. Fixed on other backends by 134ca4d438bd7cbe8f0f287a00d545f96fa04a01. Thanks Mariusz Felisiak for the solution. Backport of 7a5b7e35bf2e219225b9f26d3dd3e34f26e83e9c from master --- django/db/backends/oracle/operations.py | 3 +++ tests/bulk_create/models.py | 4 ++++ tests/bulk_create/tests.py | 8 ++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index ce6946011b72..0651105d9ff4 100644 --- a/django/db/backends/oracle/operations.py +++ b/django/db/backends/oracle/operations.py @@ -267,6 +267,9 @@ def max_in_list_size(self): def max_name_length(self): return 30 + def pk_default_value(self): + return "NULL" + def prep_for_iexact_query(self, x): return x diff --git a/tests/bulk_create/models.py b/tests/bulk_create/models.py index 22d16e75e5f4..c302a70b1b2d 100644 --- a/tests/bulk_create/models.py +++ b/tests/bulk_create/models.py @@ -47,3 +47,7 @@ class State(models.Model): class TwoFields(models.Model): f1 = models.IntegerField(unique=True) f2 = models.IntegerField(unique=True) + + +class NoFields(models.Model): + pass diff --git a/tests/bulk_create/tests.py b/tests/bulk_create/tests.py index ce069504d003..f881a1dba07b 100644 --- a/tests/bulk_create/tests.py +++ b/tests/bulk_create/tests.py @@ -10,8 +10,8 @@ ) from .models import ( - Country, Pizzeria, ProxyCountry, ProxyMultiCountry, ProxyMultiProxyCountry, - ProxyProxyCountry, Restaurant, State, TwoFields, + Country, NoFields, Pizzeria, ProxyCountry, ProxyMultiCountry, + ProxyMultiProxyCountry, ProxyProxyCountry, Restaurant, State, TwoFields, ) @@ -177,6 +177,10 @@ def test_explicit_batch_size(self): TwoFields.objects.bulk_create(objs, len(objs)) self.assertEqual(TwoFields.objects.count(), len(objs)) + def test_empty_model(self): + NoFields.objects.bulk_create([NoFields() for i in range(2)]) + self.assertEqual(NoFields.objects.count(), 2) + @skipUnlessDBFeature('has_bulk_insert') def test_explicit_batch_size_efficiency(self): objs = [TwoFields(f1=i, f2=i) for i in range(0, 100)] From 60471c9600ec3db670e6a8a206bdae0d74edfe65 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 10 Oct 2015 14:51:38 +0200 Subject: [PATCH 056/756] [1.9.x] Simplified translatable string in admindocs template --- .../contrib/admindocs/locale/en/LC_MESSAGES/django.po | 9 ++++----- .../admindocs/templates/admin_doc/bookmarklets.html | 10 +++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/django/contrib/admindocs/locale/en/LC_MESSAGES/django.po b/django/contrib/admindocs/locale/en/LC_MESSAGES/django.po index 4b7e8489a864..f1f9cb77d91d 100644 --- a/django/contrib/admindocs/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/admindocs/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: 2015-10-09 17:42+0200\n" +"POT-Creation-Date: 2015-10-10 14:49+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -57,10 +57,9 @@ msgstr "" #: contrib/admindocs/templates/admin_doc/bookmarklets.html:15 msgid "" -"\n" -"

    To install bookmarklets, drag the link to your bookmarks\n" -"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" -"select the bookmarklet from any page in the site.

    \n" +"To install bookmarklets, drag the link to your bookmarks toolbar, or right-" +"click the link and add it to your bookmarks. Now you can select the " +"bookmarklet from any page in the site." msgstr "" #: contrib/admindocs/templates/admin_doc/bookmarklets.html:22 diff --git a/django/contrib/admindocs/templates/admin_doc/bookmarklets.html b/django/contrib/admindocs/templates/admin_doc/bookmarklets.html index bb5e8b442d7f..6bba2f2d1277 100644 --- a/django/contrib/admindocs/templates/admin_doc/bookmarklets.html +++ b/django/contrib/admindocs/templates/admin_doc/bookmarklets.html @@ -12,11 +12,11 @@ {% block content %} -{% blocktrans %} -

    To install bookmarklets, drag the link to your bookmarks -toolbar, or right-click the link and add it to your bookmarks. Now you can -select the bookmarklet from any page in the site.

    -{% endblocktrans %} +

    {% blocktrans trimmed %} +To install bookmarklets, drag the link to your bookmarks toolbar, or right-click +the link and add it to your bookmarks. Now you can select the bookmarklet +from any page in the site. +{% endblocktrans %}

    {% trans "Documentation for this page" %}

    From 86dc4889f871004dbee263c2ff6bdc96fc660001 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 10 Oct 2015 15:12:55 +0200 Subject: [PATCH 057/756] [1.9.x] Pluralized translatable strings in password_validation.py --- .../auth/locale/en/LC_MESSAGES/django.po | 30 +++++++++++-------- django/contrib/auth/password_validation.py | 14 +++++++-- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/django/contrib/auth/locale/en/LC_MESSAGES/django.po b/django/contrib/auth/locale/en/LC_MESSAGES/django.po index fdfde63b7787..aa69218cc267 100644 --- a/django/contrib/auth/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/auth/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: 2015-10-09 17:42+0200\n" +"POT-Creation-Date: 2015-10-10 15:10+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -262,40 +262,46 @@ msgstr "" msgid "users" msgstr "" -#: contrib/auth/password_validation.py:100 +#: contrib/auth/password_validation.py:101 #, python-format msgid "" "This password is too short. It must contain at least %(min_length)d " +"character." +msgid_plural "" +"This password is too short. It must contain at least %(min_length)d " "characters." -msgstr "" +msgstr[0] "" +msgstr[1] "" -#: contrib/auth/password_validation.py:106 +#: contrib/auth/password_validation.py:111 #, python-format -msgid "Your password must contain at least %(min_length)d characters." -msgstr "" +msgid "Your password must contain at least %(min_length)d character." +msgid_plural "Your password must contain at least %(min_length)d characters." +msgstr[0] "" +msgstr[1] "" -#: contrib/auth/password_validation.py:139 +#: contrib/auth/password_validation.py:147 #, python-format msgid "The password is too similar to the %(verbose_name)s." msgstr "" -#: contrib/auth/password_validation.py:145 +#: contrib/auth/password_validation.py:153 msgid "Your password can't be too similar to your other personal information." msgstr "" -#: contrib/auth/password_validation.py:172 +#: contrib/auth/password_validation.py:180 msgid "This password is too common." msgstr "" -#: contrib/auth/password_validation.py:177 +#: contrib/auth/password_validation.py:185 msgid "Your password can't be a commonly used password." msgstr "" -#: contrib/auth/password_validation.py:187 +#: contrib/auth/password_validation.py:195 msgid "This password is entirely numeric." msgstr "" -#: contrib/auth/password_validation.py:192 +#: contrib/auth/password_validation.py:200 msgid "Your password can't be entirely numeric." msgstr "" diff --git a/django/contrib/auth/password_validation.py b/django/contrib/auth/password_validation.py index 332d76c8450a..2af520220783 100644 --- a/django/contrib/auth/password_validation.py +++ b/django/contrib/auth/password_validation.py @@ -13,7 +13,7 @@ from django.utils.html import format_html from django.utils.module_loading import import_string from django.utils.six import string_types -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext as _, ungettext @lru_cache.lru_cache(maxsize=None) @@ -97,13 +97,21 @@ def __init__(self, min_length=8): def validate(self, password, user=None): if len(password) < self.min_length: raise ValidationError( - _("This password is too short. It must contain at least %(min_length)d characters."), + ungettext( + "This password is too short. It must contain at least %(min_length)d character.", + "This password is too short. It must contain at least %(min_length)d characters.", + self.min_length + ), code='password_too_short', params={'min_length': self.min_length}, ) def get_help_text(self): - return _("Your password must contain at least %(min_length)d characters.") % {'min_length': self.min_length} + return ungettext( + "Your password must contain at least %(min_length)d character.", + "Your password must contain at least %(min_length)d characters.", + self.min_length + ) % {'min_length': self.min_length} class UserAttributeSimilarityValidator(object): From 66319cc59724ae28917eea62643d07cc9606897d Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 9 Oct 2015 17:15:16 +0200 Subject: [PATCH 058/756] [1.9.x] Fixed #25498 -- Documented ST_Distance/ST_Distance_Sphere difference Thanks Bibhas Debnath for the report and Tim Graham for the review. Backport of 617b1a21f from master. --- docs/ref/contrib/gis/geoquerysets.txt | 35 ++++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index d4b83e86c833..d30f1a8bef61 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -521,7 +521,8 @@ distance lookup but :lookup:`dwithin`, an optional third element, ``'spheroid'``, may be included to tell GeoDjango to use the more accurate spheroid distance calculation functions on fields with a geodetic coordinate system (e.g., ``ST_Distance_Spheroid`` -would be used instead of ``ST_Distance_Sphere``). +would be used instead of ``ST_Distance_Sphere``). The simpler ``ST_Distance`` +function is used with projected coordinate systems. .. fieldlookup:: distance_gt @@ -535,13 +536,13 @@ Example:: Zipcode.objects.filter(poly__distance_gt=(geom, D(m=5))) -========== =============================================== +========== ================================================== Backend SQL Equivalent -========== =============================================== -PostGIS ``ST_Distance(poly, geom) > 5`` +========== ================================================== +PostGIS ``ST_Distance/ST_Distance_Sphere(poly, geom) > 5`` Oracle ``SDO_GEOM.SDO_DISTANCE(poly, geom, 0.05) > 5`` SpatiaLite ``Distance(poly, geom) > 5`` -========== =============================================== +========== ================================================== .. fieldlookup:: distance_gte @@ -555,13 +556,13 @@ Example:: Zipcode.objects.filter(poly__distance_gte=(geom, D(m=5))) -========== ================================================ +========== =================================================== Backend SQL Equivalent -========== ================================================ -PostGIS ``ST_Distance(poly, geom) >= 5`` +========== =================================================== +PostGIS ``ST_Distance/ST_Distance_Sphere(poly, geom) >= 5`` Oracle ``SDO_GEOM.SDO_DISTANCE(poly, geom, 0.05) >= 5`` SpatiaLite ``Distance(poly, geom) >= 5`` -========== ================================================ +========== =================================================== .. fieldlookup:: distance_lt @@ -575,13 +576,13 @@ Example:: Zipcode.objects.filter(poly__distance_lt=(geom, D(m=5))) -========== =============================================== +========== ================================================== Backend SQL Equivalent -========== =============================================== -PostGIS ``ST_Distance(poly, geom) < 5`` +========== ================================================== +PostGIS ``ST_Distance/ST_Distance_Sphere(poly, geom) < 5`` Oracle ``SDO_GEOM.SDO_DISTANCE(poly, geom, 0.05) < 5`` SpatiaLite ``Distance(poly, geom) < 5`` -========== =============================================== +========== ================================================== .. fieldlookup:: distance_lte @@ -595,13 +596,13 @@ Example:: Zipcode.objects.filter(poly__distance_lte=(geom, D(m=5))) -========== ================================================ +========== =================================================== Backend SQL Equivalent -========== ================================================ -PostGIS ``ST_Distance(poly, geom) <= 5`` +========== =================================================== +PostGIS ``ST_Distance/ST_Distance_Sphere(poly, geom) <= 5`` Oracle ``SDO_GEOM.SDO_DISTANCE(poly, geom, 0.05) <= 5`` SpatiaLite ``Distance(poly, geom) <= 5`` -========== ================================================ +========== =================================================== .. fieldlookup:: dwithin From f35b8f8e3f811c2f5c13ea96044d740917b16d47 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 11 Oct 2015 12:06:05 +0200 Subject: [PATCH 059/756] [1.9.x] Added precision about GeoJSON field outputs Backport of 533c1099 from master. --- docs/ref/contrib/gis/functions.txt | 4 +++- docs/ref/contrib/gis/geos.txt | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt index 06fe57fe996a..490a1ef13dd2 100644 --- a/docs/ref/contrib/gis/functions.txt +++ b/docs/ref/contrib/gis/functions.txt @@ -55,7 +55,9 @@ AsGeoJSON *Availability*: PostGIS, SpatiaLite Accepts a single geographic field or expression and returns a `GeoJSON -`_ representation of the geometry. +`_ representation of the geometry. Note that the result is +not a complete GeoJSON structure but only the ``geometry`` key content of a +GeoJSON structure. See also :doc:`/ref/contrib/gis/serializers`. Example:: diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index d2e4bafe0068..47f815d4cbd1 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -282,7 +282,9 @@ that are a part of this geometry. .. attribute:: GEOSGeometry.json -Returns the GeoJSON representation of the geometry. +Returns the GeoJSON representation of the geometry. Note that the result is not +a complete GeoJSON structure but only the ``geometry`` key content of a +GeoJSON structure. See also :doc:`/ref/contrib/gis/serializers`. .. note:: From 33c6a8b621eeec0425e9939680b67fc45729370b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 13 Oct 2015 04:24:22 -0700 Subject: [PATCH 060/756] [1.9.x] Fixed typo in docs/ref/models/relations.txt. Backport of f6b9e6bf4fab731a28489ec735cc330255ef03ab from master --- docs/ref/models/relations.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/models/relations.txt b/docs/ref/models/relations.txt index fc4b10af932b..e89355679ad1 100644 --- a/docs/ref/models/relations.txt +++ b/docs/ref/models/relations.txt @@ -195,7 +195,7 @@ will first disassociate any existing objects in the related set before adding the contents of ``new_list``. Otherwise the objects in ``new_list`` will be added to the existing related object set. -.. versionchanged:1.9 +.. versionchanged:: 1.9 In earlier versions, direct assignment used to perform ``clear()`` followed by ``add()``. It now performs a ``set()`` with the keyword argument From aa0a3b680eb7e6a29ae004fcf229d288a702d89e Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Tue, 13 Oct 2015 23:07:53 +0500 Subject: [PATCH 061/756] [1.9.x] Fixed quotes in GeoQuerySet aggregates examples. Backport of 92c1ae1b0b9955a511c19e07921c10126b6faa54 from master --- docs/ref/contrib/gis/geoquerysets.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index d30f1a8bef61..7fe2fec2b96a 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -1332,7 +1332,7 @@ comprising the lower left coordinate and the upper right coordinate. Example:: >>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(Extent('poly')) - >>> print(qs[poly__extent]) + >>> print(qs['poly__extent']) (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) ``Extent3D`` @@ -1349,7 +1349,7 @@ and z coordinates). Example:: >>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(Extent3D('poly')) - >>> print(qs[poly__extent3d]) + >>> print(qs['poly__extent3d']) (-96.8016128540039, 29.7633724212646, 0, -95.3631439208984, 32.782058715820, 0) ``MakeLine`` @@ -1365,7 +1365,7 @@ Returns a ``LineString`` constructed from the point field geometries in the Example:: >>> print(City.objects.filter(name__in=('Houston', 'Dallas') - ... ).aggregate(MakeLine('poly'))[poly__makeline] + ... ).aggregate(MakeLine('poly'))['poly__makeline'] LINESTRING (-95.3631510000000020 29.7633739999999989, -96.8016109999999941 32.7820570000000018) ``Union`` From 38d6e1e2ada93e5d8c672ba18f1f8e3cd6c5ca76 Mon Sep 17 00:00:00 2001 From: Antoine Catton Date: Fri, 9 Oct 2015 10:55:19 -0600 Subject: [PATCH 062/756] [1.9.x] Fixed #25535 -- Made ForeignObject checks less strict. Check that the foreign object `from_fields` are a subset of any unique constraints on the foreign model. Backport of 80dac8c33e7f6f22577e4346f44e4c5ee89b648c and c7aff31397a7228f6ac2e33c10ebdf36c4b7a9b7 from master --- django/db/models/fields/related.py | 25 ++++-- docs/ref/checks.txt | 5 +- tests/foreign_object/tests.py | 53 +++++++++++- .../test_relative_fields.py | 83 ++++++++++++++++++- 4 files changed, 154 insertions(+), 12 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index d3f5874409d5..5fa80e59ca02 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -473,22 +473,35 @@ def _check_unique_target(self): if not self.foreign_related_fields: return [] - has_unique_field = any(rel_field.unique - for rel_field in self.foreign_related_fields) - if not has_unique_field and len(self.foreign_related_fields) > 1: + unique_foreign_fields = { + frozenset([f.name]) + for f in self.remote_field.model._meta.get_fields() + if getattr(f, 'unique', False) + } + unique_foreign_fields.update({ + frozenset(ut) + for ut in self.remote_field.model._meta.unique_together + }) + foreign_fields = {f.name for f in self.foreign_related_fields} + has_unique_constraint = any(u <= foreign_fields for u in unique_foreign_fields) + + if not has_unique_constraint and len(self.foreign_related_fields) > 1: field_combination = ', '.join("'%s'" % rel_field.name for rel_field in self.foreign_related_fields) model_name = self.remote_field.model.__name__ return [ checks.Error( - "None of the fields %s on model '%s' have a unique=True constraint." + "No subset of the fields %s on model '%s' is unique." % (field_combination, model_name), - hint=None, + hint=( + "Add unique=True on any of those fields or add at " + "least a subset of them to a unique_together constraint." + ), obj=self, id='fields.E310', ) ] - elif not has_unique_field: + elif not has_unique_constraint: field_name = self.foreign_related_fields[0].name model_name = self.remote_field.model.__name__ return [ diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index 208e3fb81fb2..3cd68a43c8ad 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -189,8 +189,9 @@ Related Fields for ````. * **fields.E306**: Related name must be a valid Python identifier or end with a ``'+'``. -* **fields.E310**: None of the fields ````, ````, ... on model - ```` have a ``unique=True`` constraint. +* **fields.E310**: No subset of the fields ````, ````, ... on + model ```` is unique. Add ``unique=True`` on any of those fields or + add at least a subset of them to a unique_together constraint. * **fields.E311**: ```` must set ``unique=True`` because it is referenced by a ``ForeignKey``. * **fields.E320**: Field specifies ``on_delete=SET_NULL``, but cannot be null. diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py index 06e2551e4fdf..51886453255f 100644 --- a/tests/foreign_object/tests.py +++ b/tests/foreign_object/tests.py @@ -2,7 +2,9 @@ from operator import attrgetter from django.core.exceptions import FieldError -from django.test import TestCase, skipUnlessDBFeature +from django.db import models +from django.db.models.fields.related import ForeignObject +from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.utils import translation from .models import ( @@ -391,3 +393,52 @@ def test_batch_create_foreign_object(self): """ See: https://code.djangoproject.com/ticket/21566 """ objs = [Person(name="abcd_%s" % i, person_country=self.usa) for i in range(0, 5)] Person.objects.bulk_create(objs, 10) + + +class TestModelCheckTests(SimpleTestCase): + + def test_check_composite_foreign_object(self): + class Parent(models.Model): + a = models.PositiveIntegerField() + b = models.PositiveIntegerField() + + class Meta: + unique_together = (('a', 'b'),) + + class Child(models.Model): + a = models.PositiveIntegerField() + b = models.PositiveIntegerField() + value = models.CharField(max_length=255) + parent = ForeignObject( + Parent, + on_delete=models.SET_NULL, + from_fields=('a', 'b'), + to_fields=('a', 'b'), + related_name='children', + ) + + self.assertEqual(Child._meta.get_field('parent').check(from_model=Child), []) + + def test_check_subset_composite_foreign_object(self): + class Parent(models.Model): + a = models.PositiveIntegerField() + b = models.PositiveIntegerField() + c = models.PositiveIntegerField() + + class Meta: + unique_together = (('a', 'b'),) + + class Child(models.Model): + a = models.PositiveIntegerField() + b = models.PositiveIntegerField() + c = models.PositiveIntegerField() + d = models.CharField(max_length=255) + parent = ForeignObject( + Parent, + on_delete=models.SET_NULL, + from_fields=('a', 'b', 'c'), + to_fields=('a', 'b', 'c'), + related_name='children', + ) + + self.assertEqual(Child._meta.get_field('parent').check(from_model=Child), []) diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py index ad1c25da5237..3ba95fdbe41a 100644 --- a/tests/invalid_models_tests/test_relative_fields.py +++ b/tests/invalid_models_tests/test_relative_fields.py @@ -5,6 +5,7 @@ from django.core.checks import Error, Warning as DjangoWarning from django.db import models +from django.db.models.fields.related import ForeignObject from django.test import ignore_warnings from django.test.testcases import skipIfDBFeature from django.test.utils import override_settings @@ -507,9 +508,11 @@ class MMembership(models.Model): errors = field.check() expected = [ Error( - "None of the fields 'country_id', 'city_id' on model 'Person' " - "have a unique=True constraint.", - hint=None, + "No subset of the fields 'country_id', 'city_id' on model 'Person' is unique.", + hint=( + "Add unique=True on any of those fields or add at least " + "a subset of them to a unique_together constraint." + ), obj=field, id='fields.E310', ) @@ -1395,3 +1398,77 @@ class Invitation(models.Model): obj=field, id='fields.E337')] self.assertEqual(expected, errors) + + def test_superset_foreign_object(self): + class Parent(models.Model): + a = models.PositiveIntegerField() + b = models.PositiveIntegerField() + c = models.PositiveIntegerField() + + class Meta: + unique_together = (('a', 'b', 'c'),) + + class Child(models.Model): + a = models.PositiveIntegerField() + b = models.PositiveIntegerField() + value = models.CharField(max_length=255) + parent = ForeignObject( + Parent, + on_delete=models.SET_NULL, + from_fields=('a', 'b'), + to_fields=('a', 'b'), + related_name='children', + ) + + field = Child._meta.get_field('parent') + errors = field.check(from_model=Child) + expected = [ + Error( + "No subset of the fields 'a', 'b' on model 'Parent' is unique.", + hint=( + "Add unique=True on any of those fields or add at least " + "a subset of them to a unique_together constraint." + ), + obj=field, + id='fields.E310', + ), + ] + self.assertEqual(expected, errors) + + def test_insersection_foreign_object(self): + class Parent(models.Model): + a = models.PositiveIntegerField() + b = models.PositiveIntegerField() + c = models.PositiveIntegerField() + d = models.PositiveIntegerField() + + class Meta: + unique_together = (('a', 'b', 'c'),) + + class Child(models.Model): + a = models.PositiveIntegerField() + b = models.PositiveIntegerField() + d = models.PositiveIntegerField() + value = models.CharField(max_length=255) + parent = ForeignObject( + Parent, + on_delete=models.SET_NULL, + from_fields=('a', 'b', 'd'), + to_fields=('a', 'b', 'd'), + related_name='children', + ) + + field = Child._meta.get_field('parent') + errors = field.check(from_model=Child) + expected = [ + Error( + "No subset of the fields 'a', 'b', 'd' on model 'Parent' is unique.", + hint=( + "Add unique=True on any of those fields or add at least " + "a subset of them to a unique_together constraint." + ), + obj=field, + id='fields.E310', + ), + ] + self.assertEqual(expected, errors) From 36e7d275ec1a65c9378ba29d4be005deb5703889 Mon Sep 17 00:00:00 2001 From: Antoine Catton Date: Mon, 5 Oct 2015 14:13:14 -0600 Subject: [PATCH 063/756] [1.9.x] Fixed #25506 -- Allowed filtering over a RawSQL annotation. Co-Authored-By: Gavin Wahl Backport of b971c1cd78a0bf831c1c30080089c4a384d037a0 from master --- django/db/models/lookups.py | 2 +- docs/releases/1.8.6.txt | 2 ++ tests/expressions/tests.py | 12 ++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py index 93053c816099..b50711ada464 100644 --- a/django/db/models/lookups.py +++ b/django/db/models/lookups.py @@ -151,7 +151,7 @@ def process_lhs(self, compiler, connection, lhs=None): lhs_sql = connection.ops.field_cast_sql( db_type, field_internal_type) % lhs_sql lhs_sql = connection.ops.lookup_cast(self.lookup_name, field_internal_type) % lhs_sql - return lhs_sql, params + return lhs_sql, list(params) def as_sql(self, compiler, connection): lhs_sql, params = self.process_lhs(compiler, connection) diff --git a/docs/releases/1.8.6.txt b/docs/releases/1.8.6.txt index b972ee014870..4e455d767243 100644 --- a/docs/releases/1.8.6.txt +++ b/docs/releases/1.8.6.txt @@ -17,3 +17,5 @@ Bugfixes * Fixed system check crash on ``ForeignKey`` to abstract model (:ticket:`25503`). + +* Allowed filtering over a ``RawSQL`` annotation (:ticket:`25506`). diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py index 1af0b6e7a227..f68892782f6a 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -57,6 +57,18 @@ def test_annotate_values_aggregate(self): ) self.assertEqual(companies['result'], 2395) + def test_annotate_values_filter(self): + companies = Company.objects.annotate( + foo=RawSQL('%s', ['value']), + ).filter(foo='value').order_by('name') + self.assertQuerysetEqual( + companies, [ + '', + '', + '', + ], + ) + def test_filter_inter_attribute(self): # We can filter on attribute relationships on same model obj, e.g. # find companies where the number of employees is greater From d1ccf7b78465ba6fc9a2d7fc0c69c71a7426bf1a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 14 Oct 2015 12:22:53 -0700 Subject: [PATCH 064/756] [1.9.x] Forwardported 1.8.6 release note. --- docs/releases/1.8.6.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/releases/1.8.6.txt b/docs/releases/1.8.6.txt index 4e455d767243..44380cded55d 100644 --- a/docs/releases/1.8.6.txt +++ b/docs/releases/1.8.6.txt @@ -18,4 +18,8 @@ Bugfixes * Fixed system check crash on ``ForeignKey`` to abstract model (:ticket:`25503`). +* Fixed incorrect queries when you have multiple ``ManyToManyField``\s on + different models that have the same field name, point to the same model, and + have their reverse relations disabled (:ticket:`25545`). + * Allowed filtering over a ``RawSQL`` annotation (:ticket:`25506`). From faafd557227bc175568ad05820599c09faf5899f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 15 Oct 2015 12:36:28 -0700 Subject: [PATCH 065/756] [1.9.x] Used SchemaEditor.delete_model() for teardown in schema tests. Some third-party database backends (MSSQL) have custom delete_model() requirements that must be executed. Thanks Michael Manfre for the initial patch and review. Backport of 4dcc2a195595f8d7ddad45bc4baf98ffdeec7f41 from master --- tests/schema/tests.py | 60 +++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 3c7bf9660501..f42049e3393d 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -69,28 +69,16 @@ def tearDown(self): def delete_tables(self): "Deletes all model tables for our models for a clean test environment" converter = connection.introspection.table_name_converter - with connection.cursor() as cursor: + with atomic(): connection.disable_constraint_checking() - table_names = connection.introspection.table_names(cursor) + table_names = connection.introspection.table_names() for model in itertools.chain(SchemaTests.models, self.local_models): - # Remove any M2M tables first - for field in model._meta.local_many_to_many: - with atomic(): - tbl = converter(field.remote_field.through._meta.db_table) - if tbl in table_names: - cursor.execute(connection.schema_editor().sql_delete_table % { - "table": connection.ops.quote_name(tbl), - }) - table_names.remove(tbl) - # Then remove the main tables - with atomic(): - tbl = converter(model._meta.db_table) - if tbl in table_names: - cursor.execute(connection.schema_editor().sql_delete_table % { - "table": connection.ops.quote_name(tbl), - }) - table_names.remove(tbl) - connection.enable_constraint_checking() + tbl = converter(model._meta.db_table) + if tbl in table_names: + with connection.schema_editor() as editor: + editor.delete_model(model) + table_names.remove(tbl) + connection.enable_constraint_checking() def column_classes(self, model): with connection.cursor() as cursor: @@ -945,11 +933,7 @@ class LocalBookWithM2M(Model): class Meta: app_label = 'schema' apps = new_apps - - self.local_models = [ - LocalBookWithM2M, - LocalBookWithM2M._meta.get_field('tags').remote_field.through, - ] + self.local_models = [LocalBookWithM2M] # Create the tables with connection.schema_editor() as editor: editor.create_model(Author) @@ -1028,7 +1012,6 @@ class Meta: # Create an M2M field new_field = M2MFieldClass("schema.TagM2MTest", related_name="authors") new_field.contribute_to_class(LocalAuthorWithM2M, "tags") - self.local_models += [new_field.remote_field.through] # Ensure there's no m2m table there self.assertRaises(DatabaseError, self.column_classes, new_field.remote_field.through) # Add the field @@ -1048,6 +1031,14 @@ class Meta: # Ensure there's no m2m table there self.assertRaises(DatabaseError, self.column_classes, new_field.remote_field.through) + # Need to tear down using a model without the added M2M field that's + # been removed. + class LocalAuthorWithM2M(Model): + class Meta: + app_label = 'schema' + apps = new_apps + self.local_models = [LocalAuthorWithM2M] + def test_m2m(self): self._test_m2m(ManyToManyField) @@ -1117,12 +1108,7 @@ class LocalBookWithM2M(Model): class Meta: app_label = 'schema' apps = new_apps - - self.local_models = [ - LocalBookWithM2M, - LocalBookWithM2M._meta.get_field('tags').remote_field.through, - ] - + self.local_models = [LocalBookWithM2M] # Create the tables with connection.schema_editor() as editor: editor.create_model(Author) @@ -1144,7 +1130,6 @@ class Meta: old_field = LocalBookWithM2M._meta.get_field("tags") new_field = M2MFieldClass(UniqueTest) new_field.contribute_to_class(LocalBookWithM2M, "uniques") - self.local_models += [new_field.remote_field.through] with connection.schema_editor() as editor: editor.alter_field(LocalBookWithM2M, old_field, new_field) # Ensure old M2M is gone @@ -1152,6 +1137,15 @@ class Meta: DatabaseError, self.column_classes, LocalBookWithM2M._meta.get_field("tags").remote_field.through ) + + # This model looks like the new model and is used for teardown. + class LocalBookWithM2M(Model): + uniques = M2MFieldClass(UniqueTest) + + class Meta: + app_label = 'schema' + apps = new_apps + self.local_models = [LocalBookWithM2M] # Ensure the new M2M exists and points to UniqueTest constraints = self.get_constraints(new_field.remote_field.through._meta.db_table) if connection.features.supports_foreign_keys: From 9039ff60e3f9848338d3fbc6957f9e25e9edc22b Mon Sep 17 00:00:00 2001 From: Yusuke Miyazaki Date: Mon, 12 Oct 2015 15:34:16 +0900 Subject: [PATCH 066/756] [1.9.x] Fixed #25346 -- Allowed collectstatic to delete broken symlinks. Backport of 0922bbf18d3ae8f37e1823df2dbc270d33334548 from master --- .../management/commands/collectstatic.py | 7 ++++++- tests/staticfiles_tests/test_management.py | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index 523f313c13d8..e6b6198952f1 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -218,7 +218,12 @@ def clear_dir(self, path): smart_text(fpath), level=1) else: self.log("Deleting '%s'" % smart_text(fpath), level=1) - self.storage.delete(fpath) + full_path = self.storage.path(fpath) + if not os.path.exists(full_path) and os.path.lexists(full_path): + # Delete broken symlinks + os.unlink(full_path) + else: + self.storage.delete(fpath) for d in dirs: self.clear_dir(os.path.join(path, d)) diff --git a/tests/staticfiles_tests/test_management.py b/tests/staticfiles_tests/test_management.py index 09557185edd7..94166f9888d2 100644 --- a/tests/staticfiles_tests/test_management.py +++ b/tests/staticfiles_tests/test_management.py @@ -323,8 +323,8 @@ class TestCollectionLinks(CollectionTestCase, TestDefaults): the standard file resolving tests here, to make sure using ``--link`` does not change the file-selection semantics. """ - def run_collectstatic(self): - super(TestCollectionLinks, self).run_collectstatic(link=True) + def run_collectstatic(self, clear=False): + super(TestCollectionLinks, self).run_collectstatic(link=True, clear=clear) def test_links_created(self): """ @@ -340,3 +340,13 @@ def test_broken_symlink(self): os.unlink(path) self.run_collectstatic() self.assertTrue(os.path.islink(path)) + + def test_clear_broken_symlink(self): + """ + With ``--clear``, broken symbolic links are deleted. + """ + nonexistent_file_path = os.path.join(settings.STATIC_ROOT, 'nonexistent.txt') + broken_symlink_path = os.path.join(settings.STATIC_ROOT, 'symlink.txt') + os.symlink(nonexistent_file_path, broken_symlink_path) + self.run_collectstatic(clear=True) + self.assertFalse(os.path.lexists(broken_symlink_path)) From 7a3b486ccd4aba0a9cf69475895a9b89ae2438b3 Mon Sep 17 00:00:00 2001 From: Josh Smeaton Date: Wed, 14 Oct 2015 10:07:42 +1100 Subject: [PATCH 067/756] [1.9.x] Fixed #25517 -- Made Concat function idempotent on SQLite. Backport of 6c95b134e9b2d5641c123551c080305e90e6a89d from master --- django/db/models/functions.py | 15 +++++++++------ docs/releases/1.8.6.txt | 2 ++ tests/db_functions/tests.py | 16 +++++++++++++++- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/django/db/models/functions.py b/django/db/models/functions.py index ac24aa7bdca0..ffa3e360840f 100644 --- a/django/db/models/functions.py +++ b/django/db/models/functions.py @@ -42,10 +42,10 @@ def __init__(self, left, right, **extra): super(ConcatPair, self).__init__(left, right, **extra) def as_sqlite(self, compiler, connection): - self.arg_joiner = ' || ' - self.template = '%(expressions)s' - self.coalesce() - return super(ConcatPair, self).as_sql(compiler, connection) + coalesced = self.coalesce() + coalesced.arg_joiner = ' || ' + coalesced.template = '%(expressions)s' + return super(ConcatPair, coalesced).as_sql(compiler, connection) def as_mysql(self, compiler, connection): # Use CONCAT_WS with an empty separator so that NULLs are ignored. @@ -55,9 +55,12 @@ def as_mysql(self, compiler, connection): def coalesce(self): # null on either side results in null for expression, wrap with coalesce + c = self.copy() expressions = [ - Coalesce(expression, Value('')) for expression in self.get_source_expressions()] - self.set_source_expressions(expressions) + Coalesce(expression, Value('')) for expression in c.get_source_expressions() + ] + c.set_source_expressions(expressions) + return c class Concat(Func): diff --git a/docs/releases/1.8.6.txt b/docs/releases/1.8.6.txt index 44380cded55d..178d714f013a 100644 --- a/docs/releases/1.8.6.txt +++ b/docs/releases/1.8.6.txt @@ -23,3 +23,5 @@ Bugfixes have their reverse relations disabled (:ticket:`25545`). * Allowed filtering over a ``RawSQL`` annotation (:ticket:`25506`). + +* Made the ``Concat`` database function idempotent on SQLite (:ticket:`25517`). diff --git a/tests/db_functions/tests.py b/tests/db_functions/tests.py index 00aac82a7b3d..c18a4b25af70 100644 --- a/tests/db_functions/tests.py +++ b/tests/db_functions/tests.py @@ -7,7 +7,8 @@ from django.db.models import CharField, TextField, Value as V from django.db.models.expressions import RawSQL from django.db.models.functions import ( - Coalesce, Concat, Greatest, Least, Length, Lower, Now, Substr, Upper, + Coalesce, Concat, ConcatPair, Greatest, Least, Length, Lower, Now, Substr, + Upper, ) from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.utils import six, timezone @@ -353,6 +354,19 @@ def test_concat_mixed_char_text(self): expected = article.title + ' - ' + article.text self.assertEqual(expected.upper(), article.title_text) + @skipUnless(connection.vendor == 'sqlite', "sqlite specific implementation detail.") + def test_concat_coalesce_idempotent(self): + pair = ConcatPair(V('a'), V('b')) + # Check nodes counts + self.assertEqual(len(list(pair.flatten())), 3) + self.assertEqual(len(list(pair.coalesce().flatten())), 7) # + 2 Coalesce + 2 Value() + self.assertEqual(len(list(pair.flatten())), 3) + + def test_concat_sql_generation_idempotency(self): + qs = Article.objects.annotate(description=Concat('title', V(': '), 'summary')) + # Multiple compilations should not alter the generated query. + self.assertEqual(str(qs.query), str(qs.all().query)) + def test_lower(self): Author.objects.create(name='John Smith', alias='smithj') Author.objects.create(name='Rhonda') From d8b26450320825377676869e08a9ff38dce2b393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 18 Oct 2015 11:42:42 +0300 Subject: [PATCH 068/756] [1.9.x] Fixed #25565 -- Removed action="" from admin forms (invalid in HTML5). Backport of 6ff500127a21f5f2104dc77a09dfa73d5f7c94ce from master --- django/contrib/admin/templates/admin/change_list.html | 2 +- django/contrib/admin/templates/admin/delete_confirmation.html | 2 +- .../admin/templates/admin/delete_selected_confirmation.html | 2 +- django/contrib/admin/templates/admin/search_form.html | 2 +- .../admin/templates/registration/password_change_form.html | 2 +- .../admin/templates/registration/password_reset_confirm.html | 2 +- .../admin/templates/registration/password_reset_form.html | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index e99b1e0a6e5c..0bd44d96b439 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -81,7 +81,7 @@

    {% trans 'Filter' %}

    {% endif %} {% endblock %} -
    {% csrf_token %} + {% csrf_token %} {% if cl.formset %}
    {{ cl.formset.management_form }}
    {% endif %} diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html index cc83be828919..bc26bca3094f 100644 --- a/django/contrib/admin/templates/admin/delete_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_confirmation.html @@ -33,7 +33,7 @@ {% include "admin/includes/object_delete_summary.html" %}

    {% trans "Objects" %}

      {{ deleted_objects|unordered_list }}
    - {% csrf_token %} + {% csrf_token %}
    {% if is_popup %}{% endif %} diff --git a/django/contrib/admin/templates/admin/delete_selected_confirmation.html b/django/contrib/admin/templates/admin/delete_selected_confirmation.html index 8c8b8aa2f749..5ebd6432e134 100644 --- a/django/contrib/admin/templates/admin/delete_selected_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_selected_confirmation.html @@ -34,7 +34,7 @@

    {% trans "Objects" %}

    {% for deletable_object in deletable_objects %}
      {{ deletable_object|unordered_list }}
    {% endfor %} - {% csrf_token %} + {% csrf_token %}
    {% for obj in queryset %} diff --git a/django/contrib/admin/templates/admin/search_form.html b/django/contrib/admin/templates/admin/search_form.html index b3cb2c7a7aab..f241f14cca21 100644 --- a/django/contrib/admin/templates/admin/search_form.html +++ b/django/contrib/admin/templates/admin/search_form.html @@ -1,6 +1,6 @@ {% load i18n admin_static %} {% if cl.search_fields %} -
    +
    diff --git a/django/contrib/admin/templates/registration/password_change_form.html b/django/contrib/admin/templates/registration/password_change_form.html index a0b8375af77c..ddb90e8f82e3 100644 --- a/django/contrib/admin/templates/registration/password_change_form.html +++ b/django/contrib/admin/templates/registration/password_change_form.html @@ -14,7 +14,7 @@ {% block content %}
    -{% csrf_token %} +{% csrf_token %}
    {% if form.errors %}

    diff --git a/django/contrib/admin/templates/registration/password_reset_confirm.html b/django/contrib/admin/templates/registration/password_reset_confirm.html index bd24806d46ca..a1e0150a5dab 100644 --- a/django/contrib/admin/templates/registration/password_reset_confirm.html +++ b/django/contrib/admin/templates/registration/password_reset_confirm.html @@ -16,7 +16,7 @@

    {% trans "Please enter your new password twice so we can verify you typed it in correctly." %}

    -{% csrf_token %} +{% csrf_token %} {{ form.new_password1.errors }}

    {{ form.new_password1 }}

    {{ form.new_password2.errors }} diff --git a/django/contrib/admin/templates/registration/password_reset_form.html b/django/contrib/admin/templates/registration/password_reset_form.html index dc05cd024563..23b68a358225 100644 --- a/django/contrib/admin/templates/registration/password_reset_form.html +++ b/django/contrib/admin/templates/registration/password_reset_form.html @@ -14,7 +14,7 @@

    {% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}

    -{% csrf_token %} +{% csrf_token %} {{ form.email.errors }}

    {{ form.email }}

    From 3aeb84df250c2f739df1f551739434fb5839c53c Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Fri, 16 Oct 2015 12:21:30 -0400 Subject: [PATCH 069/756] [1.9.x] Fixed #25560 -- Made empty string related_name invalid. Thanks to Ali Lotfi for the initial report and patch and Tim Graham for the review. Backport of c1b6a8a98b08fedfa3f3c69bc7add94c5841ba57 from master --- django/db/models/fields/related.py | 2 +- django/db/models/fields/reverse_related.py | 2 +- tests/invalid_models_tests/test_relative_fields.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 5fa80e59ca02..f10a32ae390e 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -124,7 +124,7 @@ def _check_related_name_is_valid(self): import re import keyword related_name = self.remote_field.related_name - if not related_name: + if related_name is None: return [] is_valid_id = True if keyword.iskeyword(related_name): diff --git a/django/db/models/fields/reverse_related.py b/django/db/models/fields/reverse_related.py index 015fa02165c4..304e688d5aaf 100644 --- a/django/db/models/fields/reverse_related.py +++ b/django/db/models/fields/reverse_related.py @@ -154,7 +154,7 @@ def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): def is_hidden(self): "Should the related object be hidden?" - return self.related_name is not None and self.related_name[-1] == '+' + return bool(self.related_name) and self.related_name[-1] == '+' def get_joining_columns(self): return self.field.get_reverse_joining_columns() diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py index 3ba95fdbe41a..bfdd2b7d7af4 100644 --- a/tests/invalid_models_tests/test_relative_fields.py +++ b/tests/invalid_models_tests/test_relative_fields.py @@ -662,6 +662,7 @@ def test_related_field_has_invalid_related_name(self): 'ends_with_whitespace_%s' % whitespace, 'with', # a Python keyword 'related_name\n', + '', ] # Python 2 crashes on non-ASCII strings. if six.PY3: From 094a60847a3e480fe694ce6c9f33832168987e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 19 Oct 2015 17:40:52 +0300 Subject: [PATCH 070/756] [1.9.x] Fixed #25566 -- Added title to admin's closing popup response page Backport of aff4b75c34d62ec932414b5099016007fff17317 from master --- django/contrib/admin/templates/admin/popup_response.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/templates/admin/popup_response.html b/django/contrib/admin/templates/admin/popup_response.html index 626ed98edad2..bbe8908617e7 100644 --- a/django/contrib/admin/templates/admin/popup_response.html +++ b/django/contrib/admin/templates/admin/popup_response.html @@ -1,6 +1,6 @@ - +{% load i18n %} - + {% trans 'Popup closing...' %} ``Media`` on Forms ------------------- +================== Widgets aren't the only objects that can have ``media`` definitions -- forms can also define ``media``. The rules for ``media`` definitions diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 0f690aae4699..1106980bac4b 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -208,7 +208,7 @@ Validation on a ``ModelForm`` There are two main steps involved in validating a ``ModelForm``: -1. :ref:`Validating the form ` +1. :doc:`Validating the form ` 2. :ref:`Validating the model instance ` Just like normal form validation, model form validation is triggered implicitly @@ -232,7 +232,7 @@ validation step, right after the form's ``clean()`` method is called. .. _overriding-modelform-clean-method: Overriding the clean() method -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can override the ``clean()`` method on a model form to provide additional validation in the same way you can on a normal form. @@ -251,7 +251,7 @@ attribute that gives its methods access to that specific model instance. validation, you must call the parent class's ``clean()`` method. Interaction with model validation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As part of the validation process, ``ModelForm`` will call the ``clean()`` method of each field on your model that has a corresponding field on your form. @@ -266,7 +266,7 @@ on the model's ``clean()`` hook. .. _considerations-regarding-model-errormessages: Considerations regarding model's ``error_messages`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error messages defined at the :attr:`form field ` level or at the diff --git a/docs/topics/http/index.txt b/docs/topics/http/index.txt index 3c53b2ea4d80..a606f01b73dd 100644 --- a/docs/topics/http/index.txt +++ b/docs/topics/http/index.txt @@ -1,3 +1,4 @@ +====================== Handling HTTP requests ====================== diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index e149805a119e..8b4aec530079 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -336,7 +336,7 @@ cookie-stored data to prevent tampering, a :setting:`SECRET_KEY` leak immediately escalates to a remote code execution vulnerability. Bundled Serializers -^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~ .. class:: serializers.JSONSerializer @@ -366,7 +366,7 @@ Bundled Serializers .. _custom-serializers: Write Your Own Serializer -^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~ Note that unlike :class:`~django.contrib.sessions.serializers.PickleSerializer`, the :class:`~django.contrib.sessions.serializers.JSONSerializer` cannot handle diff --git a/docs/topics/i18n/formatting.txt b/docs/topics/i18n/formatting.txt index 8a96bd74a9cf..c2efc2516a2a 100644 --- a/docs/topics/i18n/formatting.txt +++ b/docs/topics/i18n/formatting.txt @@ -1,5 +1,3 @@ -.. _format-localization: - =================== Format localization =================== diff --git a/docs/topics/i18n/timezones.txt b/docs/topics/i18n/timezones.txt index b964ccadbfc6..2adfd482679d 100644 --- a/docs/topics/i18n/timezones.txt +++ b/docs/topics/i18n/timezones.txt @@ -1,5 +1,3 @@ -.. _time-zones: - ========== Time zones ========== diff --git a/docs/topics/index.txt b/docs/topics/index.txt index 5521cd1c0eec..6f85baddb6e2 100644 --- a/docs/topics/index.txt +++ b/docs/topics/index.txt @@ -1,3 +1,4 @@ +============ Using Django ============ diff --git a/docs/topics/install.txt b/docs/topics/install.txt index e911494ceb31..f0fbc47881ea 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -158,7 +158,7 @@ It's easy, no matter which way you choose. .. _installing-official-release: Installing an official release with ``pip`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------------- This is the recommended way to install Django. @@ -183,7 +183,7 @@ This is the recommended way to install Django. .. _standalone pip installer: https://pip.pypa.io/en/latest/installing.html#install-pip Installing a distribution-specific package -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------------ Check the :doc:`distribution specific notes ` to see if your platform/distribution provides official Django packages/installers. @@ -194,7 +194,7 @@ contain the latest release of Django. .. _installing-development-version: Installing the development version -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +---------------------------------- .. admonition:: Tracking Django development diff --git a/docs/topics/migrations.txt b/docs/topics/migrations.txt index c197c5556fce..01a76f63bce3 100644 --- a/docs/topics/migrations.txt +++ b/docs/topics/migrations.txt @@ -11,7 +11,7 @@ designed to be mostly automatic, but you'll need to know when to make migrations, when to run them, and the common problems you might run into. The Commands ------------- +============ There are several commands which you will use to interact with migrations and Django's handling of database schema: @@ -53,7 +53,7 @@ need those options in some data migrations later on (for example, if you've set custom validators). Backend Support ---------------- +=============== Migrations are supported on all backends that Django ships with, as well as any third-party backends if they have programmed in support for schema @@ -63,7 +63,7 @@ However, some databases are more capable than others when it comes to schema migrations; some of the caveats are covered below. PostgreSQL -~~~~~~~~~~ +---------- PostgreSQL is the most capable of all the databases here in terms of schema support; the only caveat is that adding columns with default values will @@ -73,7 +73,7 @@ For this reason, it's recommended you always create new columns with ``null=True``, as this way they will be added immediately. MySQL -~~~~~ +----- MySQL lacks support for transactions around schema alteration operations, meaning that if a migration fails to apply you will have to manually unpick @@ -92,7 +92,7 @@ covers. This means that indexes that are possible on other backends will fail to be created under MySQL. SQLite -~~~~~~ +------ SQLite has very little built-in schema alteration support, and so Django attempts to emulate it by: @@ -110,7 +110,7 @@ developers to use SQLite on their local machines to develop less complex Django projects without the need for a full database. Workflow --------- +======== Working with migrations is simple. Make changes to your models - say, add a field and remove a model - and then run :djadmin:`makemigrations`:: @@ -150,7 +150,7 @@ one, you can use the :djadminopt:`--name` option:: $ python manage.py makemigrations --name changed_my_model your_app_label Version control -~~~~~~~~~~~~~~~ +--------------- Because migrations are stored in version control, you'll occasionally come across situations where you and another developer have both committed @@ -170,7 +170,7 @@ yourself - don't worry, this isn't difficult, and is explained more in :ref:`migration-files` below. Dependencies ------------- +============ While migrations are per-app, the tables and relationships implied by your models are too complex to be created for just one app at a time. When @@ -194,7 +194,7 @@ will be. .. _migration-files: Migration files ---------------- +=============== Migrations are stored as an on-disk format, referred to here as "migration files". These files are actually just normal Python files with @@ -240,7 +240,7 @@ more complex operations are not autodetectable and are only available via a hand-written migration, so don't be scared about editing them if you have to. Custom fields -~~~~~~~~~~~~~ +------------- You can't modify the number of positional arguments in an already migrated custom field without raising a ``TypeError``. The old migration will call the @@ -251,7 +251,7 @@ argument, please create a keyword argument and add something like .. _using-managers-in-migrations: Model managers -~~~~~~~~~~~~~~ +-------------- .. versionadded:: 1.8 @@ -279,7 +279,7 @@ Please refer to the notes about :ref:`historical-models` in migrations to see the implications that come along. Initial migrations -~~~~~~~~~~~~~~~~~~ +------------------ .. attribute:: Migration.initial @@ -306,7 +306,7 @@ already exist in the database and fake-applies the migration if so. Without from any other migration. Adding migrations to apps -------------------------- +========================= Adding migrations to new apps is straightforward - they come preconfigured to accept migrations, and so just run :djadmin:`makemigrations` once you've made @@ -344,7 +344,7 @@ Note that this only works given two things: .. _historical-models: Historical models ------------------ +================= When you run migrations, Django is working from historical versions of your models stored in the migration files. If you write Python code using the @@ -380,7 +380,7 @@ can opt to move them into a superclass. .. _migrations-removing-model-fields: Considerations when removing model fields ------------------------------------------ +========================================= .. versionadded:: 1.8 @@ -429,7 +429,7 @@ removing the old ones, you should be able to remove the field completely. .. _data-migrations: Data Migrations ---------------- +=============== As well as changing the database schema, you can also use migrations to change the data in the database itself, in conjunction with the schema if you want. @@ -514,7 +514,7 @@ want executed when migrating backwards. If this callable is omitted, migrating backwards will raise an exception. Accessing models from other apps -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-------------------------------- When writing a ``RunPython`` function that uses models from apps other than the one in which the migration is located, the migration's ``dependencies`` @@ -541,7 +541,7 @@ added a dependency that specifies the last migration of ``app2``:: ] More advanced migrations -~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------ If you're interested in the more advanced migration operations, or want to be able to write your own, see the :doc:`migration operations reference @@ -551,7 +551,7 @@ to be able to write your own, see the :doc:`migration operations reference .. _migration-squashing: Squashing migrations --------------------- +==================== You are encouraged to make migrations freely and not worry about how many you have; the migration code is optimized to deal with hundreds at a time without @@ -642,7 +642,7 @@ You must then transition the squashed migration to a normal migration by: .. _migration-serializing: Serializing values ------------------- +================== Migrations are just Python files containing the old definitions of your models - thus, to write them, Django must take the current state of your models and @@ -701,7 +701,7 @@ the main module body, rather than the class body. .. _custom-deconstruct-method: Adding a deconstruct() method -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +----------------------------- You can let Django serialize your own custom class instances by giving the class a ``deconstruct()`` method. It takes no arguments, and should return a tuple @@ -753,7 +753,7 @@ way into your constructor, and then returns those arguments exactly when deconstruct() is called. Supporting Python 2 and 3 -------------------------- +========================= In order to generate migrations that support both Python 2 and 3, all string literals used in your models and fields (e.g. ``verbose_name``, @@ -773,7 +773,7 @@ changes as it converts all the bytestring attributes to text strings; this is normal and should only happen once. Supporting multiple Django versions ------------------------------------ +=================================== If you are the maintainer of a third-party app with models, you may need to ship migrations that support multiple Django versions. In this case, you should diff --git a/docs/topics/performance.txt b/docs/topics/performance.txt index 718fa4762e0f..e50c63288fba 100644 --- a/docs/topics/performance.txt +++ b/docs/topics/performance.txt @@ -53,7 +53,7 @@ It's no good just guessing or assuming where the inefficiencies lie in your code. Django tools -^^^^^^^^^^^^ +~~~~~~~~~~~~ `django-debug-toolbar `_ is a very @@ -65,7 +65,7 @@ Third-party panels are also available for the toolbar, that can (for example) report on cache performance and template rendering times. Third-party services -^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~ There are a number of free services that will analyze and report on the performance of your site's pages from the perspective of a remote HTTP client, @@ -96,7 +96,7 @@ most skills, learning what "looks right" takes practice, but one of the most useful guidelines is: Work at the appropriate level -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Django offers many different ways of approaching things, but just because it's possible to do something in a certain way doesn't mean that it's the most @@ -259,13 +259,13 @@ Django comes with a few helpful pieces of :doc:`middleware ` that can help optimize your site's performance. They include: :class:`~django.middleware.http.ConditionalGetMiddleware` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Adds support for modern browsers to conditionally GET responses based on the ``ETag`` and ``Last-Modified`` headers. :class:`~django.middleware.gzip.GZipMiddleware` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Compresses responses for all modern browsers, saving bandwidth and transfer time. Note that GZipMiddleware is currently considered a security risk, and is @@ -276,7 +276,7 @@ Sessions -------- Using cached sessions -^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~ :ref:`Using cached sessions ` may be a way to increase performance by eliminating the need to load session data from a slower storage @@ -290,7 +290,7 @@ Static files, which by definition are not dynamic, make an excellent target for optimization gains. :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By taking advantage of web browsers' caching abilities, you can eliminate network hits entirely for a given file after the initial download. @@ -302,7 +302,7 @@ long-term without missing future changes - when a file changes, so will the tag, so browsers will reload the asset automatically. "Minification" -^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~ Several third-party Django tools and packages provide the ability to "minify" HTML, CSS, and JavaScript. They remove unnecessary whitespace, newlines, and @@ -410,7 +410,7 @@ performance gains for your application to outweigh the potential risks. With these caveats in mind, you should be aware of: `PyPy `_ -^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~ `PyPy `_ is an implementation of Python in Python itself (the 'standard' Python implementation is in C). PyPy can offer substantial @@ -422,7 +422,7 @@ Django is compatible, but you will need to check the compatibility of other libraries you rely on. C implementations of Python libraries -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Some Python libraries are also implemented in C, and can be much faster. They aim to offer the same APIs. Note that compatibility issues and behavior diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index e34553699dd8..87f98a4825e4 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -13,7 +13,7 @@ serializer to handle any format (text-based or not). form, you could use the :djadmin:`dumpdata` management command. Serializing data ----------------- +================ At the highest level, serializing data is a very simple operation:: @@ -50,7 +50,7 @@ This is useful if you want to serialize data directly to a file-like object .. _subset-of-fields: Subset of fields -~~~~~~~~~~~~~~~~ +---------------- If you only want a subset of fields to be serialized, you can specify a ``fields`` argument to the serializer:: @@ -69,7 +69,7 @@ be serialized. model, the deserializer will not be able to save deserialized instances. Inherited Models -~~~~~~~~~~~~~~~~ +---------------- If you have a model that is defined using an :ref:`abstract base class `, you don't have to do anything special to serialize @@ -102,7 +102,7 @@ serialize the ``Place`` models as well:: data = serializers.serialize('xml', all_objects) Deserializing data ------------------- +================== Deserializing data is also a fairly simple operation:: @@ -147,7 +147,7 @@ argument is passed in as ``True``:: .. _serialization-formats: Serialization formats ---------------------- +===================== Django supports a number of serialization formats, some of which require you to install third-party Python modules: @@ -167,7 +167,7 @@ Identifier Information .. _PyYAML: http://www.pyyaml.org/ XML -~~~ +--- The basic XML serialization format is quite simple:: @@ -226,7 +226,7 @@ This example links the given user with the permission models with PKs 46 and 47. .. _serialization-formats-json: JSON -~~~~ +---- When staying with the same example data as before it would be serialized as JSON in the following way:: @@ -276,7 +276,7 @@ Also note that GeoDjango provides a :doc:`customized GeoJSON serializer .. _ecma-262: http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 YAML -~~~~ +---- YAML serialization looks quite similar to JSON. The object list is serialized as a sequence mappings with the keys "pk", "model" and "fields". Each field is @@ -291,7 +291,7 @@ Referential fields are again just represented by the PK or sequence of PKs. .. _topics-serialization-natural-keys: Natural keys ------------- +============ The default serialization strategy for foreign keys and many-to-many relations is to serialize the value of the primary key(s) of the objects in the relation. @@ -327,7 +327,7 @@ key is a tuple of values that can be used to uniquely identify an object instance without using the primary key value. Deserialization of natural keys -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------- Consider the following two models:: @@ -420,7 +420,7 @@ model's manager has a ``get_by_natural_key()`` method and if so, use it to populate the deserialized object's primary key. Serialization of natural keys -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +----------------------------- So how do you get Django to emit a natural key when serializing an object? Firstly, you need to add another method -- this time to the model itself:: @@ -488,7 +488,7 @@ line flags to generate natural keys. key values, just don't define the ``get_by_natural_key()`` method. Dependencies during serialization -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +--------------------------------- Since natural keys rely on database lookups to resolve references, it is important that the data exists before it is referenced. You can't make diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 3572b31b9514..2b432211eff0 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -9,7 +9,7 @@ Django provides a small set of tools that come in handy when writing tests. .. _test-client: The test client ---------------- +=============== The test client is a Python class that acts as a dummy Web browser, allowing you to test your views and interact with your Django-powered application @@ -42,7 +42,7 @@ short: A comprehensive test suite should use a combination of both test types. Overview and a quick example -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +---------------------------- To use the test client, instantiate ``django.test.Client`` and retrieve Web pages:: @@ -105,7 +105,7 @@ Note a few important things about how the test client works: >>> csrf_client = Client(enforce_csrf_checks=True) Making requests -~~~~~~~~~~~~~~~ +--------------- Use the ``django.test.Client`` class to make requests. @@ -421,7 +421,7 @@ Use the ``django.test.Client`` class to make requests. to come from an :class:`~django.contrib.auth.models.AnonymousUser`. Testing responses -~~~~~~~~~~~~~~~~~ +----------------- The ``get()`` and ``post()`` methods both return a ``Response`` object. This ``Response`` object is *not* the same as the ``HttpResponse`` object returned @@ -535,7 +535,7 @@ of any settings in the HTTP headers. For example, you could determine the content type of a response using ``response['Content-Type']``. Exceptions -~~~~~~~~~~ +---------- If you point the test client at a view that raises an exception, that exception will be visible in the test case. You can then use a standard ``try ... except`` @@ -549,7 +549,7 @@ exceptions internally and converts them into the appropriate HTTP response codes. In these cases, you can check ``response.status_code`` in your test. Persistent state -~~~~~~~~~~~~~~~~ +---------------- The test client is stateful. If a response returns a cookie, then that cookie will be stored in the test client and sent with all subsequent ``get()`` and @@ -583,7 +583,7 @@ can access these properties as part of a test condition. session.save() Example -~~~~~~~ +------- The following is a simple unit test using the test client:: @@ -612,7 +612,7 @@ The following is a simple unit test using the test client:: .. _django-testcase-subclasses: Provided test case classes --------------------------- +========================== Normal Python unit test classes extend a base class of :class:`unittest.TestCase`. Django provides a few extensions of this base class: @@ -627,7 +627,7 @@ Normal Python unit test classes extend a base class of Hierarchy of Django unit testing classes SimpleTestCase -~~~~~~~~~~~~~~ +-------------- .. class:: SimpleTestCase() @@ -712,7 +712,7 @@ then you should use :class:`~django.test.TransactionTestCase` or calling ``super()`` to avoid this. TransactionTestCase -~~~~~~~~~~~~~~~~~~~ +------------------- .. class:: TransactionTestCase() @@ -761,7 +761,7 @@ to test the effects of commit and rollback: ``TransactionTestCase`` inherits from :class:`~django.test.SimpleTestCase`. TestCase -~~~~~~~~ +-------- .. class:: TestCase() @@ -832,7 +832,7 @@ additions, including: .. _live-test-server: LiveServerTestCase -~~~~~~~~~~~~~~~~~~ +------------------ .. class:: LiveServerTestCase() @@ -986,10 +986,10 @@ out the `full reference`_ for more details. .. _Selenium documentation: http://seleniumhq.org/docs/04_webdriver_advanced.html#explicit-waits Test cases features -------------------- +=================== Default test client -~~~~~~~~~~~~~~~~~~~ +------------------- .. attribute:: SimpleTestCase.client @@ -1028,7 +1028,7 @@ This means, instead of instantiating a ``Client`` in each test:: self.assertEqual(response.status_code, 200) Customizing the test client -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +--------------------------- .. attribute:: SimpleTestCase.client_class @@ -1052,7 +1052,7 @@ attribute:: .. _topics-testing-fixtures: Fixture loading -~~~~~~~~~~~~~~~ +--------------- .. attribute:: TransactionTestCase.fixtures @@ -1109,7 +1109,7 @@ using multiple databases and set :attr:`multi_db=True `, fixtures will be loaded into all databases. URLconf configuration -~~~~~~~~~~~~~~~~~~~~~ +--------------------- .. attribute:: SimpleTestCase.urls @@ -1148,7 +1148,7 @@ URLconf for the duration of the test case. .. _emptying-test-outbox: Multi-database support -~~~~~~~~~~~~~~~~~~~~~~ +---------------------- .. attribute:: TransactionTestCase.multi_db @@ -1186,7 +1186,7 @@ If ``multi_db=True``, fixtures are loaded into all databases. .. _overriding-settings: Overriding settings -~~~~~~~~~~~~~~~~~~~ +------------------- .. warning:: @@ -1362,7 +1362,7 @@ MEDIA_ROOT, DEFAULT_FILE_STORAGE Default file storage ================================ ======================== Emptying the test outbox -~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------ If you use any of Django's custom ``TestCase`` classes, the test runner will clear the contents of the test email outbox at the start of each test case. @@ -1372,7 +1372,7 @@ For more detail on email services during tests, see `Email services`_ below. .. _assertions: Assertions -~~~~~~~~~~ +---------- As Python's normal :class:`unittest.TestCase` class implements assertion methods such as :meth:`~unittest.TestCase.assertTrue` and @@ -1667,7 +1667,7 @@ your test suite. .. _topics-testing-email: Email services --------------- +============== If any of your Django views send email using :doc:`Django's email functionality `, you probably don't want to send email each time @@ -1724,7 +1724,7 @@ manually, assign the empty list to ``mail.outbox``:: .. _topics-testing-management-commands: Management Commands -------------------- +=================== Management commands can be tested with the :func:`~django.core.management.call_command` function. The output can be @@ -1743,7 +1743,7 @@ redirected into a ``StringIO`` instance:: .. _skipping-tests: Skipping tests --------------- +============== .. currentmodule:: django.test From 1aee5e8582dacd1908561dd57ea9883fcf4a2126 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 22 Jan 2016 10:55:31 -0500 Subject: [PATCH 364/756] [1.9.x] Fixed #26116 -- Corrected schema's test_alter_implicit_id_to_explicit. AUTOINCREMENT is dropped converting an AutoField to IntegerField which isn't the point of this test. MySQL would warn or error about this. Backport of b49cc8664306f8b44e9e12ebb9e43791d508ec74 from master --- tests/schema/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index dfd0659d3472..ab95b0c79eaa 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -826,7 +826,7 @@ def test_alter_implicit_id_to_explicit(self): editor.create_model(Author) old_field = Author._meta.get_field("id") - new_field = IntegerField(primary_key=True) + new_field = AutoField(primary_key=True) new_field.set_attributes_from_name("id") new_field.model = Author with connection.schema_editor() as editor: @@ -834,6 +834,7 @@ def test_alter_implicit_id_to_explicit(self): # This will fail if DROP DEFAULT is inadvertently executed on this # field which drops the id sequence, at least on PostgreSQL. Author.objects.create(name='Foo') + Author.objects.create(name='Bar') def test_alter_int_pk_to_autofield_pk(self): """ From 3ab0439b69d6e26671c6da8b2ed41d46bcf2ec7b Mon Sep 17 00:00:00 2001 From: seungkwon Date: Sat, 23 Jan 2016 20:51:37 +0900 Subject: [PATCH 365/756] [1.9.x] Fixed typo in django/db/backends/sqlite3/operations.py. Backport of 5925e20c377a0524ef2cc7d11a16f34a67c9398d from master --- django/db/backends/sqlite3/operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/db/backends/sqlite3/operations.py b/django/db/backends/sqlite3/operations.py index 1a4f4baa6c3f..2af08469fb95 100644 --- a/django/db/backends/sqlite3/operations.py +++ b/django/db/backends/sqlite3/operations.py @@ -108,7 +108,7 @@ def _quote_params_for_last_executed_query(self, params): Only for last_executed_query! Don't use this to execute SQL queries! """ # This function is limited both by SQLITE_LIMIT_VARIABLE_NUMBER (the - # number of paramters, default = 999) and SQLITE_MAX_COLUMN (the + # number of parameters, default = 999) and SQLITE_MAX_COLUMN (the # number of return values, default = 2000). Since Python's sqlite3 # module doesn't expose the get_limit() C API, assume the default # limits are in effect and split the work in batches if needed. From 74db1378d5952f48dd623ce887dc22c016b83740 Mon Sep 17 00:00:00 2001 From: Mingun Pak Date: Sun, 24 Jan 2016 02:18:11 +0900 Subject: [PATCH 366/756] [1.9.x] Fixed typos in test comments. Backport of 4c912d184d5ba2ae7355fd8dacb70a0366b8f23b from master --- tests/file_uploads/tests.py | 2 +- tests/gis_tests/geoapp/test_functions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py index 1d0d0ffee13c..ccecfce5c17d 100644 --- a/tests/file_uploads/tests.py +++ b/tests/file_uploads/tests.py @@ -112,7 +112,7 @@ def test_unicode_file_name(self): tdir = sys_tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tdir, True) - # This file contains chinese symbols and an accented char in the name. + # This file contains Chinese symbols and an accented char in the name. with open(os.path.join(tdir, UNICODE_FILENAME), 'w+b') as file1: file1.write(b'b' * (2 ** 10)) file1.seek(0) diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py index 99135643dd07..13b49db1b583 100644 --- a/tests/gis_tests/geoapp/test_functions.py +++ b/tests/gis_tests/geoapp/test_functions.py @@ -94,7 +94,7 @@ def test_asgeojson(self): @skipUnlessDBFeature("has_AsGML_function") def test_asgml(self): - # Should throw a TypeError when tyring to obtain GML from a + # Should throw a TypeError when trying to obtain GML from a # non-geometry field. qs = City.objects.all() with self.assertRaises(TypeError): From 2ef1367426be5f584ae3aa43f2966e0bdc9c1da7 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 23 Jan 2016 17:40:41 -0500 Subject: [PATCH 367/756] [1.9.x] Updated Python trove classifiers in reusable apps tutorial. Backport of 145e9ca301e6bde965baa88a2e96a1ea0f7d523a from master --- docs/intro/reusable-apps.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index 5d1dc2c945f8..b5827ac42a66 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -216,13 +216,13 @@ this. For a small app like polls, this process isn't too difficult. 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', # example license + 'License :: OSI Approved :: BSD License', # example license 'Operating System :: OS Independent', 'Programming Language :: Python', # Replace these appropriately if you are stuck on Python 2. 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], From 5e44348ad79c3a45dc59b9ed92a35d8d190d48e1 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 24 Jan 2016 08:35:47 -0500 Subject: [PATCH 368/756] [1.9.x] Added Django version trove classifier to reusable apps tutorial. Backport of 2d36c7d515312e7a476041c96a29727ed47eb517 from master --- docs/intro/reusable-apps.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index b5827ac42a66..074276baf8b5 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -215,6 +215,7 @@ this. For a small app like polls, this process isn't too difficult. classifiers=[ 'Environment :: Web Environment', 'Framework :: Django', + 'Framework :: Django :: X.Y', # replace "X.Y" as appropriate 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', # example license 'Operating System :: OS Independent', From 41e059de7ccc25307b02de8b7b346b6735673f0a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 24 Jan 2016 15:36:10 -0500 Subject: [PATCH 369/756] [1.9.x] Fixed #26132 -- Discouraged use of TransactionTestCase.fixtures. Backport of b0b45f9a8373f47e96ef6c22d254c984d3b6b3c0 from master --- docs/topics/testing/tools.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 2b432211eff0..fe6eab7838d8 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -1057,9 +1057,9 @@ Fixture loading .. attribute:: TransactionTestCase.fixtures A test case for a database-backed website isn't much use if there isn't any -data in the database. To make it easy to put test data into the database, -Django's custom ``TransactionTestCase`` class provides a way of loading -**fixtures**. +data in the database. Tests are more readable and it's more maintainable to +create objects using the ORM, for example in :meth:`TestCase.setUpTestData`, +however, you can also use fixtures. A fixture is a collection of data that Django knows how to import into a database. For example, if your site has user accounts, you might set up a From 4aec49d015a24ddd893567d2cd2682e1dc1dc107 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 25 Jan 2016 08:33:02 -0500 Subject: [PATCH 370/756] [1.9.x] Refs #26034 -- Added another case fixed by this ticket to release notes. Thanks Shai Berger for the report. Backport of 497b5d6feee5b7947231bd0ae6edf833773b6cce and 5e8685c1b14e94e3f540ac1d68b61e71dcc27517 from master --- docs/releases/1.8.9.txt | 10 ++++++---- docs/releases/1.9.2.txt | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/releases/1.8.9.txt b/docs/releases/1.8.9.txt index 86bb03d9161d..752dc409c436 100644 --- a/docs/releases/1.8.9.txt +++ b/docs/releases/1.8.9.txt @@ -19,10 +19,12 @@ Bugfixes the admin calendar widget for timezones from GMT+0100 to GMT+1200 (:ticket:`24980`). -* Fixed incorrect index handling in migrations on PostgreSQL when adding - ``db_index=True`` or ``unique=True`` to a ``CharField`` or ``TextField`` that - already had the other specified, or when removing one of them from a field - that had both (:ticket:`26034`). +* Fixed a regression in 1.8.8 causing incorrect index handling in migrations on + PostgreSQL when adding ``db_index=True`` or ``unique=True`` to a + ``CharField`` or ``TextField`` that already had the other specified, or when + removing one of them from a field that had both, or when adding + ``unique=True`` to a field already listed in ``unique_together`` + (:ticket:`26034`). * Fixed a crash when using an ``__in`` lookup inside a ``Case`` expression (:ticket:`26071`). diff --git a/docs/releases/1.9.2.txt b/docs/releases/1.9.2.txt index 179cfafbb352..f977b02ac327 100644 --- a/docs/releases/1.9.2.txt +++ b/docs/releases/1.9.2.txt @@ -26,10 +26,12 @@ Bugfixes escaped value to be displayed in the select dropdown of the parent window (:ticket:`25997`). -* Fixed incorrect index handling in migrations on PostgreSQL when adding - ``db_index=True`` or ``unique=True`` to a ``CharField`` or ``TextField`` that - already had the other specified, or when removing one of them from a field - that had both (:ticket:`26034`). +* Fixed a regression in 1.8.8 causing incorrect index handling in migrations on + PostgreSQL when adding ``db_index=True`` or ``unique=True`` to a + ``CharField`` or ``TextField`` that already had the other specified, or when + removing one of them from a field that had both, or when adding + ``unique=True`` to a field already listed in ``unique_together`` + (:ticket:`26034`). * Fixed a regression where defining a relation on an abstract model's field using a string model name without an app_label no longer resolved that From 20b217b0b0b94de374bf5b21285fed20c3880dfb Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 25 Jan 2016 11:57:14 -0500 Subject: [PATCH 371/756] [1.9.x] Fixed Sphinx highlight warnings in docs. Backport of 9c43d8252a926f72be5a279186b42848501819b8 from master --- docs/topics/i18n/translation.txt | 27 ++++++++++----------------- docs/topics/security.txt | 3 ++- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 681f0aa73a95..700319baf83c 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -70,8 +70,6 @@ as a shorter alias, ``_``, to save typing. global ``_()`` function causes interference. Explicitly importing ``ugettext()`` as ``_()`` avoids this problem. -.. highlightlang:: python - In this example, the text ``"Welcome to my site."`` is marked as a translation string:: @@ -1213,6 +1211,8 @@ Additionally, if there are complex rules around pluralization, the catalog view will render a conditional expression. This will evaluate to either a ``true`` (should pluralize) or ``false`` (should **not** pluralize) value. +.. highlight:: python + The ``json_catalog`` view ------------------------- @@ -1247,7 +1247,8 @@ The view is hooked up to your application and configured in the same fashion as The response format is as follows: -.. code-block:: json +.. code-block:: text +.. JSON doesn't allow comments so highlighting as JSON won't work here. { "catalog": { @@ -1270,9 +1271,7 @@ Server-side caching will reduce CPU load. It's easily implemented with the :func:`~django.views.decorators.cache.cache_page` decorator. To trigger cache invalidation when your translations change, provide a version-dependent key prefix, as shown in the example below, or map the view at a version-dependent -URL. - -.. code-block:: python +URL:: from django.views.decorators.cache import cache_page from django.views.i18n import javascript_catalog @@ -1286,9 +1285,7 @@ Client-side caching will save bandwidth and make your site load faster. If you're using ETags (:setting:`USE_ETAGS = True `), you're already covered. Otherwise, you can apply :ref:`conditional decorators `. In the following example, the cache is invalidated -whenever you restart your application server. - -.. code-block:: python +whenever you restart your application server:: from django.utils import timezone from django.views.decorators.http import last_modified @@ -1370,8 +1367,8 @@ After defining these URL patterns, Django will automatically add the language prefix to the URL patterns that were added by the ``i18n_patterns`` function. Example:: - from django.core.urlresolvers import reverse - from django.utils.translation import activate + >>> from django.core.urlresolvers import reverse + >>> from django.utils.translation import activate >>> activate('en') >>> reverse('sitemap-xml') @@ -1428,8 +1425,8 @@ After you've created the translations, the :func:`~django.core.urlresolvers.reverse` function will return the URL in the active language. Example:: - from django.core.urlresolvers import reverse - from django.utils.translation import activate + >>> from django.core.urlresolvers import reverse + >>> from django.utils.translation import activate >>> activate('en') >>> reverse('news:category', kwargs={'slug': 'recent'}) @@ -1727,8 +1724,6 @@ translation utilities with a ``gettext`` package if the command ``xgettext Customizing the ``makemessages`` command ---------------------------------------- -.. highlightlang:: python - If you want to pass additional parameters to ``xgettext``, you need to create a custom :djadmin:`makemessages` command and override its ``xgettext_options`` attribute:: @@ -1767,8 +1762,6 @@ Miscellaneous The ``set_language`` redirect view ---------------------------------- -.. highlightlang:: python - .. currentmodule:: django.views.i18n .. function:: set_language(request) diff --git a/docs/topics/security.txt b/docs/topics/security.txt index 221036e32800..917817ba6fe4 100644 --- a/docs/topics/security.txt +++ b/docs/topics/security.txt @@ -29,7 +29,8 @@ which are particularly dangerous to HTML. While this protects users from most malicious input, it is not entirely foolproof. For example, it will not protect the following: -.. code-block:: html+django +.. code-block:: text +.. highlighting as html+django fails due to intentionally missing quotes. From 3306106fb1ce610e0961ce319a98e33c93795c1f Mon Sep 17 00:00:00 2001 From: Chris Lamb Date: Sun, 24 Jan 2016 10:06:01 +0100 Subject: [PATCH 372/756] [1.9.x] Fixed #25968 -- Changed project/app templates to use a "py-tpl" suffix. Debian packages unconditionally byte-compile .py files on installation and do not silence errors by design. Therefore, we need a way of shipping these invalid .py files without a .py extension but ensuring that when we template them, they end up as .py. We don't special-case .py files so that the all the TemplateCommand command-line options (eg. extra_files and extensions) still work entirely as expected and it may even be useful for other formats too. Backport of abc0777b63057e2ff97eee2ff184356051e14c47 from master --- .../{__init__.py => __init__.py-tpl} | 0 .../app_template/{admin.py => admin.py-tpl} | 0 .../app_template/{apps.py => apps.py-tpl} | 0 .../{__init__.py => __init__.py-tpl} | 0 .../app_template/{models.py => models.py-tpl} | 0 .../app_template/{tests.py => tests.py-tpl} | 0 .../app_template/{views.py => views.py-tpl} | 0 .../{manage.py => manage.py-tpl} | 0 .../{__init__.py => __init__.py-tpl} | 0 .../{settings.py => settings.py-tpl} | 0 .../project_name/{urls.py => urls.py-tpl} | 0 .../project_name/{wsgi.py => wsgi.py-tpl} | 0 django/core/management/templates.py | 12 +++++++++++- docs/ref/django-admin.txt | 9 +++++++++ docs/releases/1.9.2.txt | 19 ++++++++++++++++++- docs/releases/1.9.txt | 5 +++-- docs/topics/install.txt | 3 +-- .../{manage.py => manage.py-tpl} | 0 tests/admin_scripts/tests.py | 2 +- tests/project_template/test_settings.py | 15 +++++++++++++++ 20 files changed, 58 insertions(+), 7 deletions(-) rename django/conf/app_template/{__init__.py => __init__.py-tpl} (100%) rename django/conf/app_template/{admin.py => admin.py-tpl} (100%) rename django/conf/app_template/{apps.py => apps.py-tpl} (100%) rename django/conf/app_template/migrations/{__init__.py => __init__.py-tpl} (100%) rename django/conf/app_template/{models.py => models.py-tpl} (100%) rename django/conf/app_template/{tests.py => tests.py-tpl} (100%) rename django/conf/app_template/{views.py => views.py-tpl} (100%) rename django/conf/project_template/{manage.py => manage.py-tpl} (100%) rename django/conf/project_template/project_name/{__init__.py => __init__.py-tpl} (100%) rename django/conf/project_template/project_name/{settings.py => settings.py-tpl} (100%) rename django/conf/project_template/project_name/{urls.py => urls.py-tpl} (100%) rename django/conf/project_template/project_name/{wsgi.py => wsgi.py-tpl} (100%) rename tests/admin_scripts/custom_templates/project_template/{manage.py => manage.py-tpl} (100%) diff --git a/django/conf/app_template/__init__.py b/django/conf/app_template/__init__.py-tpl similarity index 100% rename from django/conf/app_template/__init__.py rename to django/conf/app_template/__init__.py-tpl diff --git a/django/conf/app_template/admin.py b/django/conf/app_template/admin.py-tpl similarity index 100% rename from django/conf/app_template/admin.py rename to django/conf/app_template/admin.py-tpl diff --git a/django/conf/app_template/apps.py b/django/conf/app_template/apps.py-tpl similarity index 100% rename from django/conf/app_template/apps.py rename to django/conf/app_template/apps.py-tpl diff --git a/django/conf/app_template/migrations/__init__.py b/django/conf/app_template/migrations/__init__.py-tpl similarity index 100% rename from django/conf/app_template/migrations/__init__.py rename to django/conf/app_template/migrations/__init__.py-tpl diff --git a/django/conf/app_template/models.py b/django/conf/app_template/models.py-tpl similarity index 100% rename from django/conf/app_template/models.py rename to django/conf/app_template/models.py-tpl diff --git a/django/conf/app_template/tests.py b/django/conf/app_template/tests.py-tpl similarity index 100% rename from django/conf/app_template/tests.py rename to django/conf/app_template/tests.py-tpl diff --git a/django/conf/app_template/views.py b/django/conf/app_template/views.py-tpl similarity index 100% rename from django/conf/app_template/views.py rename to django/conf/app_template/views.py-tpl diff --git a/django/conf/project_template/manage.py b/django/conf/project_template/manage.py-tpl similarity index 100% rename from django/conf/project_template/manage.py rename to django/conf/project_template/manage.py-tpl diff --git a/django/conf/project_template/project_name/__init__.py b/django/conf/project_template/project_name/__init__.py-tpl similarity index 100% rename from django/conf/project_template/project_name/__init__.py rename to django/conf/project_template/project_name/__init__.py-tpl diff --git a/django/conf/project_template/project_name/settings.py b/django/conf/project_template/project_name/settings.py-tpl similarity index 100% rename from django/conf/project_template/project_name/settings.py rename to django/conf/project_template/project_name/settings.py-tpl diff --git a/django/conf/project_template/project_name/urls.py b/django/conf/project_template/project_name/urls.py-tpl similarity index 100% rename from django/conf/project_template/project_name/urls.py rename to django/conf/project_template/project_name/urls.py-tpl diff --git a/django/conf/project_template/project_name/wsgi.py b/django/conf/project_template/project_name/wsgi.py-tpl similarity index 100% rename from django/conf/project_template/project_name/wsgi.py rename to django/conf/project_template/project_name/wsgi.py-tpl diff --git a/django/core/management/templates.py b/django/core/management/templates.py index 3addb068f7b8..9cb4d952df57 100644 --- a/django/core/management/templates.py +++ b/django/core/management/templates.py @@ -42,6 +42,11 @@ class TemplateCommand(BaseCommand): # Can't perform any active locale changes during this command, because # setting might not be available at all. leave_locale_alone = True + # Rewrite the following suffixes when determining the target filename. + rewrite_template_suffixes = ( + # Allow shipping invalid .py files without byte-compilation. + ('.py-tpl', '.py'), + ) def add_arguments(self, parser): parser.add_argument('name', help='Name of the application or project.') @@ -139,6 +144,11 @@ def handle(self, app_or_project, name, target=None, **options): old_path = path.join(root, filename) new_path = path.join(top_dir, relative_dir, filename.replace(base_name, name)) + for old_suffix, new_suffix in self.rewrite_template_suffixes: + if new_path.endswith(old_suffix): + new_path = new_path[:-len(old_suffix)] + new_suffix + break # Only rewrite once + if path.exists(new_path): raise CommandError("%s already exists, overlaying a " "project or app into an existing " @@ -149,7 +159,7 @@ def handle(self, app_or_project, name, target=None, **options): # accidentally render Django templates files with open(old_path, 'rb') as template_file: content = template_file.read() - if filename.endswith(extensions) or filename in extra_files: + if new_path.endswith(extensions) or filename in extra_files: content = content.decode('utf-8') template = Engine().from_string(content) content = template.render(context) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 06a037484606..06fd1bfd8c8c 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1147,6 +1147,15 @@ with the ``--name`` option. The :class:`template context To work around this problem, you can use the :ttag:`templatetag` templatetag to "escape" the various parts of the template syntax. + In addition, to allow Python template files that contain Django template + language syntax while also preventing packaging systems from trying to + byte-compile invalid ``*.py`` files, template files ending with ``.py-tpl`` + will be renamed to ``.py``. + + .. versionchanged:: 1.9.2 + + Renaming of ``.py-tpl`` to ``.py`` was added. + .. _source: https://github.com/django/django/tree/master/django/conf/app_template/ startproject [destination] diff --git a/docs/releases/1.9.2.txt b/docs/releases/1.9.2.txt index f977b02ac327..e64c62e3daf4 100644 --- a/docs/releases/1.9.2.txt +++ b/docs/releases/1.9.2.txt @@ -4,7 +4,24 @@ Django 1.9.2 release notes *Under development* -Django 1.9.2 fixes several bugs in 1.9.1. +Django 1.9.2 fixes several bugs in 1.9.1 and makes a small backwards +incompatible change that hopefully doesn't affect any users. + +Backwards incompatible change: ``.py-tpl`` files rewritten in project/app templates +=================================================================================== + +The addition of some Django template language syntax to the default app +template in Django 1.9 means those files now have some invalid Python syntax. +This causes difficulties for packaging systems that unconditionally +byte-compile ``*.py`` files. + +To remedy this, a ``.py-tpl`` suffix is now used for the project and app +template files included in Django. The ``.py-tpl`` suffix is replaced with +``.py`` by the ``startproject`` and ``startapp`` commands. For example, a +template with the filename ``manage.py-tpl`` will be created as ``manage.py``. + +Please file a ticket if you have a custom project template containing +``.py-tpl`` files and find this behavior problematic. Bugfixes ======== diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 039171634c61..0742b52e8814 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -1016,7 +1016,7 @@ a Django application with this structure:: ``SyntaxError`` when installing Django setuptools 5.5.x ------------------------------------------------------- -When installing Django 1.9+ with setuptools 5.5.x, you'll see:: +When installing Django 1.9 or 1.9.1 with setuptools 5.5.x, you'll see:: Compiling django/conf/app_template/apps.py ... File "django/conf/app_template/apps.py", line 4 @@ -1033,7 +1033,8 @@ When installing Django 1.9+ with setuptools 5.5.x, you'll see:: It's safe to ignore these errors (Django will still install just fine), but you can avoid them by upgrading setuptools to a more recent version. If you're using pip, you can upgrade pip using ``pip install -U pip`` which will also -upgrade setuptools. +upgrade setuptools. This is resolved in later versions of Django as described +in the :doc:`/releases/1.9.2`. Miscellaneous ------------- diff --git a/docs/topics/install.txt b/docs/topics/install.txt index f0fbc47881ea..b5195bea5747 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -165,8 +165,7 @@ This is the recommended way to install Django. 1. Install pip_. The easiest is to use the `standalone pip installer`_. If your distribution already has ``pip`` installed, you might need to update it if it's outdated. If it's outdated, you'll know because installation won't - work. If you're using an old version of setuptools, you might see some - :ref:`harmless SyntaxErrors ` also. + work. 2. Take a look at virtualenv_ and virtualenvwrapper_. These tools provide isolated Python environments, which are more practical than installing diff --git a/tests/admin_scripts/custom_templates/project_template/manage.py b/tests/admin_scripts/custom_templates/project_template/manage.py-tpl similarity index 100% rename from tests/admin_scripts/custom_templates/project_template/manage.py rename to tests/admin_scripts/custom_templates/project_template/manage.py-tpl diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index 71885a464112..839981767ee9 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -181,7 +181,7 @@ def safe_remove(path): pass conf_dir = os.path.dirname(upath(conf.__file__)) - template_manage_py = os.path.join(conf_dir, 'project_template', 'manage.py') + template_manage_py = os.path.join(conf_dir, 'project_template', 'manage.py-tpl') test_manage_py = os.path.join(self.test_dir, 'manage.py') shutil.copyfile(template_manage_py, test_manage_py) diff --git a/tests/project_template/test_settings.py b/tests/project_template/test_settings.py index ac115f7dc284..25a95717a93a 100644 --- a/tests/project_template/test_settings.py +++ b/tests/project_template/test_settings.py @@ -1,13 +1,28 @@ +import os +import shutil import unittest +from django import conf from django.test import TestCase from django.utils import six +from django.utils._os import upath @unittest.skipIf(six.PY2, 'Python 2 cannot import the project template because ' 'django/conf/project_template doesn\'t have an __init__.py file.') class TestStartProjectSettings(TestCase): + def setUp(self): + # Ensure settings.py exists + project_dir = os.path.join( + os.path.dirname(upath(conf.__file__)), + 'project_template', + 'project_name', + ) + template_settings_py = os.path.join(project_dir, 'settings.py-tpl') + test_settings_py = os.path.join(project_dir, 'settings.py') + shutil.copyfile(template_settings_py, test_settings_py) + self.addCleanup(os.remove, test_settings_py) def test_middleware_classes_headers(self): """ From abdbf00815ae8f49247082528d02a3d0890785a9 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Mon, 25 Jan 2016 14:27:35 -0500 Subject: [PATCH 373/756] [1.9.x] Fixed #26135 -- Adjusted the migration questioner's handling of disabled apps. This was causing an issue when calling the `migrate` command in a test case with the `available_apps` attribute pointing to an application with migrations disabled using the `MIGRATION_MODULES` setting. Thanks to Tim Graham for the review. Refs #24919 Backport of 4dcaa5871b70859952c6f9c437dfe1b5f10509f2 from master --- django/db/migrations/questioner.py | 3 +++ docs/releases/1.9.2.txt | 4 ++++ tests/migrations/test_questioner.py | 15 +++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 tests/migrations/test_questioner.py diff --git a/django/db/migrations/questioner.py b/django/db/migrations/questioner.py index aef80f4a5235..47d4cdf130f5 100644 --- a/django/db/migrations/questioner.py +++ b/django/db/migrations/questioner.py @@ -38,6 +38,9 @@ def ask_initial(self, app_label): except LookupError: # It's a fake app. return self.defaults.get("ask_initial", False) migrations_import_path = MigrationLoader.migrations_module(app_config.label) + if migrations_import_path is None: + # It's an application with migrations disabled. + return self.defaults.get("ask_initial", False) try: migrations_module = importlib.import_module(migrations_import_path) except ImportError: diff --git a/docs/releases/1.9.2.txt b/docs/releases/1.9.2.txt index e64c62e3daf4..1612a938a209 100644 --- a/docs/releases/1.9.2.txt +++ b/docs/releases/1.9.2.txt @@ -71,3 +71,7 @@ Bugfixes * Fixed a crash when using a reverse ``OneToOneField`` in ``ModelAdmin.readonly_fields`` (:ticket:`26060`). + +* Fixed a crash when calling the ``migrate`` command in a test case with the + ``available_apps`` attribute pointing to an application with migrations + disabled using the ``MIGRATION_MODULES`` setting (:ticket:`26135`). diff --git a/tests/migrations/test_questioner.py b/tests/migrations/test_questioner.py new file mode 100644 index 000000000000..d5ba18a68402 --- /dev/null +++ b/tests/migrations/test_questioner.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals + +from django.db.migrations.questioner import MigrationQuestioner +from django.test import SimpleTestCase +from django.test.utils import override_settings + + +class QuestionerTests(SimpleTestCase): + @override_settings( + INSTALLED_APPS=['migrations'], + MIGRATION_MODULES={'migrations': None}, + ) + def test_ask_initial_with_disabled_migrations(self): + questioner = MigrationQuestioner() + self.assertIs(False, questioner.ask_initial('migrations')) From 645fddcd4ee6adaa619b90404b85ccc8d9c316d8 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Mon, 25 Jan 2016 14:29:24 -0500 Subject: [PATCH 374/756] [1.9.x] Refs #24919 -- Used the documented way of disabling migrations per app. Backport of 477274acb46b2f07666e9f84dea2e65ea6b63ad3 from master --- tests/runtests.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/runtests.py b/tests/runtests.py index e231773850da..7740387e29a1 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -153,11 +153,11 @@ def no_available_apps(self): settings.SITE_ID = 1 settings.MIDDLEWARE_CLASSES = ALWAYS_MIDDLEWARE_CLASSES settings.MIGRATION_MODULES = { - # these 'tests.migrations' modules don't actually exist, but this lets - # us skip creating migrations for the test models. - 'auth': 'django.contrib.auth.tests.migrations', - 'contenttypes': 'contenttypes_tests.migrations', - 'sessions': 'sessions_tests.migrations', + # This lets us skip creating migrations for the test models as many of + # them depend on one of the following contrib applications. + 'auth': None, + 'contenttypes': None, + 'sessions': None, } log_config = copy.deepcopy(DEFAULT_LOGGING) # Filter out non-error logging so we don't have to capture it in lots of From 218cc71073012cc4e72c150421fc89da626d2923 Mon Sep 17 00:00:00 2001 From: Preston Timmons Date: Thu, 14 Jan 2016 20:36:05 -0600 Subject: [PATCH 375/756] [1.9.x] Fixed #25848 -- Set template origin on each node. Prior to 55f12f8709, the template origin was available on each node via `self.token.source[0]`. This behavior was removed when debug handling was simplified, but 3rd-party debugging tools still depend on its presence. This updates the Parser to set origin on individual nodes. This enables the source template to be determined even when template extending or including is used. Backport of cfda1fa3f8d95f0f4a369da9021dbd770e5fa44a from master --- django/template/base.py | 8 ++++++-- docs/releases/1.9.2.txt | 7 +++++++ tests/template_tests/tests.py | 9 +++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index 0df744f43e76..30d339a74892 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -224,6 +224,7 @@ def compile_nodelist(self): tokens = lexer.tokenize() parser = Parser( tokens, self.engine.template_libraries, self.engine.template_builtins, + self.origin, ) try: @@ -444,7 +445,7 @@ def tokenize(self): class Parser(object): - def __init__(self, tokens, libraries=None, builtins=None): + def __init__(self, tokens, libraries=None, builtins=None, origin=None): self.tokens = tokens self.tags = {} self.filters = {} @@ -458,6 +459,7 @@ def __init__(self, tokens, libraries=None, builtins=None): self.libraries = libraries for builtin in builtins: self.add_library(builtin) + self.origin = origin def parse(self, parse_until=None): """ @@ -534,8 +536,10 @@ def extend_nodelist(self, nodelist, node, token): ) if isinstance(nodelist, NodeList) and not isinstance(node, TextNode): nodelist.contains_nontext = True - # Set token here since we can't modify the node __init__ method + # Set origin and token here since we can't modify the node __init__() + # method. node.token = token + node.origin = self.origin nodelist.append(node) def error(self, token, e): diff --git a/docs/releases/1.9.2.txt b/docs/releases/1.9.2.txt index 1612a938a209..30527c146a38 100644 --- a/docs/releases/1.9.2.txt +++ b/docs/releases/1.9.2.txt @@ -75,3 +75,10 @@ Bugfixes * Fixed a crash when calling the ``migrate`` command in a test case with the ``available_apps`` attribute pointing to an application with migrations disabled using the ``MIGRATION_MODULES`` setting (:ticket:`26135`). + +* Restored the ability for testing and debugging tools to determine the + template from which a node came from, even during template inheritance or + inclusion. Prior to Django 1.9, debugging tools could access the template + origin from the node via ``Node.token.source[0]``. This was an undocumented, + private API. The origin is now available directly on each node using the + ``Node.origin`` attribute (:ticket:`25848`). diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py index 38d7eaa8bb95..e026c1598e81 100644 --- a/tests/template_tests/tests.py +++ b/tests/template_tests/tests.py @@ -140,3 +140,12 @@ def test_extends_generic_template(self): child = engine.from_string( '{% extends parent %}{% block content %}child{% endblock %}') self.assertEqual(child.render(Context({'parent': parent})), 'child') + + def test_node_origin(self): + """ + #25848 -- Set origin on Node so debugging tools can determine which + template the node came from even if extending or including templates. + """ + template = Engine().from_string('content') + for node in template.nodelist: + self.assertEqual(node.origin, template.origin) From dee5896b55385d2c66d8c1b8386604868f7fc6b4 Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Sat, 23 Jan 2016 18:02:15 -0800 Subject: [PATCH 376/756] [1.9.x] Fixed #26122 -- Fixed copying a LazyObject Shallow copying of `django.utils.functional.LazyObject` or its subclasses has been broken in a couple of different ways in the past, most recently due to 35355a4. Backport of 13023ba86746980aace2341ba32a9419e7567751 from master --- django/utils/functional.py | 20 +++++++ docs/releases/1.8.9.txt | 3 + docs/releases/1.9.2.txt | 3 + tests/utils_tests/test_lazyobject.py | 85 +++++++++++++++++++++++++--- 4 files changed, 104 insertions(+), 7 deletions(-) diff --git a/django/utils/functional.py b/django/utils/functional.py index 3df8c2f5c816..ebfed8ff8a52 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -219,6 +219,8 @@ class LazyObject(object): _wrapped = None def __init__(self): + # Note: if a subclass overrides __init__(), it will likely need to + # override __copy__() and __deepcopy__() as well. self._wrapped = empty __getattr__ = new_method_proxy(getattr) @@ -271,6 +273,15 @@ def __reduce__(self): def __getstate__(self): return {} + def __copy__(self): + if self._wrapped is empty: + # If uninitialized, copy the wrapper. Use type(self), not + # self.__class__, because the latter is proxied. + return type(self)() + else: + # If initialized, return a copy of the wrapped object. + return copy.copy(self._wrapped) + def __deepcopy__(self, memo): if self._wrapped is empty: # We have to use type(self), not self.__class__, because the @@ -348,6 +359,15 @@ def __repr__(self): repr_attr = self._wrapped return '<%s: %r>' % (type(self).__name__, repr_attr) + def __copy__(self): + if self._wrapped is empty: + # If uninitialized, copy the wrapper. Use SimpleLazyObject, not + # self.__class__, because the latter is proxied. + return SimpleLazyObject(self._setupfunc) + else: + # If initialized, return a copy of the wrapped object. + return copy.copy(self._wrapped) + def __deepcopy__(self, memo): if self._wrapped is empty: # We have to use SimpleLazyObject, not self.__class__, because the diff --git a/docs/releases/1.8.9.txt b/docs/releases/1.8.9.txt index 752dc409c436..0009de0bdbac 100644 --- a/docs/releases/1.8.9.txt +++ b/docs/releases/1.8.9.txt @@ -31,3 +31,6 @@ Bugfixes * Fixed a crash when using a reverse ``OneToOneField`` in ``ModelAdmin.readonly_fields`` (:ticket:`26060`). + +* Fixed a regression in Django 1.8.5 that broke copying a ``SimpleLazyObject`` + with ``copy.copy()`` (:ticket:`26122`). diff --git a/docs/releases/1.9.2.txt b/docs/releases/1.9.2.txt index 30527c146a38..6d0288a33795 100644 --- a/docs/releases/1.9.2.txt +++ b/docs/releases/1.9.2.txt @@ -82,3 +82,6 @@ Bugfixes origin from the node via ``Node.token.source[0]``. This was an undocumented, private API. The origin is now available directly on each node using the ``Node.origin`` attribute (:ticket:`25848`). + +* Fixed a regression in Django 1.8.5 that broke copying a ``SimpleLazyObject`` + with ``copy.copy()`` (:ticket:`26122`). diff --git a/tests/utils_tests/test_lazyobject.py b/tests/utils_tests/test_lazyobject.py index e0f043318c97..62d7964fcf3a 100644 --- a/tests/utils_tests/test_lazyobject.py +++ b/tests/utils_tests/test_lazyobject.py @@ -194,28 +194,99 @@ def test_pickle(self): self.assertEqual(unpickled, obj) self.assertEqual(unpickled.foo, obj.foo) - def test_deepcopy(self): - # Check that we *can* do deep copy, and that it returns the right - # objects. + # Test copying lazy objects wrapping both builtin types and user-defined + # classes since a lot of the relevant code does __dict__ manipulation and + # builtin types don't have __dict__. + def test_copy_list(self): + # Copying a list works and returns the correct objects. l = [1, 2, 3] obj = self.lazy_wrap(l) len(l) # forces evaluation - obj2 = copy.deepcopy(obj) + obj2 = copy.copy(obj) + self.assertIsNot(obj, obj2) self.assertIsInstance(obj2, list) self.assertEqual(obj2, [1, 2, 3]) - def test_deepcopy_no_evaluation(self): - # copying doesn't force evaluation + def test_copy_list_no_evaluation(self): + # Copying a list doesn't force evaluation. + l = [1, 2, 3] + + obj = self.lazy_wrap(l) + obj2 = copy.copy(obj) + + self.assertIsNot(obj, obj2) + self.assertIs(obj._wrapped, empty) + self.assertIs(obj2._wrapped, empty) + + def test_copy_class(self): + # Copying a class works and returns the correct objects. + foo = Foo() + + obj = self.lazy_wrap(foo) + str(foo) # forces evaluation + obj2 = copy.copy(obj) + + self.assertIsNot(obj, obj2) + self.assertIsInstance(obj2, Foo) + self.assertEqual(obj2, Foo()) + + def test_copy_class_no_evaluation(self): + # Copying a class doesn't force evaluation. + foo = Foo() + + obj = self.lazy_wrap(foo) + obj2 = copy.copy(obj) + + self.assertIsNot(obj, obj2) + self.assertIs(obj._wrapped, empty) + self.assertIs(obj2._wrapped, empty) + + def test_deepcopy_list(self): + # Deep copying a list works and returns the correct objects. + l = [1, 2, 3] + + obj = self.lazy_wrap(l) + len(l) # forces evaluation + obj2 = copy.deepcopy(obj) + self.assertIsNot(obj, obj2) + self.assertIsInstance(obj2, list) + self.assertEqual(obj2, [1, 2, 3]) + + def test_deepcopy_list_no_evaluation(self): + # Deep copying doesn't force evaluation. l = [1, 2, 3] obj = self.lazy_wrap(l) obj2 = copy.deepcopy(obj) - # Copying shouldn't force evaluation + self.assertIsNot(obj, obj2) + self.assertIs(obj._wrapped, empty) + self.assertIs(obj2._wrapped, empty) + + def test_deepcopy_class(self): + # Deep copying a class works and returns the correct objects. + foo = Foo() + + obj = self.lazy_wrap(foo) + str(foo) # forces evaluation + obj2 = copy.deepcopy(obj) + + self.assertIsNot(obj, obj2) + self.assertIsInstance(obj2, Foo) + self.assertEqual(obj2, Foo()) + + def test_deepcopy_class_no_evaluation(self): + # Deep copying doesn't force evaluation. + foo = Foo() + + obj = self.lazy_wrap(foo) + obj2 = copy.deepcopy(obj) + + self.assertIsNot(obj, obj2) self.assertIs(obj._wrapped, empty) self.assertIs(obj2._wrapped, empty) From 204d31cd6033bfcd7ede1a6eedcc5f351d2cfa11 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 26 Jan 2016 19:06:39 -0500 Subject: [PATCH 377/756] [1.9.x] Refs #26142 -- Documented that Formset's extra=0 doesn't prevent creating objects. Backport of 8e6a08e937272f088902cdbec65a9f2e919783bf from master --- docs/topics/forms/modelforms.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 1106980bac4b..95ef3334fc91 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -931,6 +931,12 @@ extra forms displayed. >>> [x.name for x in formset.get_queryset()] ['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman'] +Also, ``extra=0`` doesn't prevent creation of new model instances as you can +:ref:`add additional forms with JavaScript ` +or just send additional POST data. Formsets `don't yet provide functionality +`_ for an "edit only" view that +prevents creation of new instances. + If the value of ``max_num`` is greater than the number of existing related objects, up to ``extra`` additional blank forms will be added to the formset, so long as the total number of forms does not exceed ``max_num``:: From e47f0c12471b6688b2ace9a0732bf79125ee9086 Mon Sep 17 00:00:00 2001 From: Yoong Kang Lim Date: Wed, 27 Jan 2016 21:32:06 +1100 Subject: [PATCH 378/756] [1.9.x] Fixed #26136 -- Removed URL reversing by dotted path from JavaScript catalog example. Backport of 31817dd2eb69db54eb559716aae42fe55ada5fea from master --- AUTHORS | 1 + docs/topics/i18n/translation.txt | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index f4aaf41af0e6..29dfb2d3c44a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -753,6 +753,7 @@ answer newbie questions, and generally made Django that much better: Yasushi Masuda ye7cakf02@sneakemail.com ymasuda@ethercube.com + Yoong Kang Lim Yusuke Miyazaki Zachary Voase Zach Thompson diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 700319baf83c..f7bf9dba0849 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -988,7 +988,7 @@ You hook it up like this:: } urlpatterns = [ - url(r'^jsi18n/$', javascript_catalog, js_info_dict), + url(r'^jsi18n/$', javascript_catalog, js_info_dict, name='javascript-catalog'), ] Each string in ``packages`` should be in Python dotted-package syntax (the @@ -1008,7 +1008,7 @@ changed by altering the ``domain`` argument. You can make the view dynamic by putting the packages into the URL pattern:: urlpatterns = [ - url(r'^jsi18n/(?P\S+?)/$', javascript_catalog), + url(r'^jsi18n/(?P\S+?)/$', javascript_catalog, name='javascript-catalog'), ] With this, you specify the packages as a list of package names delimited by '+' @@ -1058,7 +1058,7 @@ To use the catalog, just pull in the dynamically generated script like this: .. code-block:: html+django - + This uses reverse URL lookup to find the URL of the JavaScript catalog view. When the catalog is loaded, your JavaScript code can use the following methods: From ca6ab72bb76fefac0188b95e60320466cc8d7610 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 26 Jan 2016 11:06:29 +0100 Subject: [PATCH 379/756] [1.9.x] Fixed #26138 -- Ensured geometry_field's geometry is always serialized Thanks Bernd Schlapsi for the report. Backport of 54236a2c1c from master. --- django/contrib/gis/serializers/geojson.py | 3 +++ docs/releases/1.9.2.txt | 3 +++ tests/gis_tests/geoapp/test_serializers.py | 17 +++++++++++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/django/contrib/gis/serializers/geojson.py b/django/contrib/gis/serializers/geojson.py index f26147a59a22..b26ec2e809c5 100644 --- a/django/contrib/gis/serializers/geojson.py +++ b/django/contrib/gis/serializers/geojson.py @@ -18,6 +18,9 @@ def _init_options(self): super(Serializer, self)._init_options() self.geometry_field = self.json_kwargs.pop('geometry_field', None) self.srid = self.json_kwargs.pop('srid', 4326) + if (self.selected_fields is not None and self.geometry_field is not None + and self.geometry_field not in self.selected_fields): + self.selected_fields = list(self.selected_fields) + [self.geometry_field] def start_serialization(self): self._init_options() diff --git a/docs/releases/1.9.2.txt b/docs/releases/1.9.2.txt index 6d0288a33795..be6eb562954b 100644 --- a/docs/releases/1.9.2.txt +++ b/docs/releases/1.9.2.txt @@ -85,3 +85,6 @@ Bugfixes * Fixed a regression in Django 1.8.5 that broke copying a ``SimpleLazyObject`` with ``copy.copy()`` (:ticket:`26122`). + +* Always included ``geometry_field`` in the GeoJSON serializer output regardless + of the ``fields`` parameter (:ticket:`26138`). diff --git a/tests/gis_tests/geoapp/test_serializers.py b/tests/gis_tests/geoapp/test_serializers.py index 1bf3ec0acf9f..de104b5cdac8 100644 --- a/tests/gis_tests/geoapp/test_serializers.py +++ b/tests/gis_tests/geoapp/test_serializers.py @@ -47,8 +47,21 @@ def test_geometry_field_option(self): geodata = json.loads(geojson) self.assertEqual(geodata['features'][0]['geometry']['type'], 'Point') - geojson = serializers.serialize('geojson', MultiFields.objects.all(), - geometry_field='poly') + geojson = serializers.serialize( + 'geojson', + MultiFields.objects.all(), + geometry_field='poly' + ) + geodata = json.loads(geojson) + self.assertEqual(geodata['features'][0]['geometry']['type'], 'Polygon') + + # geometry_field is considered even if not in fields (#26138). + geojson = serializers.serialize( + 'geojson', + MultiFields.objects.all(), + geometry_field='poly', + fields=('city',) + ) geodata = json.loads(geojson) self.assertEqual(geodata['features'][0]['geometry']['type'], 'Polygon') From ca6830c49f6676193be502f1c1b13d6ed75beabd Mon Sep 17 00:00:00 2001 From: Yoong Kang Lim Date: Thu, 28 Jan 2016 23:06:53 +1100 Subject: [PATCH 380/756] [1.9.x] Added a missing test method in tests/migrations/test_writer.py. Backport of 5453aa66cfdf228f40dc1997d811ca986de405a3 from master --- tests/migrations/test_writer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index 9027c3b6986e..6be8f074f853 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -297,6 +297,8 @@ def test_serialize_settings(self): SettingsReference("someapp.model", "AUTH_USER_MODEL"), ("settings.AUTH_USER_MODEL", {"from django.conf import settings"}) ) + + def test_serialize_iterators(self): self.assertSerializedResultEqual( ((x, x * x) for x in range(3)), ("((0, 0), (1, 1), (2, 4))", set()) From 6103b4990025656a649caad641abb135941f6bf4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 28 Jan 2016 12:47:08 -0500 Subject: [PATCH 381/756] [1.9.x] Fixed #25917 -- Clarified reversibility of RemoveField. Thanks kaifeldhoff for the draft patch. Backport of 55481bcdeef43ef5e345f8ea3bae87f4a8ec7bb8 from master --- docs/ref/migration-operations.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/ref/migration-operations.txt b/docs/ref/migration-operations.txt index de7c6cc9eb8d..e08984e3a8ec 100644 --- a/docs/ref/migration-operations.txt +++ b/docs/ref/migration-operations.txt @@ -163,9 +163,11 @@ RemoveField Removes a field from a model. -Bear in mind that when reversed this is actually adding a field to a model; -if the field is not nullable this may make this operation irreversible (apart -from any data loss, which of course is irreversible). +Bear in mind that when reversed, this is actually adding a field to a model. +The operation is reversible (apart from any data loss, which of course is +irreversible) if the field is nullable or if it has a default value that can be +used to populate the recreated column. If the field is not nullable and does +not have a default value, the operation is irreversible. AlterField ---------- From 61452616baca63fa58efbb5991f65ba362eccbe2 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 28 Jan 2016 07:44:55 -0500 Subject: [PATCH 382/756] [1.9.x] Fixed #26147 -- Relaxed expected values in GIS tests to account for database/library differences. Backport of 5aa53286758fbb1fb864a5efda38718a2ca96759 from master --- tests/gis_tests/distapp/tests.py | 4 ++-- tests/gis_tests/geogapp/tests.py | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/gis_tests/distapp/tests.py b/tests/gis_tests/distapp/tests.py index ef9cc65a2e45..20be91f437d2 100644 --- a/tests/gis_tests/distapp/tests.py +++ b/tests/gis_tests/distapp/tests.py @@ -146,7 +146,7 @@ def test_distance_geodetic(self): """ Test the `distance` GeoQuerySet method on geodetic coordinate systems. """ - tol = 2 if oracle else 5 + tol = 2 if oracle else 4 # Testing geodetic distance calculation with a non-point geometry # (a LineString of Wollongong and Shellharbour coords). @@ -526,7 +526,7 @@ def test_distance_geodetic(self): @skipUnlessDBFeature("has_Distance_function", "supports_distance_geodetic") def test_distance_geodetic_spheroid(self): - tol = 2 if oracle else 5 + tol = 2 if oracle else 4 # Got the reference distances using the raw SQL statements: # SELECT ST_distance_spheroid(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326), diff --git a/tests/gis_tests/geogapp/tests.py b/tests/gis_tests/geogapp/tests.py index 80afbd63c706..b99203afeaad 100644 --- a/tests/gis_tests/geogapp/tests.py +++ b/tests/gis_tests/geogapp/tests.py @@ -98,10 +98,12 @@ def test05_geography_layermapping(self): def test06_geography_area(self): "Testing that Area calculations work on geography columns." # SELECT ST_Area(poly) FROM geogapp_zipcode WHERE code='77002'; - ref_area = 5439100.13586914 if oracle else 5439084.70637573 - tol = 5 z = Zipcode.objects.area().get(code='77002') - self.assertAlmostEqual(z.area.sq_m, ref_area, tol) + # Round to the nearest thousand as possible values (depending on + # the database and geolib) include 5439084, 5439100, 5439101. + rounded_value = z.area.sq_m + rounded_value -= z.area.sq_m % 1000 + self.assertEqual(rounded_value, 5439000) @skipUnlessDBFeature("gis_enabled") @@ -128,7 +130,9 @@ def test_geography_area(self): Testing that Area calculations work on geography columns. """ # SELECT ST_Area(poly) FROM geogapp_zipcode WHERE code='77002'; - ref_area = 5439100.13587 if oracle else 5439084.70637573 - tol = 5 z = Zipcode.objects.annotate(area=Area('poly')).get(code='77002') - self.assertAlmostEqual(z.area.sq_m, ref_area, tol) + # Round to the nearest thousand as possible values (depending on + # the database and geolib) include 5439084, 5439100, 5439101. + rounded_value = z.area.sq_m + rounded_value -= z.area.sq_m % 1000 + self.assertEqual(rounded_value, 5439000) From efd855481581b5758d866a009bbd1074e74869b4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 27 Jan 2016 09:25:49 -0500 Subject: [PATCH 383/756] [1.9.x] Fixed #20415 -- Ensured srid isn't localized in OpenLayers JavaScript. Backport of 19d1cb14519186902d7e27813bf2643fe3f7cfa3 from master --- django/contrib/gis/templates/gis/openlayers.html | 5 +++-- docs/releases/1.8.9.txt | 3 +++ docs/releases/1.9.2.txt | 3 +++ tests/gis_tests/test_geoforms.py | 5 ++++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/django/contrib/gis/templates/gis/openlayers.html b/django/contrib/gis/templates/gis/openlayers.html index 02fa988737e9..81f33ae4c150 100644 --- a/django/contrib/gis/templates/gis/openlayers.html +++ b/django/contrib/gis/templates/gis/openlayers.html @@ -1,4 +1,5 @@ - +.. highlighting as html+django fails due to intentionally missing quotes. + If ``var`` is set to ``'class1 onmouseover=javascript:func()'``, this can result in unauthorized JavaScript execution, depending on how the browser renders imperfect HTML. (Quoting the attribute value would fix this case.) From f7332b3ec552cb0067851fb5a4c0f97c5b2bb1f6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 6 Feb 2016 09:51:06 -0500 Subject: [PATCH 417/756] [1.9.x] Updated instructions for gettext on Windows. Backport of 1d86d4c72b322498819fb8a17a577300152dc6d4 from master --- docs/topics/i18n/translation.txt | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 69fd9c15fdf9..b7255c2d52c7 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1694,25 +1694,8 @@ updating message files, just run :djadmin:`django-admin compilemessages This is only needed for people who either want to extract message IDs or compile message files (``.po``). Translation work itself just involves editing existing files of this type, but if you want to create your own message files, or want to -test or compile a changed message file, you will need the ``gettext`` utilities: - -* Download the following zip files from the GNOME servers - https://download.gnome.org/binaries/win32/dependencies/ - - * ``gettext-runtime-X.zip`` - * ``gettext-tools-X.zip`` - - ``X`` is the version number, we are requiring ``0.15`` or higher. - -* Extract the contents of the ``bin\`` directories in both files to the - same folder on your system (i.e. ``C:\Program Files\gettext-utils``) - -* Update the system PATH: - - * ``Control Panel > System > Advanced > Environment Variables``. - * In the ``System variables`` list, click ``Path``, click ``Edit``. - * Add ``;C:\Program Files\gettext-utils\bin`` at the end of the - ``Variable value`` field. +test or compile a changed message file, download `a precompiled binary +installer `_. You may also use ``gettext`` binaries you have obtained elsewhere, so long as the ``xgettext --version`` command works properly. Do not attempt to use Django From 25496f0f7b953b9bca709ab20bc536137f57402d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Fri, 29 Jan 2016 11:03:47 +0200 Subject: [PATCH 418/756] [1.9.x] Fixed #26153 -- Reallowed Q-objects in ForeignObject.get_extra_descriptor_filter(). Backport of 353aecbf8c1a8cc6f3985149e2895d49e53dfc1c from master --- django/db/models/fields/related.py | 10 ++++++++-- django/db/models/fields/related_descriptors.py | 2 +- docs/releases/1.9.3.txt | 4 ++++ tests/foreign_object/models/article.py | 13 +++++++++++++ tests/foreign_object/tests.py | 13 +++++++++++++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 211c1cdf8628..31434864cd9e 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -8,6 +8,7 @@ from django.core import checks, exceptions from django.db import connection, router from django.db.backends import utils +from django.db.models import Q from django.db.models.deletion import CASCADE, SET_DEFAULT, SET_NULL from django.db.models.query_utils import PathInfo from django.db.models.utils import make_model_tuple @@ -332,8 +333,13 @@ def get_reverse_related_filter(self, obj): rh_field.attname: getattr(obj, lh_field.attname) for lh_field, rh_field in self.related_fields } - base_filter.update(self.get_extra_descriptor_filter(obj) or {}) - return base_filter + descriptor_filter = self.get_extra_descriptor_filter(obj) + base_q = Q(**base_filter) + if isinstance(descriptor_filter, dict): + return base_q & Q(**descriptor_filter) + elif descriptor_filter: + return base_q & descriptor_filter + return base_q @property def swappable_setting(self): diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py index e9caa2680f98..abfa7283aeaa 100644 --- a/django/db/models/fields/related_descriptors.py +++ b/django/db/models/fields/related_descriptors.py @@ -164,7 +164,7 @@ def __get__(self, instance, instance_type=None): rel_obj = None else: qs = self.get_queryset(instance=instance) - qs = qs.filter(**self.field.get_reverse_related_filter(instance)) + qs = qs.filter(self.field.get_reverse_related_filter(instance)) # Assuming the database enforces foreign keys, this won't fail. rel_obj = qs.get() # If this is a one-to-one relation, set the reverse accessor diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt index 994a018fbc66..61a92878c7ce 100644 --- a/docs/releases/1.9.3.txt +++ b/docs/releases/1.9.3.txt @@ -17,3 +17,7 @@ Bugfixes * Added system checks for query name clashes of hidden relationships (:ticket:`26162`). + +* Fixed a regression for cases where + ``ForeignObject.get_extra_descriptor_filter()`` returned a ``Q`` object + (:ticket:`26153`). diff --git a/tests/foreign_object/models/article.py b/tests/foreign_object/models/article.py index f0c2f3fbac9d..6e16784d2c60 100644 --- a/tests/foreign_object/models/article.py +++ b/tests/foreign_object/models/article.py @@ -44,6 +44,11 @@ def contribute_to_class(self, cls, name): setattr(cls, self.name, ArticleTranslationDescriptor(self)) +class ActiveTranslationFieldWithQ(ActiveTranslationField): + def get_extra_descriptor_filter(self, instance): + return models.Q(lang=get_language()) + + @python_2_unicode_compatible class Article(models.Model): active_translation = ActiveTranslationField( @@ -54,6 +59,14 @@ class Article(models.Model): on_delete=models.CASCADE, null=True, ) + active_translation_q = ActiveTranslationFieldWithQ( + 'ArticleTranslation', + from_fields=['id'], + to_fields=['article'], + related_name='+', + on_delete=models.CASCADE, + null=True, + ) pub_date = models.DateField() def __str__(self): diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py index bcdadc6d6d32..c838ae4b6603 100644 --- a/tests/foreign_object/tests.py +++ b/tests/foreign_object/tests.py @@ -467,3 +467,16 @@ class Meta: apps = test_apps self.assertEqual(Child._meta.get_field('parent').check(from_model=Child), []) + + +class TestExtraJoinFilterQ(TestCase): + @translation.override('fi') + def test_extra_join_filter_q(self): + a = Article.objects.create(pub_date=datetime.datetime.today()) + ArticleTranslation.objects.create(article=a, lang='fi', title='title', body='body') + qs = Article.objects.all() + with self.assertNumQueries(2): + self.assertEqual(qs[0].active_translation_q.title, 'title') + qs = qs.select_related('active_translation_q') + with self.assertNumQueries(1): + self.assertEqual(qs[0].active_translation_q.title, 'title') From 1d9ee181fe09a5c3784bbbf802cc522f11ff25ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Thu, 11 Feb 2016 08:39:37 +0200 Subject: [PATCH 419/756] [1.9.x] Fixed #26196 -- Made sure __in lookups use to_field as default. Thanks Simon Charette for the test. Backport of 46ecfb9b3a11a360724e3375ba78c33c46d6a992 from master --- django/db/models/expressions.py | 2 +- django/db/models/fields/__init__.py | 2 +- django/db/models/query.py | 8 +++++++- django/db/models/sql/query.py | 2 +- docs/releases/1.9.3.txt | 3 +++ tests/queries/tests.py | 12 ++++++++++++ 6 files changed, 25 insertions(+), 4 deletions(-) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index f03a2736b365..b5586686a258 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -210,7 +210,7 @@ def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize ]) return c - def _prepare(self): + def _prepare(self, field): """ Hook used by Field.get_prep_lookup() to do custom preparation. """ diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 9449904130d1..df2e3b45de01 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -732,7 +732,7 @@ def get_prep_lookup(self, lookup_type, value): Perform preliminary non-db specific lookup checks and conversions """ if hasattr(value, '_prepare'): - return value._prepare() + return value._prepare(self) if lookup_type in { 'iexact', 'contains', 'icontains', diff --git a/django/db/models/query.py b/django/db/models/query.py index 852a5ba0dd04..cb02085cc577 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1107,12 +1107,18 @@ def _merge_known_related_objects(self, other): for field, objects in other._known_related_objects.items(): self._known_related_objects.setdefault(field, {}).update(objects) - def _prepare(self): + def _prepare(self, field): if self._fields is not None: # values() queryset can only be used as nested queries # if they are set up to select only a single field. if len(self._fields or self.model._meta.concrete_fields) > 1: raise TypeError('Cannot use multi-field values as a filter value.') + else: + # If the query is used as a subquery for a ForeignKey with non-pk + # target field, make sure to select the target field in the subquery. + foreign_fields = getattr(field, 'foreign_related_fields', ()) + if len(foreign_fields) == 1 and not foreign_fields[0].primary_key: + return self.values(foreign_fields[0].name) return self def _as_sql(self, connection): diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 2b446b9ef3a8..29fe885cc656 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -244,7 +244,7 @@ def __deepcopy__(self, memo): memo[id(self)] = result return result - def _prepare(self): + def _prepare(self, field): return self def get_compiler(self, using=None, connection=None): diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt index 61a92878c7ce..e6f200c864c7 100644 --- a/docs/releases/1.9.3.txt +++ b/docs/releases/1.9.3.txt @@ -21,3 +21,6 @@ Bugfixes * Fixed a regression for cases where ``ForeignObject.get_extra_descriptor_filter()`` returned a ``Q`` object (:ticket:`26153`). + +* Fixed regression with an ``__in=qs`` lookup for a ``ForeignKey`` with + ``to_field`` set (:ticket:`26196`). diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 98d68d409269..7c306c9f15b1 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -2463,6 +2463,18 @@ def test_in_query(self): {lunch, dinner}, ) + def test_in_subquery(self): + apple = Food.objects.create(name="apple") + lunch = Eaten.objects.create(food=apple, meal="lunch") + self.assertEqual( + set(Eaten.objects.filter(food__in=Food.objects.filter(name='apple'))), + {lunch} + ) + self.assertEqual( + set(Eaten.objects.filter(food__in=Food.objects.filter(name='apple').values('eaten__meal'))), + set() + ) + def test_reverse_in(self): apple = Food.objects.create(name="apple") pear = Food.objects.create(name="pear") From ec16503f2b2da31e742ecce419c0cb22669964a8 Mon Sep 17 00:00:00 2001 From: Becka R Date: Thu, 11 Feb 2016 11:27:15 -0800 Subject: [PATCH 420/756] [1.9.x] Clarified "database column type" explanation. Backport of cf48962b361b573e580b79839c19b640b7f304a2 from master --- docs/topics/db/models.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index e2cc998e8bce..3192e0a27f01 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -111,7 +111,8 @@ Each field in your model should be an instance of the appropriate :class:`~django.db.models.Field` class. Django uses the field class types to determine a few things: -* The database column type (e.g. ``INTEGER``, ``VARCHAR``). +* The column type, which tells the database what kind of data to store (e.g. + ``INTEGER``, ``VARCHAR``, ``TEXT``). * The default HTML :doc:`widget ` to use when rendering a form field (e.g. ````, ``') + def test_boundfield_slice(self): + class BeatleForm(Form): + name = ChoiceField( + choices=[('john', 'John'), ('paul', 'Paul'), ('george', 'George'), ('ringo', 'Ringo')], + widget=RadioSelect, + ) + + f = BeatleForm() + bf = f['name'] + self.assertEqual( + [str(item) for item in bf[1:]], + [str(bf[1]), str(bf[2]), str(bf[3])], + ) + def test_forms_with_multiple_choice(self): # MultipleChoiceField is a special case, as its data is required to be a list: class SongForm(Form): From bd9dc810ebd56e7c3ffa8f824df2d2e06cf1f32f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 24 Feb 2016 09:57:39 -0500 Subject: [PATCH 445/756] [1.9.x] Removed docs of deprecated SimpleTestCase warnings behavior. Removed in Django 1.7 (4f6be9a0c43050500af598527e1453d27c5c5b85). Backport of 6637cd0ef2fd5f063df82000c18c64c246bb6e1b from master --- docs/topics/testing/tools.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 3968ef516b60..a837c516ded0 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -634,7 +634,6 @@ Normal Python unit test classes extend a base class of A thin subclass of :class:`unittest.TestCase`, it extends it with some basic functionality like: -* Saving and restoring the Python warning machinery state. * Some useful assertions like: * Checking that a callable :meth:`raises a certain exception From 3e1aa3742248c81756b6fff73ad289f107eeac79 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 24 Feb 2016 16:24:33 -0500 Subject: [PATCH 446/756] [1.9.x] Fixed a function signature in docs/topics/auth/default.txt. Backport of 441c537b66233ae57bf0023f02d8262474229e1a from master --- docs/topics/auth/default.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index afa8ad520478..b1b049dd18d9 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -986,7 +986,7 @@ All authentication views This is a list with all the views ``django.contrib.auth`` provides. For implementation details see :ref:`using-the-views`. -.. function:: login(request, template_name=`registration/login.html`, redirect_field_name=, authentication_form, current_app, extra_context) +.. function:: login(request, template_name=`registration/login.html`, redirect_field_name='next', authentication_form=AuthenticationForm, current_app=None, extra_context=None) **URL name:** ``login`` From d7881bfa5ce46def9b82906b63ba7fb69513019b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 24 Feb 2016 10:18:18 -0500 Subject: [PATCH 447/756] [1.9.x] Refs #26270 -- Reorganized TestCase docs. Backport of 7a7e403325427642905a5b3e26931c2b8e92d4b1 from master --- docs/topics/testing/tools.txt | 81 +++++++++++++++-------------------- 1 file changed, 35 insertions(+), 46 deletions(-) diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index a837c516ded0..51510e4cba27 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -626,13 +626,18 @@ Normal Python unit test classes extend a base class of Hierarchy of Django unit testing classes +Converting a normal :class:`unittest.TestCase` to any of the subclasses is +easy: change the base class of your test from ``unittest.TestCase`` to the +subclass. All of the standard Python unit test functionality will be available, +and it will be augmented with some useful additions as described in each +section below. + ``SimpleTestCase`` ------------------ .. class:: SimpleTestCase() -A thin subclass of :class:`unittest.TestCase`, it extends it with some basic -functionality like: +A subclass of :class:`unittest.TestCase` that adds this functionality: * Some useful assertions like: @@ -657,17 +662,8 @@ functionality like: * Using the :attr:`~SimpleTestCase.client` :class:`~django.test.Client`. * Custom test-time :attr:`URL maps `. -If you need any of the other more complex and heavyweight Django-specific -features like: - -* Testing or using the ORM. -* Database :attr:`~TransactionTestCase.fixtures`. -* Test :ref:`skipping based on database backend features `. -* The remaining specialized :meth:`assert* - ` methods. - -then you should use :class:`~django.test.TransactionTestCase` or -:class:`~django.test.TestCase` instead. +If your tests make any database queries, use subclasses +:class:`~django.test.TransactionTestCase` or :class:`~django.test.TestCase`. .. attribute:: SimpleTestCase.allow_database_queries @@ -680,8 +676,6 @@ then you should use :class:`~django.test.TransactionTestCase` or setting the ``allow_database_queries`` class attribute to ``True`` on your test class. -``SimpleTestCase`` inherits from ``unittest.TestCase``. - .. warning:: ``SimpleTestCase`` and its subclasses (e.g. ``TestCase``, ...) rely on @@ -715,12 +709,23 @@ then you should use :class:`~django.test.TransactionTestCase` or .. class:: TransactionTestCase() -Django's ``TestCase`` class (described below) makes use of database transaction -facilities to speed up the process of resetting the database to a known state -at the beginning of each test. A consequence of this, however, is that some -database behaviors cannot be tested within a Django ``TestCase`` class. For -instance, you cannot test that a block of code is executing within a -transaction, as is required when using +``TransactionTestCase`` inherits from :class:`~django.test.SimpleTestCase` to +add some database-specific features: + +* Resetting the database to a known state at the beginning of each test to + ease testing and using the ORM. +* Database :attr:`~TransactionTestCase.fixtures`. +* Test :ref:`skipping based on database backend features `. +* The remaining specialized :meth:`assert* + ` methods. + +Django's :class:`TestCase` class is a more commonly used subclass of +``TransactionTestCase`` that makes use of database transaction facilities +to speed up the process of resetting the database to a known state at the +beginning of each test. A consequence of this, however, is that some database +behaviors cannot be tested within a Django ``TestCase`` class. For instance, +you cannot test that a block of code is executing within a transaction, as is +required when using :meth:`~django.db.models.query.QuerySet.select_for_update()`. In those cases, you should use ``TransactionTestCase``. @@ -757,31 +762,23 @@ to test the effects of commit and rollback: this) you can set ``serialized_rollback = True`` inside the ``TestCase`` body. -``TransactionTestCase`` inherits from :class:`~django.test.SimpleTestCase`. - ``TestCase`` ------------ .. class:: TestCase() -This class provides some additional capabilities that can be useful for testing -websites. - -Converting a normal :class:`unittest.TestCase` to a Django :class:`TestCase` is -easy: Just change the base class of your test from ``'unittest.TestCase'`` to -``'django.test.TestCase'``. All of the standard Python unit test functionality -will continue to be available, but it will be augmented with some useful -additions, including: +This is the most common class to use for writing tests in Django. It inherits +from :class:`TransactionTestCase` (and by extension :class:`SimpleTestCase`). +If your Django application doesn't use a database, use :class:`SimpleTestCase`. -* Automatic loading of fixtures. +The class: -* Wraps the tests within two nested ``atomic`` blocks: one for the whole class - and one for each test. +* Wraps the tests within two nested :func:`~django.db.transaction.atomic` + blocks: one for the whole class and one for each test. Therefore, if you want + to test some specific database transaction behavior, use + :class:`TransactionTestCase`. -* Creates a TestClient instance. - -* Django-specific assertions for testing for things like redirection and form - errors. +It also provides an additional method: .. classmethod:: TestCase.setUpTestData() @@ -820,14 +817,6 @@ additions, including: modify them, you could reload them in the ``setUp()`` method with :meth:`~django.db.models.Model.refresh_from_db`, for example. -.. warning:: - - If you want to test some specific database transaction behavior, you should - use ``TransactionTestCase``, as ``TestCase`` wraps test execution within an - :func:`~django.db.transaction.atomic()` block. - -``TestCase`` inherits from :class:`~django.test.TransactionTestCase`. - .. _live-test-server: ``LiveServerTestCase`` From 7f02c1eded6b44d155672464fa156032d91c39de Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 25 Feb 2016 08:55:10 -0500 Subject: [PATCH 448/756] [1.9.x] Fixed #26278 -- Clarified apps.ready docs. Backport of 1f8cfcf3b41bac0ec862f171e2efb51b35324045 from master --- docs/ref/applications.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/applications.txt b/docs/ref/applications.txt index 3271f494a0c1..0a944de4e8ea 100644 --- a/docs/ref/applications.txt +++ b/docs/ref/applications.txt @@ -304,8 +304,8 @@ Application registry .. attribute:: apps.ready - Boolean attribute that is set to ``True`` when the registry is fully - populated. + Boolean attribute that is set to ``True`` after the registry is fully + populated and all :meth:`AppConfig.ready` methods are called. .. method:: apps.get_app_configs() From a23b387e5a1f22aad2fe0bb87c7e2bc3d065f5e7 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 25 Feb 2016 14:22:41 -0500 Subject: [PATCH 449/756] [1.9.x] Corrected a run on sentence in doc/topics/db/models.txt. Backport of 22d2a5b00ac99e638d95cbfe1cc41ef217fa50d4 from master --- docs/topics/db/models.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 3192e0a27f01..05348b91f3fe 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -170,10 +170,10 @@ ones: ) The first element in each tuple is the value that will be stored in the - database, the second element will be displayed by the default form widget - or in a ModelChoiceField. Given an instance of a model object, the - display value for a choices field can be accessed using the - ``get_FOO_display`` method. For example:: + database. The second element will be displayed by the default form widget + or in a :class:`~django.forms.ModelChoiceField`. Given a model instance, + the display value for a choices field can be accessed using the + ``get_FOO_display()`` method. For example:: from django.db import models From 911a77fcca49a92d0750b7f7d573ea2dcf77a1a9 Mon Sep 17 00:00:00 2001 From: Sjoerd Job Postmus Date: Wed, 17 Feb 2016 15:18:24 +0100 Subject: [PATCH 450/756] [1.9.x] Fixed #26231 -- Used .get_username in admin login template. Backport of bbe136e1a2f9cbf3fd10d49fbe8558a5b394752c from master --- django/contrib/admin/templates/admin/login.html | 2 +- docs/releases/1.9.3.txt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html index 7a0d650036ad..adb58d683c55 100644 --- a/django/contrib/admin/templates/admin/login.html +++ b/django/contrib/admin/templates/admin/login.html @@ -34,7 +34,7 @@ {% if user.is_authenticated %}

    -{% blocktrans with username=request.user.username trimmed %} +{% blocktrans with username=request.user.get_username trimmed %} You are authenticated as {{ username }}, but are not authorized to access this page. Would you like to login to a different account? {% endblocktrans %} diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt index cd7d67486420..ef9568102424 100644 --- a/docs/releases/1.9.3.txt +++ b/docs/releases/1.9.3.txt @@ -42,3 +42,7 @@ Bugfixes regressed in Django 1.9 (:ticket:`26253`). * Fixed ``BoundField`` to reallow slices of subwidgets (:ticket:`26267`). + +* Changed the admin's "permission denied" message in the login template to use + ``get_username`` instead of ``username`` to support custom user models + (:ticket:`26231`). From cd46947ddb6719c819e75464d6aa0a10a6c10fad Mon Sep 17 00:00:00 2001 From: Ivan Tsouvarev Date: Fri, 26 Feb 2016 09:49:02 +0300 Subject: [PATCH 451/756] [1.9.x] Fixed #26280 -- Fixed cached template loader crash when loading nonexistent template. Backport of 8890c533e0b53cb0021bd5faf15668430cd3075a from master --- django/template/loaders/cached.py | 2 +- docs/releases/1.9.3.txt | 3 +++ tests/template_tests/test_loaders.py | 12 ++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/django/template/loaders/cached.py b/django/template/loaders/cached.py index c13346b9612a..5bf5c10586c3 100644 --- a/django/template/loaders/cached.py +++ b/django/template/loaders/cached.py @@ -131,7 +131,7 @@ def load_template(self, template_name, template_dirs=None): template_tuple = self.template_cache.get(key) # A cached previous failure: if template_tuple is TemplateDoesNotExist: - raise TemplateDoesNotExist + raise TemplateDoesNotExist(template_name) elif template_tuple is None: template, origin = self.find_template(template_name, template_dirs) if not hasattr(template, 'render'): diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt index ef9568102424..625899daa025 100644 --- a/docs/releases/1.9.3.txt +++ b/docs/releases/1.9.3.txt @@ -46,3 +46,6 @@ Bugfixes * Changed the admin's "permission denied" message in the login template to use ``get_username`` instead of ``username`` to support custom user models (:ticket:`26231`). + +* Fixed a crash when passing a nonexistent template name to the cached template + loader's ``load_template()`` method (:ticket:`26280`). diff --git a/tests/template_tests/test_loaders.py b/tests/template_tests/test_loaders.py index 36147935649e..11f20c6debff 100644 --- a/tests/template_tests/test_loaders.py +++ b/tests/template_tests/test_loaders.py @@ -87,6 +87,18 @@ def test_load_template_missing(self): "Cached loader failed to cache the TemplateDoesNotExist exception", ) + @ignore_warnings(category=RemovedInDjango20Warning) + def test_load_nonexistent_cached_template(self): + loader = self.engine.template_loaders[0] + template_name = 'nonexistent.html' + + # fill the template cache + with self.assertRaises(TemplateDoesNotExist): + loader.find_template(template_name) + + with self.assertRaisesMessage(TemplateDoesNotExist, template_name): + loader.get_template(template_name) + def test_templatedir_caching(self): """ #13573 -- Template directories should be part of the cache key. From ba6f83ec95dbd265ae3d493c8d6375f36052d12e Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Fri, 18 Dec 2015 14:49:23 -0500 Subject: [PATCH 452/756] [1.9.x] Fixed #26286 -- Prevented content type managers from sharing their cache. This should prevent managers methods from returning content type instances registered to foreign apps now that these managers are also attached to models created during migration phases. Thanks Tim for the review. Refs #23822. Backport of 3938b3ccaa85f1c366909a4839696007726a09da from master --- django/contrib/contenttypes/models.py | 20 +++++++++++--------- docs/releases/1.8.10.txt | 3 +++ docs/releases/1.9.3.txt | 3 +++ tests/contenttypes_tests/test_models.py | 14 +++++++++++++- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py index 27a5388a702e..8d459f65a2ac 100644 --- a/django/contrib/contenttypes/models.py +++ b/django/contrib/contenttypes/models.py @@ -13,13 +13,15 @@ class ContentTypeManager(models.Manager): use_in_migrations = True - # Cache to avoid re-looking up ContentType objects all over the place. - # This cache is shared by all the get_for_* methods. - _cache = {} + def __init__(self, *args, **kwargs): + super(ContentTypeManager, self).__init__(*args, **kwargs) + # Cache shared by all the get_for_* methods to speed up + # ContentType retrieval. + self._cache = {} def get_by_natural_key(self, app_label, model): try: - ct = self.__class__._cache[self.db][(app_label, model)] + ct = self._cache[self.db][(app_label, model)] except KeyError: ct = self.get(app_label=app_label, model=model) self._add_to_cache(self.db, ct) @@ -34,7 +36,7 @@ def _get_opts(self, model, for_concrete_model): def _get_from_cache(self, opts): key = (opts.app_label, opts.model_name) - return self.__class__._cache[self.db][key] + return self._cache[self.db][key] def create(self, **kwargs): if 'name' in kwargs: @@ -129,7 +131,7 @@ def get_for_id(self, id): (though ContentTypes are obviously not created on-the-fly by get_by_id). """ try: - ct = self.__class__._cache[self.db][id] + ct = self._cache[self.db][id] except KeyError: # This could raise a DoesNotExist; that's correct behavior and will # make sure that only correct ctypes get stored in the cache dict. @@ -144,15 +146,15 @@ def clear_cache(self): django.contrib.contenttypes.management.update_contenttypes for where this gets called). """ - self.__class__._cache.clear() + self._cache.clear() def _add_to_cache(self, using, ct): """Insert a ContentType into the cache.""" # Note it's possible for ContentType objects to be stale; model_class() will return None. # Hence, there is no reliance on model._meta.app_label here, just using the model fields instead. key = (ct.app_label, ct.model) - self.__class__._cache.setdefault(using, {})[key] = ct - self.__class__._cache.setdefault(using, {})[ct.id] = ct + self._cache.setdefault(using, {})[key] = ct + self._cache.setdefault(using, {})[ct.id] = ct @python_2_unicode_compatible diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt index c9fbd0470d70..b1319ff80204 100644 --- a/docs/releases/1.8.10.txt +++ b/docs/releases/1.8.10.txt @@ -26,3 +26,6 @@ Bugfixes ``URLValidator`` to fix a regression in Django 1.8 (:ticket:`26204`). * Fixed ``BoundField`` to reallow slices of subwidgets (:ticket:`26267`). + +* Prevented ``ContentTypeManager`` instances from sharing their cache + (:ticket:`26286`). diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt index 625899daa025..e0447d2afeff 100644 --- a/docs/releases/1.9.3.txt +++ b/docs/releases/1.9.3.txt @@ -49,3 +49,6 @@ Bugfixes * Fixed a crash when passing a nonexistent template name to the cached template loader's ``load_template()`` method (:ticket:`26280`). + +* Prevented ``ContentTypeManager`` instances from sharing their cache + (:ticket:`26286`). diff --git a/tests/contenttypes_tests/test_models.py b/tests/contenttypes_tests/test_models.py index eb436b6a64a1..75cea0a4d864 100644 --- a/tests/contenttypes_tests/test_models.py +++ b/tests/contenttypes_tests/test_models.py @@ -2,7 +2,7 @@ import warnings -from django.contrib.contenttypes.models import ContentType +from django.contrib.contenttypes.models import ContentType, ContentTypeManager from django.contrib.contenttypes.views import shortcut from django.contrib.sites.shortcuts import get_current_site from django.db.utils import IntegrityError, OperationalError, ProgrammingError @@ -170,6 +170,18 @@ def test_get_for_concrete_models(self): DeferredProxyModel: proxy_model_ct, }) + def test_cache_not_shared_between_managers(self): + with self.assertNumQueries(1): + ContentType.objects.get_for_model(ContentType) + with self.assertNumQueries(0): + ContentType.objects.get_for_model(ContentType) + other_manager = ContentTypeManager() + other_manager.model = ContentType + with self.assertNumQueries(1): + other_manager.get_for_model(ContentType) + with self.assertNumQueries(0): + other_manager.get_for_model(ContentType) + @override_settings(ALLOWED_HOSTS=['example.com']) def test_shortcut_view(self): """ From e96cdc6f99923621f543751d4cc0fddef1514422 Mon Sep 17 00:00:00 2001 From: inondle Date: Fri, 26 Feb 2016 13:44:22 -0800 Subject: [PATCH 453/756] [1.9.x] Fixed #26275 -- Noted difference between o and Y date format chars. Backport of 5fb9756eba01237cc0e550da689b9b79c51c96ed from master --- docs/ref/templates/builtins.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index ad8fadfb7d04..e67dc637c39f 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1349,8 +1349,9 @@ n Month without leading zeros. ``'1'`` to ``'12'`` N Month abbreviation in Associated Press ``'Jan.'``, ``'Feb.'``, ``'March'``, ``'May'`` style. Proprietary extension. o ISO-8601 week-numbering year, ``'1999'`` - corresponding to - the ISO-8601 week number (W) + corresponding to the ISO-8601 week + number (W) which uses leap weeks. See Y + for the more common year format. O Difference to Greenwich time in hours. ``'+0200'`` P Time, in 12-hour hours, minutes and ``'1 a.m.'``, ``'1:30 p.m.'``, ``'midnight'``, ``'noon'``, ``'12:30 p.m.'`` 'a.m.'/'p.m.', with minutes left off From e9234569f69e0afcc97b3b1ff5293bad1678a076 Mon Sep 17 00:00:00 2001 From: Shai Berger Date: Sat, 27 Feb 2016 17:55:10 +0200 Subject: [PATCH 454/756] [1.9.x] Fixed docs: release-process, Supported Versions section, concrete example Security & data loss fixes are applied to the two last feature releases, not just one. Thanks Loic Bistuer and Tim Graham for review Backport of 3dd4e92+72e5778 from master --- docs/internals/release-process.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/internals/release-process.txt b/docs/internals/release-process.txt index e56ac9ab2af5..8c18de97a1b7 100644 --- a/docs/internals/release-process.txt +++ b/docs/internals/release-process.txt @@ -161,8 +161,9 @@ Django 5.1 and 5.2. At this point in time: released as 5.1.1, 5.1.2, etc. * Security fixes and bug fixes for data loss issues will be applied to - ``master`` and to the ``stable/5.1.x``, and ``stable/4.2.x`` (LTS) branches. - They will trigger the release of ``5.1.1``, ``4.2.1``, etc. + ``master`` and to the ``stable/5.1.x``, ``stable/5.0.x``, and + ``stable/4.2.x`` (LTS) branches. They will trigger the release of ``5.1.1``, + ``5.0.5``, ``4.2.8``, etc. * Documentation fixes will be applied to master, and, if easily backported, to the latest stable branch, ``5.1.x``. From 48cf7516409c668bf11e24e6b7cd8eaea13ae3b8 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Mon, 22 Feb 2016 16:05:47 -0500 Subject: [PATCH 455/756] [1.9.x] Fixed #26186 -- Documented how app relative relationships of abstract models behave. This partially reverts commit bc7d201bdbaeac14a49f51a9ef292d6312b4c45e. Thanks Tim for the review. Refs #25858. Backport of 0223e213dd690b6b6e0669f836a20efb10998c83 from master --- django/db/models/fields/related.py | 21 +++------- docs/ref/models/fields.txt | 29 +++++++++++++ docs/releases/1.9.3.txt | 4 ++ tests/model_fields/tests.py | 66 +++++++++++++++++------------- 4 files changed, 77 insertions(+), 43 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 31434864cd9e..5d9b881c16b6 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -40,7 +40,7 @@ RECURSIVE_RELATIONSHIP_CONSTANT = 'self' -def resolve_relation(scope_model, relation, resolve_recursive_relationship=True): +def resolve_relation(scope_model, relation): """ Transform relation into a model or fully-qualified model string of the form "app_label.ModelName", relative to scope_model. @@ -55,11 +55,12 @@ def resolve_relation(scope_model, relation, resolve_recursive_relationship=True) """ # Check for recursive relations if relation == RECURSIVE_RELATIONSHIP_CONSTANT: - if resolve_recursive_relationship: - relation = scope_model + relation = scope_model + # Look for an "app.Model" relation - elif isinstance(relation, six.string_types) and '.' not in relation: - relation = "%s.%s" % (scope_model._meta.app_label, relation) + if isinstance(relation, six.string_types): + if "." not in relation: + relation = "%s.%s" % (scope_model._meta.app_label, relation) return relation @@ -303,11 +304,6 @@ def resolve_related_class(model, related, field): field.remote_field.model = related field.do_related_class(related, model) lazy_related_operation(resolve_related_class, cls, self.remote_field.model, field=self) - else: - # Bind a lazy reference to the app in which the model is defined. - self.remote_field.model = resolve_relation( - cls, self.remote_field.model, resolve_recursive_relationship=False - ) def get_forward_related_filter(self, obj): """ @@ -1584,11 +1580,6 @@ def resolve_through_model(_, model, field): lazy_related_operation(resolve_through_model, cls, self.remote_field.through, field=self) elif not cls._meta.swapped: self.remote_field.through = create_many_to_many_intermediary_model(self, cls) - else: - # Bind a lazy reference to the app in which the model is defined. - self.remote_field.through = resolve_relation( - cls, self.remote_field.through, resolve_recursive_relationship=False - ) # Add the descriptor for the m2m relation. setattr(cls, self.name, ManyToManyDescriptor(self.remote_field, reverse=False)) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index d8d047e3c987..20c50a0b12b0 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1155,6 +1155,35 @@ you can use the name of the model, rather than the model object itself:: # ... pass +Relationships defined this way on :ref:`abstract models +` are resolved when the model is subclassed as a +concrete model and are not relative to the abstract model's ``app_label``: + +.. snippet:: + :filename: products/models.py + + from django.db import models + + class AbstractCar(models.Model): + manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE) + + class Meta: + abstract = True + +.. snippet:: + :filename: production/models.py + + from django.db import models + from products.models import AbstractCar + + class Manufacturer(models.Model): + pass + + class Car(AbstractCar): + pass + + # Car.manufacturer will point to `production.Manufacturer` here. + To refer to models defined in another application, you can explicitly specify a model with the full application label. For example, if the ``Manufacturer`` model above is defined in another application called ``production``, you'd diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt index e0447d2afeff..e94d555ec065 100644 --- a/docs/releases/1.9.3.txt +++ b/docs/releases/1.9.3.txt @@ -52,3 +52,7 @@ Bugfixes * Prevented ``ContentTypeManager`` instances from sharing their cache (:ticket:`26286`). + +* Reverted a change in Django 1.9.2 (:ticket:`25858`) that prevented relative + lazy relationships defined on abstract models to be resolved according to + their concrete model's ``app_label`` (:ticket:`26186`). diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index 5aafdb284491..cc6639b03234 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -251,24 +251,29 @@ class Meta: def test_abstract_model_app_relative_foreign_key(self): test_apps = Apps(['model_fields', 'model_fields.tests']) - class Refered(models.Model): - class Meta: - apps = test_apps - app_label = 'model_fields' - class AbstractReferent(models.Model): reference = models.ForeignKey('Refered', on_delete=models.CASCADE) class Meta: + apps = test_apps app_label = 'model_fields' abstract = True - class ConcreteReferent(AbstractReferent): - class Meta: - apps = test_apps - app_label = 'tests' + def assert_app_model_resolved(label): + class Refered(models.Model): + class Meta: + apps = test_apps + app_label = label + + class ConcreteReferent(AbstractReferent): + class Meta: + apps = test_apps + app_label = label + + self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) - self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) + assert_app_model_resolved('model_fields') + assert_app_model_resolved('tests') class ManyToManyFieldTests(test.SimpleTestCase): @@ -295,33 +300,38 @@ class Meta: def test_abstract_model_app_relative_foreign_key(self): test_apps = Apps(['model_fields', 'model_fields.tests']) - class Refered(models.Model): + class AbstractReferent(models.Model): + reference = models.ManyToManyField('Refered', through='Through') + class Meta: apps = test_apps app_label = 'model_fields' + abstract = True - class Through(models.Model): - refered = models.ForeignKey('Refered', on_delete=models.CASCADE) - referent = models.ForeignKey('tests.ConcreteReferent', on_delete=models.CASCADE) + def assert_app_model_resolved(label): + class Refered(models.Model): + class Meta: + apps = test_apps + app_label = label - class Meta: - apps = test_apps - app_label = 'model_fields' + class Through(models.Model): + refered = models.ForeignKey('Refered', on_delete=models.CASCADE) + referent = models.ForeignKey('ConcreteReferent', on_delete=models.CASCADE) - class AbstractReferent(models.Model): - reference = models.ManyToManyField('Refered', through='Through') + class Meta: + apps = test_apps + app_label = label - class Meta: - app_label = 'model_fields' - abstract = True + class ConcreteReferent(AbstractReferent): + class Meta: + apps = test_apps + app_label = label - class ConcreteReferent(AbstractReferent): - class Meta: - apps = test_apps - app_label = 'tests' + self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) + self.assertEqual(ConcreteReferent.reference.through, Through) - self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) - self.assertEqual(ConcreteReferent.reference.through, Through) + assert_app_model_resolved('model_fields') + assert_app_model_resolved('tests') class TextFieldTests(test.TestCase): From 46c6ac1ffc619a5f0c2acf43fd48f85e1971b8a8 Mon Sep 17 00:00:00 2001 From: Taranjeet Date: Tue, 1 Mar 2016 15:07:22 +0530 Subject: [PATCH 456/756] [1.9.x] Fixed typos in docs/ref/models/meta.txt. Backport of 11a8207d4294b46561ce34b37803f191014509af from master --- docs/ref/models/meta.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/models/meta.txt b/docs/ref/models/meta.txt index b0868d341c60..9e4d9435a8ca 100644 --- a/docs/ref/models/meta.txt +++ b/docs/ref/models/meta.txt @@ -179,11 +179,11 @@ can be made to convert your code to the new API: then check if: - ``f.auto_created == False``, because the new ``get_field()`` - API will find "reverse" relations), and: + API will find "reverse" relations, and: - ``f.is_relation and f.related_model is None``, because the new ``get_field()`` API will find - :class:`~django.contrib.contenttypes.fields.GenericForeignKey` relations; + :class:`~django.contrib.contenttypes.fields.GenericForeignKey` relations. * ``MyModel._meta.get_field_by_name(name)`` returns a tuple of these four values with the following replacements: From 73d8e646d743aad79d6067ae3d8facf83b1e76a4 Mon Sep 17 00:00:00 2001 From: acemaster Date: Wed, 3 Feb 2016 23:59:45 +0530 Subject: [PATCH 457/756] [1.9.x] Fixed #26165 -- Added some FAQs about CSRF protection. Thanks Florian Apolloner and Shai Berger for review. Backport of a1b1688c7d6c1a6d307bd22669bd20f08e948f8d from master --- docs/ref/csrf.txt | 57 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/docs/ref/csrf.txt b/docs/ref/csrf.txt index 53ed8a547220..ca94b2155207 100644 --- a/docs/ref/csrf.txt +++ b/docs/ref/csrf.txt @@ -240,12 +240,16 @@ The CSRF protection is based on the following things: This check is done by ``CsrfViewMiddleware``. 4. In addition, for HTTPS requests, strict referer checking is done by - ``CsrfViewMiddleware``. This is necessary to address a Man-In-The-Middle - attack that is possible under HTTPS when using a session independent nonce, - due to the fact that HTTP 'Set-Cookie' headers are (unfortunately) accepted - by clients that are talking to a site under HTTPS. (Referer checking is not - done for HTTP requests because the presence of the Referer header is not - reliable enough under HTTP.) + ``CsrfViewMiddleware``. This means that even if a subdomain can set or + modify cookies on your domain, it can't force a user to post to your + application since that request won't come from your own exact domain. + + This also addresses a man-in-the-middle attack that's possible under HTTPS + when using a session independent nonce, due to the fact that HTTP + ``Set-Cookie`` headers are (unfortunately) accepted by clients even when + they are talking to a site under HTTPS. (Referer checking is not done for + HTTP requests because the presence of the ``Referer`` header isn't reliable + enough under HTTP.) If the :setting:`CSRF_COOKIE_DOMAIN` setting is set, the referer is compared against it. This setting supports subdomains. For example, @@ -263,7 +267,15 @@ It deliberately ignores GET requests (and other requests that are defined as 'safe' by :rfc:`2616`). These requests ought never to have any potentially dangerous side effects , and so a CSRF attack with a GET request ought to be harmless. :rfc:`2616` defines POST, PUT and DELETE as 'unsafe', and all other -methods are assumed to be unsafe, for maximum protection. +methods are also assumed to be unsafe, for maximum protection. + +The CSRF protection cannot protect against man-in-the-middle attacks, so use +:ref:`HTTPS ` with +:ref:`http-strict-transport-security`. It also assumes :ref:`validation of +the HOST header ` and that there aren't any +:ref:`cross-site scripting vulnerabilities ` on your site +(because XSS vulnerabilities already let an attacker do anything a CSRF +vulnerability allows and much worse). .. versionchanged:: 1.9 @@ -462,3 +474,34 @@ A number of settings can be used to control Django's CSRF behavior: * :setting:`CSRF_FAILURE_VIEW` * :setting:`CSRF_HEADER_NAME` * :setting:`CSRF_TRUSTED_ORIGINS` + +Frequently Asked Questions +========================== + +Is posting an arbitrary CSRF token pair (cookie and POST data) a vulnerability? +------------------------------------------------------------------------------- + +No, this is by design. Without a man-in-the-middle attack, there is no way for +an attacker to send a CSRF token cookie to a victim's browser, so a successful +attack would need to obtain the victim's browser's cookie via XSS or similar, +in which case an attacker usually doesn't need CSRF attacks. + +Some security audit tools flag this as a problem but as mentioned before, an +attacker cannot steal a user's browser's CSRF cookie. "Stealing" or modifying +*your own* token using Firebug, Chrome dev tools, etc. isn't a vulnerability. + +Is the fact that Django's CSRF protection isn't linked to a session a problem? +------------------------------------------------------------------------------ + +No, this is by design. Not linking CSRF protection to a session allows using +the protection on sites such as a `pastebin` that allow submissions from +anonymous users which don't have a session. + +Why not use a new token for each request? +----------------------------------------- + +Generating a new token for each request is problematic from a UI perspective +because it invalidates all previous forms. Most users would be very unhappy to +find that opening a new tab on your site has invalidated the form they had +just spent time filling out in another tab or that a form they accessed via +the back button could not be filled out. From 468c6a3b6348fc3701ac569eeb119ddd146cf7c6 Mon Sep 17 00:00:00 2001 From: Michal Petrucha Date: Sat, 20 Feb 2016 18:04:50 +0100 Subject: [PATCH 458/756] [1.9.x] Fixed #26217 -- Added a warning about format strings to WeekArchiveView docs. Backport of fe8ea3ba3ba709b3d6c39da046f0883a296e6441 from master --- .../class-based-views/generic-date-based.txt | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt index d2d7b153fca4..8e93182baefc 100644 --- a/docs/ref/class-based-views/generic-date-based.txt +++ b/docs/ref/class-based-views/generic-date-based.txt @@ -333,6 +333,15 @@ views for displaying drilldown pages for date-based data. * Uses a default ``template_name_suffix`` of ``_archive_week``. + * The ``week_format`` attribute is a :func:`~time.strptime` format string + used to parse the week number. The following values are supported: + + * ``'%U'``: Based on the United States week system where the week + begins on Sunday. This is the default value. + + * ``'%V'``: Similar to ``'%U'``, except it assumes that the week + begins on Monday. This is not the same as the ISO 8601 week number. + **Example myapp/views.py**:: from django.views.generic.dates import WeekArchiveView @@ -372,24 +381,23 @@ views for displaying drilldown pages for date-based data.

    {% if previous_week %} - Previous Week: {{ previous_week|date:"F Y" }} + Previous Week: {{ previous_week|date:"W" }} of year {{ previous_week|date:"Y" }} {% endif %} {% if previous_week and next_week %}--{% endif %} {% if next_week %} - Next week: {{ next_week|date:"F Y" }} + Next week: {{ next_week|date:"W" }} of year {{ next_week|date:"Y" }} {% endif %}

    - In this example, you are outputting the week number. The default - ``week_format`` in the ``WeekArchiveView`` uses week format ``'%U'`` - which is based on the United States week system where the week begins on a - Sunday. The ``'%W'`` format uses the ISO week format and its week - begins on a Monday. The ``'%W'`` format is the same in both the - :func:`~time.strftime` and the :tfilter:`date`. - - However, the :tfilter:`date` template filter does not have an equivalent - output format that supports the US based week system. The :tfilter:`date` - filter ``'%U'`` outputs the number of seconds since the Unix epoch. + In this example, you are outputting the week number. Keep in mind that week + numbers computed by the :tfilter:`date` template filter with the ``'W'`` + format character are not always the same as those computed by + :func:`~time.strftime` and :func:`~time.strptime` with the ``'%W'`` format + string. For year 2015, for example, week numbers output by :tfilter:`date` + are higher by one compared to those output by :func:`~time.strftime`. There + isn't an equivalent for the ``'%U'`` :func:`~time.strftime` format string + in :tfilter:`date`. Therefore, you should avoid using :tfilter:`date` to + generate URLs for ``WeekArchiveView``. ``DayArchiveView`` ================== From 7e799217c5cf1ba365af41c801d5cfbadea18fa9 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 19 Feb 2016 11:33:17 -0500 Subject: [PATCH 459/756] [1.9.x] Added stub release notes for security issues. --- docs/releases/1.8.10.txt | 4 ++-- docs/releases/1.9.3.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt index b1319ff80204..d93f24372032 100644 --- a/docs/releases/1.8.10.txt +++ b/docs/releases/1.8.10.txt @@ -2,9 +2,9 @@ Django 1.8.10 release notes =========================== -*Under development* +*March 1, 2015* -Django 1.8.10 fixes several bugs in 1.8.9. +Django 1.8.10 fixes two security issues and several bugs in 1.8.9. Bugfixes ======== diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt index e94d555ec065..8cc57d8a814c 100644 --- a/docs/releases/1.9.3.txt +++ b/docs/releases/1.9.3.txt @@ -2,9 +2,9 @@ Django 1.9.3 release notes ========================== -*Under development* +*March 1, 2015* -Django 1.9.3 fixes several bugs in 1.9.2. +Django 1.9.3 fixes two security issues and several bugs in 1.9.2. Bugfixes ======== From fc6d147a63f89795dbcdecb0559256470fff4380 Mon Sep 17 00:00:00 2001 From: Mark Striemer Date: Mon, 22 Feb 2016 16:50:23 -0500 Subject: [PATCH 460/756] [1.9.x] Fixed CVE-2016-2512 -- Prevented spoofing is_safe_url() with basic auth. This is a security fix. --- django/utils/http.py | 8 ++++++-- docs/releases/1.8.10.txt | 16 ++++++++++++++++ docs/releases/1.9.3.txt | 16 ++++++++++++++++ tests/utils_tests/test_http.py | 12 ++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/django/utils/http.py b/django/utils/http.py index 70bcbd90ac3b..fb18f48f771a 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -290,8 +290,12 @@ def is_safe_url(url, host=None): url = url.strip() if not url: return False - # Chrome treats \ completely as / - url = url.replace('\\', '/') + # Chrome treats \ completely as / in paths but it could be part of some + # basic auth credentials so we need to check both URLs. + return _is_safe_url(url, host) and _is_safe_url(url.replace('\\', '/'), host) + + +def _is_safe_url(url, host): # Chrome considers any URL with more than two slashes to be absolute, but # urlparse is not so flexible. Treat any url with three slashes as unsafe. if url.startswith('///'): diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt index d93f24372032..73c7cc04a4df 100644 --- a/docs/releases/1.8.10.txt +++ b/docs/releases/1.8.10.txt @@ -6,6 +6,22 @@ Django 1.8.10 release notes Django 1.8.10 fixes two security issues and several bugs in 1.8.9. +CVE-2016-2512: Malicious redirect and possible XSS attack via user-supplied redirect URLs containing basic auth +=============================================================================================================== + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n `) +to redirect the user to an "on success" URL. The security check for these +redirects (namely ``django.utils.http.is_safe_url()``) considered some URLs +with basic authentication credentials "safe" when they shouldn't be. + +For example, a URL like ``http://mysite.example.com\@attacker.com`` would be +considered safe if the request's host is ``http://mysite.example.com``, but +redirecting to this URL sends the user to ``attacker.com``. + +Also, if a developer relies on ``is_safe_url()`` to provide safe redirect +targets and puts such a URL into a link, they could suffer from an XSS attack. + Bugfixes ======== diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt index 8cc57d8a814c..056122b16e39 100644 --- a/docs/releases/1.9.3.txt +++ b/docs/releases/1.9.3.txt @@ -6,6 +6,22 @@ Django 1.9.3 release notes Django 1.9.3 fixes two security issues and several bugs in 1.9.2. +CVE-2016-2512: Malicious redirect and possible XSS attack via user-supplied redirect URLs containing basic auth +=============================================================================================================== + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n `) +to redirect the user to an "on success" URL. The security check for these +redirects (namely ``django.utils.http.is_safe_url()``) considered some URLs +with basic authentication credentials "safe" when they shouldn't be. + +For example, a URL like ``http://mysite.example.com\@attacker.com`` would be +considered safe if the request's host is ``http://mysite.example.com``, but +redirecting to this URL sends the user to ``attacker.com``. + +Also, if a developer relies on ``is_safe_url()`` to provide safe redirect +targets and puts such a URL into a link, they could suffer from an XSS attack. + Bugfixes ======== diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py index 6051818958d3..ad9a1666c35a 100644 --- a/tests/utils_tests/test_http.py +++ b/tests/utils_tests/test_http.py @@ -92,6 +92,11 @@ def test_is_safe_url(self): 'javascript:alert("XSS")', '\njavascript:alert(x)', '\x08//example.com', + r'http://otherserver\@example.com', + r'http:\\testserver\@example.com', + r'http://testserver\me:pass@example.com', + r'http://testserver\@example.com', + r'http:\\testserver\confirm\me@example.com', '\n'): self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url) for good_url in ('/view/?param=http://example.com', @@ -101,8 +106,15 @@ def test_is_safe_url(self): 'https://testserver/', 'HTTPS://testserver/', '//testserver/', + 'http://testserver/confirm?email=me@example.com', '/url%20with%20spaces/'): self.assertTrue(http.is_safe_url(good_url, host='testserver'), "%s should be allowed" % good_url) + # Valid basic auth credentials are allowed. + self.assertTrue(http.is_safe_url(r'http://user:pass@testserver/', host='user:pass@testserver')) + # A path without host is allowed. + self.assertTrue(http.is_safe_url('/confirm/me@example.com')) + # Basic auth without host is not allowed. + self.assertFalse(http.is_safe_url(r'http://testserver\@example.com')) def test_urlsafe_base64_roundtrip(self): bytestring = b'foo' From af7d09b0c5c6ab68e629fd9baf736f9dd203b18e Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Sat, 13 Feb 2016 21:09:46 +0100 Subject: [PATCH 461/756] [1.9.x] Fixed CVE-2016-2513 -- Fixed user enumeration timing attack during login. This is a security fix. --- django/contrib/auth/hashers.py | 77 +++++++++++++++++++++++--------- docs/releases/1.8.10.txt | 33 ++++++++++++++ docs/releases/1.9.3.txt | 33 ++++++++++++++ docs/topics/auth/passwords.txt | 31 +++++++++++++ tests/auth_tests/test_hashers.py | 58 +++++++++++++++++++++++- 5 files changed, 211 insertions(+), 21 deletions(-) diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 93d4598ccec0..05ac8493acb7 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -4,6 +4,7 @@ import binascii import hashlib import importlib +import warnings from collections import OrderedDict from django.conf import settings @@ -46,10 +47,17 @@ def check_password(password, encoded, setter=None, preferred='default'): preferred = get_hasher(preferred) hasher = identify_hasher(encoded) - must_update = hasher.algorithm != preferred.algorithm - if not must_update: - must_update = preferred.must_update(encoded) + hasher_changed = hasher.algorithm != preferred.algorithm + must_update = hasher_changed or preferred.must_update(encoded) is_correct = hasher.verify(password, encoded) + + # If the hasher didn't change (we don't protect against enumeration if it + # does) and the password should get updated, try to close the timing gap + # between the work factor of the current encoded password and the default + # work factor. + if not is_correct and not hasher_changed and must_update: + hasher.harden_runtime(password, encoded) + if setter and is_correct and must_update: setter(password) return is_correct @@ -216,6 +224,19 @@ def safe_summary(self, encoded): def must_update(self, encoded): return False + def harden_runtime(self, password, encoded): + """ + Bridge the runtime gap between the work factor supplied in `encoded` + and the work factor suggested by this hasher. + + Taking PBKDF2 as an example, if `encoded` contains 20000 iterations and + `self.iterations` is 30000, this method should run password through + another 10000 iterations of PBKDF2. Similar approaches should exist + for any hasher that has a work factor. If not, this method should be + defined as a no-op to silence the warning. + """ + warnings.warn('subclasses of BasePasswordHasher should provide a harden_runtime() method') + class PBKDF2PasswordHasher(BasePasswordHasher): """ @@ -258,6 +279,12 @@ def must_update(self, encoded): algorithm, iterations, salt, hash = encoded.split('$', 3) return int(iterations) != self.iterations + def harden_runtime(self, password, encoded): + algorithm, iterations, salt, hash = encoded.split('$', 3) + extra_iterations = self.iterations - int(iterations) + if extra_iterations > 0: + self.encode(password, salt, extra_iterations) + class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher): """ @@ -308,23 +335,8 @@ def encode(self, password, salt): def verify(self, password, encoded): algorithm, data = encoded.split('$', 1) assert algorithm == self.algorithm - bcrypt = self._load_library() - - # Hash the password prior to using bcrypt to prevent password truncation - # See: https://code.djangoproject.com/ticket/20138 - if self.digest is not None: - # We use binascii.hexlify here because Python3 decided that a hex encoded - # bytestring is somehow a unicode. - password = binascii.hexlify(self.digest(force_bytes(password)).digest()) - else: - password = force_bytes(password) - - # Ensure that our data is a bytestring - data = force_bytes(data) - # force_bytes() necessary for py-bcrypt compatibility - hashpw = force_bytes(bcrypt.hashpw(password, data)) - - return constant_time_compare(data, hashpw) + encoded_2 = self.encode(password, force_bytes(data)) + return constant_time_compare(encoded, encoded_2) def safe_summary(self, encoded): algorithm, empty, algostr, work_factor, data = encoded.split('$', 4) @@ -341,6 +353,16 @@ def must_update(self, encoded): algorithm, empty, algostr, rounds, data = encoded.split('$', 4) return int(rounds) != self.rounds + def harden_runtime(self, password, encoded): + _, data = encoded.split('$', 1) + salt = data[:29] # Length of the salt in bcrypt. + rounds = data.split('$')[2] + # work factor is logarithmic, adding one doubles the load. + diff = 2**(self.rounds - int(rounds)) - 1 + while diff > 0: + self.encode(password, force_bytes(salt)) + diff -= 1 + class BCryptPasswordHasher(BCryptSHA256PasswordHasher): """ @@ -388,6 +410,9 @@ def safe_summary(self, encoded): (_('hash'), mask_hash(hash)), ]) + def harden_runtime(self, password, encoded): + pass + class MD5PasswordHasher(BasePasswordHasher): """ @@ -416,6 +441,9 @@ def safe_summary(self, encoded): (_('hash'), mask_hash(hash)), ]) + def harden_runtime(self, password, encoded): + pass + class UnsaltedSHA1PasswordHasher(BasePasswordHasher): """ @@ -448,6 +476,9 @@ def safe_summary(self, encoded): (_('hash'), mask_hash(hash)), ]) + def harden_runtime(self, password, encoded): + pass + class UnsaltedMD5PasswordHasher(BasePasswordHasher): """ @@ -481,6 +512,9 @@ def safe_summary(self, encoded): (_('hash'), mask_hash(encoded, show=3)), ]) + def harden_runtime(self, password, encoded): + pass + class CryptPasswordHasher(BasePasswordHasher): """ @@ -515,3 +549,6 @@ def safe_summary(self, encoded): (_('salt'), salt), (_('hash'), mask_hash(data, show=3)), ]) + + def harden_runtime(self, password, encoded): + pass diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt index 73c7cc04a4df..d57afc470d3f 100644 --- a/docs/releases/1.8.10.txt +++ b/docs/releases/1.8.10.txt @@ -22,6 +22,39 @@ redirecting to this URL sends the user to ``attacker.com``. Also, if a developer relies on ``is_safe_url()`` to provide safe redirect targets and puts such a URL into a link, they could suffer from an XSS attack. +CVE-2016-2513: User enumeration through timing difference on password hasher work factor upgrade +================================================================================================ + +In each major version of Django since 1.6, the default number of iterations for +the ``PBKDF2PasswordHasher`` and its subclasses has increased. This improves +the security of the password as the speed of hardware increases, however, it +also creates a timing difference between a login request for a user with a +password encoded in an older number of iterations and login request for a +nonexistent user (which runs the default hasher's default number of iterations +since Django 1.6). + +This only affects users who haven't logged in since the iterations were +increased. The first time a user logs in after an iterations increase, their +password is updated with the new iterations and there is no longer a timing +difference. + +The new ``BasePasswordHasher.harden_runtime()`` method allows hashers to bridge +the runtime gap between the work factor (e.g. iterations) supplied in existing +encoded passwords and the default work factor of the hasher. This method +is implemented for ``PBKDF2PasswordHasher`` and ``BCryptPasswordHasher``. +The number of rounds for the latter hasher hasn't changed since Django 1.4, but +some projects may subclass it and increase the work factor as needed. + +A warning will be emitted for any :ref:`third-party password hashers that don't +implement ` a ``harden_runtime()`` method. + +If you have different password hashes in your database (such as SHA1 hashes +from users who haven't logged in since the default hasher switched to PBKDF2 +in Django 1.4), the timing difference on a login request for these users may be +even greater and this fix doesn't remedy that difference (or any difference +when changing hashers). You may be able to :ref:`upgrade those hashes +` to prevent a timing attack for that case. + Bugfixes ======== diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt index 056122b16e39..355eaf96e962 100644 --- a/docs/releases/1.9.3.txt +++ b/docs/releases/1.9.3.txt @@ -22,6 +22,39 @@ redirecting to this URL sends the user to ``attacker.com``. Also, if a developer relies on ``is_safe_url()`` to provide safe redirect targets and puts such a URL into a link, they could suffer from an XSS attack. +CVE-2016-2513: User enumeration through timing difference on password hasher work factor upgrade +================================================================================================ + +In each major version of Django since 1.6, the default number of iterations for +the ``PBKDF2PasswordHasher`` and its subclasses has increased. This improves +the security of the password as the speed of hardware increases, however, it +also creates a timing difference between a login request for a user with a +password encoded in an older number of iterations and login request for a +nonexistent user (which runs the default hasher's default number of iterations +since Django 1.6). + +This only affects users who haven't logged in since the iterations were +increased. The first time a user logs in after an iterations increase, their +password is updated with the new iterations and there is no longer a timing +difference. + +The new ``BasePasswordHasher.harden_runtime()`` method allows hashers to bridge +the runtime gap between the work factor (e.g. iterations) supplied in existing +encoded passwords and the default work factor of the hasher. This method +is implemented for ``PBKDF2PasswordHasher`` and ``BCryptPasswordHasher``. +The number of rounds for the latter hasher hasn't changed since Django 1.4, but +some projects may subclass it and increase the work factor as needed. + +A warning will be emitted for any :ref:`third-party password hashers that don't +implement ` a ``harden_runtime()`` method. + +If you have different password hashes in your database (such as SHA1 hashes +from users who haven't logged in since the default hasher switched to PBKDF2 +in Django 1.4), the timing difference on a login request for these users may be +even greater and this fix doesn't remedy that difference (or any difference +when changing hashers). You may be able to :ref:`upgrade those hashes +` to prevent a timing attack for that case. + Bugfixes ======== diff --git a/docs/topics/auth/passwords.txt b/docs/topics/auth/passwords.txt index 17d47d8a0b98..2da25ecf6ccc 100644 --- a/docs/topics/auth/passwords.txt +++ b/docs/topics/auth/passwords.txt @@ -195,6 +195,14 @@ unmentioned algorithms won't be able to upgrade. Hashed passwords will be updated when increasing (or decreasing) the number of PBKDF2 iterations or bcrypt rounds. +Be aware that if all the passwords in your database aren't encoded in the +default hasher's algorithm, you may be vulnerable to a user enumeration timing +attack due to a difference between the duration of a login request for a user +with a password encoded in a non-default algorithm and the duration of a login +request for a nonexistent user (which runs the default hasher). You may be able +to mitigate this by :ref:`upgrading older password hashes +`. + .. versionchanged:: 1.9 Passwords updates when changing the number of bcrypt rounds was added. @@ -288,6 +296,29 @@ Include any other hashers that your site uses in this list. .. _bcrypt: https://en.wikipedia.org/wiki/Bcrypt .. _`bcrypt library`: https://pypi.python.org/pypi/bcrypt/ +.. _write-your-own-password-hasher: + +Writing your own hasher +----------------------- + +.. versionadded:: 1.9.3 + +If you write your own password hasher that contains a work factor such as a +number of iterations, you should implement a +``harden_runtime(self, password, encoded)`` method to bridge the runtime gap +between the work factor supplied in the ``encoded`` password and the default +work factor of the hasher. This prevents a user enumeration timing attack due +to difference between a login request for a user with a password encoded in an +older number of iterations and a nonexistent user (which runs the default +hasher's default number of iterations). + +Taking PBKDF2 as example, if ``encoded`` contains 20,000 iterations and the +hasher's default ``iterations`` is 30,000, the method should run ``password`` +through another 10,000 iterations of PBKDF2. + +If your hasher doesn't have a work factor, implement the method as a no-op +(``pass``). + Manually managing a user's password =================================== diff --git a/tests/auth_tests/test_hashers.py b/tests/auth_tests/test_hashers.py index 93a66a545b08..e67534a34406 100644 --- a/tests/auth_tests/test_hashers.py +++ b/tests/auth_tests/test_hashers.py @@ -10,9 +10,10 @@ check_password, get_hasher, identify_hasher, is_password_usable, make_password, ) -from django.test import SimpleTestCase +from django.test import SimpleTestCase, mock from django.test.utils import override_settings from django.utils import six +from django.utils.encoding import force_bytes try: import crypt @@ -209,6 +210,28 @@ def setter(password): finally: hasher.rounds = old_rounds + @skipUnless(bcrypt, "bcrypt not installed") + def test_bcrypt_harden_runtime(self): + hasher = get_hasher('bcrypt') + self.assertEqual('bcrypt', hasher.algorithm) + + with mock.patch.object(hasher, 'rounds', 4): + encoded = make_password('letmein', hasher='bcrypt') + + with mock.patch.object(hasher, 'rounds', 6), \ + mock.patch.object(hasher, 'encode', side_effect=hasher.encode): + hasher.harden_runtime('wrong_password', encoded) + + # Increasing rounds from 4 to 6 means an increase of 4 in workload, + # therefore hardening should run 3 times to make the timing the + # same (the original encode() call already ran once). + self.assertEqual(hasher.encode.call_count, 3) + + # Get the original salt (includes the original workload factor) + algorithm, data = encoded.split('$', 1) + expected_call = (('wrong_password', force_bytes(data[:29])),) + self.assertEqual(hasher.encode.call_args_list, [expected_call] * 3) + def test_unusable(self): encoded = make_password(None) self.assertEqual(len(encoded), len(UNUSABLE_PASSWORD_PREFIX) + UNUSABLE_PASSWORD_SUFFIX_LENGTH) @@ -316,6 +339,25 @@ def setter(password): finally: hasher.iterations = old_iterations + def test_pbkdf2_harden_runtime(self): + hasher = get_hasher('default') + self.assertEqual('pbkdf2_sha256', hasher.algorithm) + + with mock.patch.object(hasher, 'iterations', 1): + encoded = make_password('letmein') + + with mock.patch.object(hasher, 'iterations', 6), \ + mock.patch.object(hasher, 'encode', side_effect=hasher.encode): + hasher.harden_runtime('wrong_password', encoded) + + # Encode should get called once ... + self.assertEqual(hasher.encode.call_count, 1) + + # ... with the original salt and 5 iterations. + algorithm, iterations, salt, hash = encoded.split('$', 3) + expected_call = (('wrong_password', salt, 5),) + self.assertEqual(hasher.encode.call_args, expected_call) + def test_pbkdf2_upgrade_new_hasher(self): hasher = get_hasher('default') self.assertEqual('pbkdf2_sha256', hasher.algorithm) @@ -344,6 +386,20 @@ def setter(password): self.assertTrue(check_password('letmein', encoded, setter)) self.assertTrue(state['upgraded']) + def test_check_password_calls_harden_runtime(self): + hasher = get_hasher('default') + encoded = make_password('letmein') + + with mock.patch.object(hasher, 'harden_runtime'), \ + mock.patch.object(hasher, 'must_update', return_value=True): + # Correct password supplied, no hardening needed + check_password('letmein', encoded) + self.assertEqual(hasher.harden_runtime.call_count, 0) + + # Wrong password supplied, hardening needed + check_password('wrong_password', encoded) + self.assertEqual(hasher.harden_runtime.call_count, 1) + def test_load_library_no_algorithm(self): with self.assertRaises(ValueError) as e: BasePasswordHasher()._load_library() From 37935743edbf60201adb1b53b56b8cafa754c69a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Mar 2016 11:38:39 -0500 Subject: [PATCH 462/756] [1.9.x] Bumped version for 1.9.3 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 3c44b25cb942..0dceeaa65571 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 3, 'alpha', 0) +VERSION = (1, 9, 3, 'final', 0) __version__ = get_version(VERSION) From 31ca830a29db6374b05f7d393909f1fdddceb357 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Mar 2016 12:22:10 -0500 Subject: [PATCH 463/756] [1.9.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 0dceeaa65571..942857ad34a0 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 3, 'final', 0) +VERSION = (1, 9, 4, 'alpha', 0) __version__ = get_version(VERSION) From a53ee2bbf4e6676ba67160a04d02be63c2bff767 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Mar 2016 12:32:42 -0500 Subject: [PATCH 464/756] [1.9.x] Added CVE-2016-2512/2513 to security release archive. Backport of 24fc9352183c449a8b11d1c7b442e70aa61a8800 from master --- docs/releases/security.txt | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index ddb4871a7b7b..c8d29ef7ba6b 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -691,8 +691,8 @@ Versions affected * Django 1.8 `(patch) `__ * Django 1.7 `(patch) `__ -February 1, 2016 -- CVE-2016-2048 ---------------------------------- +February 1, 2016 - CVE-2016-2048 +-------------------------------- `CVE-2016-2048 `_: User with "change" but not "add" permission can create objects for ``ModelAdmin``’s with ``save_as=True``. @@ -702,3 +702,29 @@ Versions affected ~~~~~~~~~~~~~~~~~ * Django 1.9 `(patch) `__ + +March 1, 2016 - CVE-2016-2512 +----------------------------- + +`CVE-2016-2512 `_: +Malicious redirect and possible XSS attack via user-supplied redirect URLs containing basic auth. +`Full description `__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 1.9 `(patch) `__ +* Django 1.8 `(patch) `__ + +March 1, 2016 - CVE-2016-2513 +----------------------------- + +`CVE-2016-2513 `_: +User enumeration through timing difference on password hasher work factor upgrade. +`Full description `__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 1.9 `(patch) `__ +* Django 1.8 `(patch) `__ From e0ea4edca0801fec2fa1422f52c3377321651f42 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Mar 2016 12:39:01 -0500 Subject: [PATCH 465/756] [1.9.x] Added stub release notes for 1.9.4. Backport of 2e895d2870860e9855b79fcda41693783671ed12 from master --- docs/releases/1.9.4.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.9.4.txt diff --git a/docs/releases/1.9.4.txt b/docs/releases/1.9.4.txt new file mode 100644 index 000000000000..15adcee3290e --- /dev/null +++ b/docs/releases/1.9.4.txt @@ -0,0 +1,12 @@ +========================== +Django 1.9.4 release notes +========================== + +*Under development* + +Django 1.9.4 fixes several bugs in 1.9.3. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index adbda90109f5..baf72288b9a8 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.9.4 1.9.3 1.9.2 1.9.1 From 6679cdd92c71a77f1809c180174de026a6c17918 Mon Sep 17 00:00:00 2001 From: Alasdair Nicol Date: Tue, 1 Mar 2016 16:02:09 +0000 Subject: [PATCH 466/756] [1.9.x] Fixed #26303 -- Updated links to mod_wsgi docs. Backport of 8c42cf0cbd87f344748423f235938dd6ade03f55 from master --- docs/howto/deployment/wsgi/apache-auth.txt | 4 ++-- docs/howto/deployment/wsgi/modwsgi.txt | 4 ++-- tests/auth_tests/test_handlers.py | 7 +++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/howto/deployment/wsgi/apache-auth.txt b/docs/howto/deployment/wsgi/apache-auth.txt index 9246c081e7be..e6fea98c85b0 100644 --- a/docs/howto/deployment/wsgi/apache-auth.txt +++ b/docs/howto/deployment/wsgi/apache-auth.txt @@ -97,8 +97,8 @@ Requests beginning with ``/secret/`` will now require a user to authenticate. The mod_wsgi `access control mechanisms documentation`_ provides additional details and information about alternative methods of authentication. -.. _Defining Application Groups: https://code.google.com/p/modwsgi/wiki/ConfigurationGuidelines#Defining_Application_Groups -.. _access control mechanisms documentation: https://code.google.com/p/modwsgi/wiki/AccessControlMechanisms +.. _Defining Application Groups: https://modwsgi.readthedocs.org/en/develop/user-guides/configuration-guidelines.html#defining-application-groups +.. _access control mechanisms documentation: https://modwsgi.readthedocs.org/en/develop/user-guides/access-control-mechanisms.html Authorization with ``mod_wsgi`` and Django groups ------------------------------------------------- diff --git a/docs/howto/deployment/wsgi/modwsgi.txt b/docs/howto/deployment/wsgi/modwsgi.txt index a3d5e8a571c7..988ff8a389d5 100644 --- a/docs/howto/deployment/wsgi/modwsgi.txt +++ b/docs/howto/deployment/wsgi/modwsgi.txt @@ -140,7 +140,7 @@ to the configuration above: See the official mod_wsgi documentation for `details on setting up daemon mode`_. -.. _details on setting up daemon mode: https://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide#Delegation_To_Daemon_Process +.. _details on setting up daemon mode: https://modwsgi.readthedocs.org/en/develop/user-guides/quick-configuration-guide.html#delegation-to-daemon-process .. _serving-files: @@ -198,7 +198,7 @@ If you are using a version of Apache older than 2.4, replace .. More details on configuring a mod_wsgi site to serve static files can be found .. in the mod_wsgi documentation on `hosting static files`_. -.. _hosting static files: https://code.google.com/p/modwsgi/wiki/ConfigurationGuidelines#Hosting_Of_Static_Files +.. _hosting static files: https://modwsgi.readthedocs.org/en/develop/user-guides/configuration-guidelines.html#hosting-of-static-files .. _serving-the-admin-files: diff --git a/tests/auth_tests/test_handlers.py b/tests/auth_tests/test_handlers.py index e2ceab9c4e48..6228607fae52 100644 --- a/tests/auth_tests/test_handlers.py +++ b/tests/auth_tests/test_handlers.py @@ -23,7 +23,7 @@ class ModWsgiHandlerTestCase(TransactionTestCase): def test_check_password(self): """ Verify that check_password returns the correct values as per - http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider + https://modwsgi.readthedocs.org/en/develop/user-guides/access-control-mechanisms.html#apache-authentication-provider """ User.objects.create_user('test', 'test@example.com', 'test') @@ -44,8 +44,7 @@ def test_check_password(self): def test_check_password_custom_user(self): """ Verify that check_password returns the correct values as per - http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider - + https://modwsgi.readthedocs.org/en/develop/user-guides/access-control-mechanisms.html#apache-authentication-provider with custom user installed """ @@ -63,7 +62,7 @@ def test_check_password_custom_user(self): def test_groups_for_user(self): """ Check that groups_for_user returns correct values as per - http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Group_Authorisation + https://modwsgi.readthedocs.org/en/develop/user-guides/access-control-mechanisms.html#apache-group-authorisation """ user1 = User.objects.create_user('test', 'test@example.com', 'test') User.objects.create_user('test1', 'test1@example.com', 'test1') From fddd79dacd6e9e24a7c439b841e952ef093d1ef2 Mon Sep 17 00:00:00 2001 From: Dmitry Dygalo Date: Wed, 2 Mar 2016 08:15:53 +0100 Subject: [PATCH 467/756] [1.9.x] Fixed typo in 1.9.3/1.8.10 release date. Backport of 5155c2b4587629c4bc77a11846e5b9d3ba5a43ef from master --- docs/releases/1.8.10.txt | 2 +- docs/releases/1.9.3.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt index d57afc470d3f..77d5c48ff6b8 100644 --- a/docs/releases/1.8.10.txt +++ b/docs/releases/1.8.10.txt @@ -2,7 +2,7 @@ Django 1.8.10 release notes =========================== -*March 1, 2015* +*March 1, 2016* Django 1.8.10 fixes two security issues and several bugs in 1.8.9. diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt index 355eaf96e962..ad99cd863c7d 100644 --- a/docs/releases/1.9.3.txt +++ b/docs/releases/1.9.3.txt @@ -2,7 +2,7 @@ Django 1.9.3 release notes ========================== -*March 1, 2015* +*March 1, 2016* Django 1.9.3 fixes two security issues and several bugs in 1.9.2. From c6d39c644d7ed990aef30b41c9fd5569f200089c Mon Sep 17 00:00:00 2001 From: Alasdair Nicol Date: Wed, 2 Mar 2016 15:48:13 +0000 Subject: [PATCH 468/756] [1.9.x] Fixed #26309 -- Documented that login URL settings no longer support dotted paths. Backport of 2404d209a5e8c4573927e14587735562b79e13ed from master --- docs/internals/deprecation.txt | 3 +++ docs/ref/settings.txt | 27 ++++++++++++++------------- docs/releases/1.8.txt | 3 +++ 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 4b90a822332e..28b25270d971 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -131,6 +131,9 @@ details on these changes. * The ability to :func:`~django.core.urlresolvers.reverse` URLs using a dotted Python path will be removed. +* The ability to use a dotted Python path for the ``LOGIN_URL`` and + ``LOGIN_REDIRECT_URL`` settings will be removed. + * Support for :py:mod:`optparse` will be dropped for custom management commands (replaced by :py:mod:`argparse`). diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index d6da94ff7d89..360fd7dcaf34 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -2795,9 +2795,14 @@ The URL where requests are redirected after login when the This is used by the :func:`~django.contrib.auth.decorators.login_required` decorator, for example. -This setting also accepts view function names and :ref:`named URL patterns -` which can be used to reduce configuration duplication -since you don't have to define the URL in two places (``settings`` and URLconf). +This setting also accepts :ref:`named URL patterns ` which +can be used to reduce configuration duplication since you don't have to define +the URL in two places (``settings`` and URLconf). + +.. deprecated:: 1.8 + + The setting may also be a dotted Python path to a view function. Support + for this will be removed in Django 1.10. .. setting:: LOGIN_URL @@ -2809,18 +2814,14 @@ Default: ``'/accounts/login/'`` The URL where requests are redirected for login, especially when using the :func:`~django.contrib.auth.decorators.login_required` decorator. -This setting also accepts view function names and :ref:`named URL patterns -` which can be used to reduce configuration duplication -since you don't have to define the URL in two places (``settings`` and URLconf). - -.. setting:: LOGOUT_URL +This setting also accepts :ref:`named URL patterns ` which +can be used to reduce configuration duplication since you don't have to define +the URL in two places (``settings`` and URLconf). -LOGOUT_URL ----------- - -Default: ``'/accounts/logout/'`` +.. deprecated:: 1.8 -LOGIN_URL counterpart. + The setting may also be a dotted Python path to a view function. Support + for this will be removed in Django 1.10. .. setting:: PASSWORD_RESET_TIMEOUT_DAYS diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index ccc6bbdc51c8..c4b6752c97e0 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -1356,6 +1356,9 @@ to ensure compatibility when reversing by Python path is removed in Django 1.10. Similarly for GIS sitemaps, add ``name='django.contrib.gis.sitemaps.views.kml'`` or ``name='django.contrib.gis.sitemaps.views.kmz'``. +If you are using a Python path for the :setting:`LOGIN_URL` or +:setting:`LOGIN_REDIRECT_URL` setting, use the name of the ``url()`` instead. + .. _security issue: https://www.djangoproject.com/weblog/2014/apr/21/security/#s-issue-unexpected-code-execution-using-reverse Aggregate methods and modules From 377ca697a133e6063be3dff09b83b1c533d91950 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 3 Mar 2016 19:34:31 -0500 Subject: [PATCH 469/756] [1.9.x] Fixed #26321 -- Added missing "for_save" parameter in expressions example. Thanks tomaszn for the patch. Backport of de8a11ba18d5902c668d4db47c38c9c6bdf9c1da from master --- docs/ref/models/expressions.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ref/models/expressions.txt b/docs/ref/models/expressions.txt index a580ca426e11..223c0f6460bd 100644 --- a/docs/ref/models/expressions.txt +++ b/docs/ref/models/expressions.txt @@ -460,7 +460,7 @@ calling the appropriate methods on the wrapped expression. Tells Django that this expression contains an aggregate and that a ``GROUP BY`` clause needs to be added to the query. - .. method:: resolve_expression(query=None, allow_joins=True, reuse=None, summarize=False) + .. method:: resolve_expression(query=None, allow_joins=True, reuse=None, summarize=False, for_save=False) Provides the chance to do any pre-processing or validation of the expression before it's added to the query. ``resolve_expression()`` @@ -591,11 +591,11 @@ Now we implement the pre-processing and validation. Since we do not have any of our own validation at this point, we just delegate to the nested expressions:: - def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False): + def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False): c = self.copy() c.is_summary = summarize for pos, expression in enumerate(self.expressions): - c.expressions[pos] = expression.resolve_expression(query, allow_joins, reuse, summarize) + c.expressions[pos] = expression.resolve_expression(query, allow_joins, reuse, summarize, for_save) return c Next, we write the method responsible for generating the SQL:: From 2a9ce3627179425f20c56ae43cac65d9f67c69df Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 4 Mar 2016 09:47:43 -0500 Subject: [PATCH 470/756] [1.9.x] Added stub release notes for 1.8.11. Backport of 2f0c785a4c2353a3035ba6022cec5e25fb9d569b from master --- docs/releases/1.8.11.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.8.11.txt diff --git a/docs/releases/1.8.11.txt b/docs/releases/1.8.11.txt new file mode 100644 index 000000000000..9837371f7bcf --- /dev/null +++ b/docs/releases/1.8.11.txt @@ -0,0 +1,12 @@ +=========================== +Django 1.8.11 release notes +=========================== + +*Under development* + +Django 1.8.11 fixes several bugs in 1.8.10. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index baf72288b9a8..0224a2e6c12a 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -36,6 +36,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.8.11 1.8.10 1.8.9 1.8.8 From 067d8c3500cfe63627b2f5d342f49451d2e68307 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 4 Mar 2016 14:16:56 -0500 Subject: [PATCH 471/756] [1.9.x] Fixed typo in docs/releases/1.9.1.txt. Backport of cecbf1bdef04e00e6947f47d96198aa57c2a0dc3 from master --- docs/releases/1.9.1.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.9.1.txt b/docs/releases/1.9.1.txt index b336419ebe56..27c22c000106 100644 --- a/docs/releases/1.9.1.txt +++ b/docs/releases/1.9.1.txt @@ -36,7 +36,7 @@ Bugfixes to work on both Python 2 and Python 3. * Prevented ``QuerySet.delete()`` from crashing on MySQL when querying across - relations (:ticket`25882`). + relations (:ticket:`25882`). * Fixed evaluation of zero-length slices of ``QuerySet.values()`` (:ticket:`25894`). From 78f48300567b816b3c8177c33bef1a3ea6b36987 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 4 Mar 2016 15:41:52 +0100 Subject: [PATCH 472/756] [1.9.x] Fixed #26308 -- Prevented crash with binary URLs in is_safe_url() This fixes a regression introduced by c5544d28923. Thanks John Eskew for the reporti and Tim Graham for the review. Backport of ada7a4aef from master. --- django/utils/http.py | 2 ++ docs/releases/1.8.11.txt | 10 +++------- docs/releases/1.9.4.txt | 10 +++------- tests/utils_tests/test_http.py | 12 ++++++++++++ 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/django/utils/http.py b/django/utils/http.py index fb18f48f771a..e925cfc543ff 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -290,6 +290,8 @@ def is_safe_url(url, host=None): url = url.strip() if not url: return False + if six.PY2: + url = force_text(url, errors='replace') # Chrome treats \ completely as / in paths but it could be part of some # basic auth credentials so we need to check both URLs. return _is_safe_url(url, host) and _is_safe_url(url.replace('\\', '/'), host) diff --git a/docs/releases/1.8.11.txt b/docs/releases/1.8.11.txt index 9837371f7bcf..b01807129b8d 100644 --- a/docs/releases/1.8.11.txt +++ b/docs/releases/1.8.11.txt @@ -2,11 +2,7 @@ Django 1.8.11 release notes =========================== -*Under development* +*March 4, 2016* -Django 1.8.11 fixes several bugs in 1.8.10. - -Bugfixes -======== - -* ... +Django 1.8.11 fixes a regression on Python 2 in the 1.8.10 security release +where ``utils.http.is_safe_url()`` crashes on bytestring URLs (:ticket:`26308`). diff --git a/docs/releases/1.9.4.txt b/docs/releases/1.9.4.txt index 15adcee3290e..d3f66bb7a1a5 100644 --- a/docs/releases/1.9.4.txt +++ b/docs/releases/1.9.4.txt @@ -2,11 +2,7 @@ Django 1.9.4 release notes ========================== -*Under development* +*March 4, 2016* -Django 1.9.4 fixes several bugs in 1.9.3. - -Bugfixes -======== - -* ... +Django 1.9.4 fixes a regression on Python 2 in the 1.9.3 security release +where ``utils.http.is_safe_url()`` crashes on bytestring URLs (:ticket:`26308`). diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py index ad9a1666c35a..a7c282aedc2b 100644 --- a/tests/utils_tests/test_http.py +++ b/tests/utils_tests/test_http.py @@ -1,3 +1,4 @@ +# -*- encoding: utf-8 -*- from __future__ import unicode_literals import sys @@ -109,6 +110,17 @@ def test_is_safe_url(self): 'http://testserver/confirm?email=me@example.com', '/url%20with%20spaces/'): self.assertTrue(http.is_safe_url(good_url, host='testserver'), "%s should be allowed" % good_url) + + if six.PY2: + # Check binary URLs, regression tests for #26308 + self.assertTrue( + http.is_safe_url(b'https://testserver/', host='testserver'), + "binary URLs should be allowed on Python 2" + ) + self.assertFalse(http.is_safe_url(b'\x08//example.com', host='testserver')) + self.assertTrue(http.is_safe_url('àview/'.encode('utf-8'), host='testserver')) + self.assertTrue(http.is_safe_url('àview'.encode('latin-1'), host='testserver')) + # Valid basic auth credentials are allowed. self.assertTrue(http.is_safe_url(r'http://user:pass@testserver/', host='user:pass@testserver')) # A path without host is allowed. From 9c195d45a64b0d2baee218e617ca3a762efc0bf5 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 4 Mar 2016 23:33:35 +0100 Subject: [PATCH 473/756] [1.9.x] Added safety to URL decoding in is_safe_url() on Python 2 The errors='replace' parameter to force_text altered the URL before checking it, which wasn't considered sane. Refs 24fc935218 and ada7a4aef. Backport of 552f03869e from master. --- django/utils/http.py | 5 ++++- docs/releases/1.8.11.txt | 2 +- docs/releases/1.9.4.txt | 2 +- tests/utils_tests/test_http.py | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/django/utils/http.py b/django/utils/http.py index e925cfc543ff..62e854da8a28 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -291,7 +291,10 @@ def is_safe_url(url, host=None): if not url: return False if six.PY2: - url = force_text(url, errors='replace') + try: + url = force_text(url) + except UnicodeDecodeError: + return False # Chrome treats \ completely as / in paths but it could be part of some # basic auth credentials so we need to check both URLs. return _is_safe_url(url, host) and _is_safe_url(url.replace('\\', '/'), host) diff --git a/docs/releases/1.8.11.txt b/docs/releases/1.8.11.txt index b01807129b8d..f33149b9e76f 100644 --- a/docs/releases/1.8.11.txt +++ b/docs/releases/1.8.11.txt @@ -2,7 +2,7 @@ Django 1.8.11 release notes =========================== -*March 4, 2016* +*March 5, 2016* Django 1.8.11 fixes a regression on Python 2 in the 1.8.10 security release where ``utils.http.is_safe_url()`` crashes on bytestring URLs (:ticket:`26308`). diff --git a/docs/releases/1.9.4.txt b/docs/releases/1.9.4.txt index d3f66bb7a1a5..36e4ea329a67 100644 --- a/docs/releases/1.9.4.txt +++ b/docs/releases/1.9.4.txt @@ -2,7 +2,7 @@ Django 1.9.4 release notes ========================== -*March 4, 2016* +*March 5, 2016* Django 1.9.4 fixes a regression on Python 2 in the 1.9.3 security release where ``utils.http.is_safe_url()`` crashes on bytestring URLs (:ticket:`26308`). diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py index a7c282aedc2b..dc117869addc 100644 --- a/tests/utils_tests/test_http.py +++ b/tests/utils_tests/test_http.py @@ -119,7 +119,7 @@ def test_is_safe_url(self): ) self.assertFalse(http.is_safe_url(b'\x08//example.com', host='testserver')) self.assertTrue(http.is_safe_url('àview/'.encode('utf-8'), host='testserver')) - self.assertTrue(http.is_safe_url('àview'.encode('latin-1'), host='testserver')) + self.assertFalse(http.is_safe_url('àview'.encode('latin-1'), host='testserver')) # Valid basic auth credentials are allowed. self.assertTrue(http.is_safe_url(r'http://user:pass@testserver/', host='user:pass@testserver')) From e16a0b0998e456d011327a28ce68ba8afbddfa9a Mon Sep 17 00:00:00 2001 From: Bob McDonald Date: Fri, 4 Mar 2016 10:24:23 +0900 Subject: [PATCH 474/756] [1.9.x] Fixed #26312 -- Documented "create database" requirement in tutorial 2. Backport of b388c294eb5745b3e01dedf0220636fae87ea9a2 from master --- docs/intro/tutorial02.txt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index fbf15e8d1b49..53daae91353a 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -41,11 +41,16 @@ If you are not using SQLite as your database, additional settings such as :setting:`USER`, :setting:`PASSWORD`, and :setting:`HOST` must be added. For more details, see the reference documentation for :setting:`DATABASES`. -.. note:: +.. admonition:: For non-SQLite users - If you're using PostgreSQL or MySQL, make sure you've created a database by - this point. Do that with "``CREATE DATABASE database_name;``" within your - database's interactive prompt. + If you're using a database besides SQLite, make sure you've created a + database by this point. Do that with "``CREATE DATABASE database_name;``" + within your database's interactive prompt. + + Also make sure that the database user provided in :file:`mysite/settings.py` + has "create database" privileges. This allows automatic creation of a + :ref:`test database ` which will be needed in a later + tutorial. If you're using SQLite, you don't need to create anything beforehand - the database file will be created automatically when it is needed. From dafddb6b8c0eb778072bec1ccd536bafad0eb936 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 5 Mar 2016 09:24:08 -0500 Subject: [PATCH 475/756] [1.9.x] Bumped version for 1.9.4 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 942857ad34a0..e0c468ff2a63 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 4, 'alpha', 0) +VERSION = (1, 9, 4, 'final', 0) __version__ = get_version(VERSION) From ae26a822cc30715956fee2b19ac2384785b347ec Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 5 Mar 2016 09:41:28 -0500 Subject: [PATCH 476/756] [1.9.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index e0c468ff2a63..4c75ce658bb1 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 4, 'final', 0) +VERSION = (1, 9, 5, 'alpha', 0) __version__ = get_version(VERSION) From 51b7f102134a4b8d5c484ff44d50526a955223e2 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 5 Mar 2016 10:00:40 -0500 Subject: [PATCH 477/756] [1.9.x] Added stub release notes for 1.9.5/1.8.12. Backport of c960af4adb87f8ce87f5698902b68e8332e448cb from master --- docs/releases/1.8.12.txt | 12 ++++++++++++ docs/releases/1.9.5.txt | 12 ++++++++++++ docs/releases/index.txt | 2 ++ 3 files changed, 26 insertions(+) create mode 100644 docs/releases/1.8.12.txt create mode 100644 docs/releases/1.9.5.txt diff --git a/docs/releases/1.8.12.txt b/docs/releases/1.8.12.txt new file mode 100644 index 000000000000..74879bd046f8 --- /dev/null +++ b/docs/releases/1.8.12.txt @@ -0,0 +1,12 @@ +=========================== +Django 1.8.12 release notes +=========================== + +*Under development* + +Django 1.8.12 several bugs in 1.8.11. + +Bugfixes +======== + +* ... diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt new file mode 100644 index 000000000000..edff4db6bc4e --- /dev/null +++ b/docs/releases/1.9.5.txt @@ -0,0 +1,12 @@ +========================== +Django 1.9.5 release notes +========================== + +*Under development* + +Django 1.9.5 fixes several bugs in 1.9.4. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 0224a2e6c12a..03439d6c053d 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.9.5 1.9.4 1.9.3 1.9.2 @@ -36,6 +37,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.8.12 1.8.11 1.8.10 1.8.9 From 52b06546c07545b592a9205acfc4049292d52766 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 5 Mar 2016 10:02:29 -0500 Subject: [PATCH 478/756] [1.9.x] Fixed typo in docs/releases/1.8.12.txt. Backport of bc0410d98adcb70ad91f37fa9fee9a7ae71faa18 from master --- docs/releases/1.8.12.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.8.12.txt b/docs/releases/1.8.12.txt index 74879bd046f8..332c6091a591 100644 --- a/docs/releases/1.8.12.txt +++ b/docs/releases/1.8.12.txt @@ -4,7 +4,7 @@ Django 1.8.12 release notes *Under development* -Django 1.8.12 several bugs in 1.8.11. +Django 1.8.12 fixes several bugs in 1.8.11. Bugfixes ======== From 72134a04004271a0bd3e853cca3f54f9efc68be2 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 5 Mar 2016 10:58:45 -0500 Subject: [PATCH 479/756] [1.9.x] Refs #26312 -- Reworded tutorial 2 to avoid spelling "error". Backport of 9ed4a788aa8d6ba6a57a2daa15253c3047048dfb from master --- docs/intro/tutorial02.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index 53daae91353a..1d4e16fa4ff1 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -41,7 +41,7 @@ If you are not using SQLite as your database, additional settings such as :setting:`USER`, :setting:`PASSWORD`, and :setting:`HOST` must be added. For more details, see the reference documentation for :setting:`DATABASES`. -.. admonition:: For non-SQLite users +.. admonition:: For databases other than SQLite If you're using a database besides SQLite, make sure you've created a database by this point. Do that with "``CREATE DATABASE database_name;``" From d38da1cc401f61ff06aeda31e2b1b1f3be242efd Mon Sep 17 00:00:00 2001 From: Michal Petrucha Date: Sun, 6 Mar 2016 00:47:26 +0100 Subject: [PATCH 480/756] [1.9.x] Refs #26217 -- Fixed typo in docs/ref/class-based-views/generic-date-based.txt. Backport of 2109975e901440da70e29d0f330a600bc2d37e9a from master --- docs/ref/class-based-views/generic-date-based.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt index 8e93182baefc..0aab34b375d5 100644 --- a/docs/ref/class-based-views/generic-date-based.txt +++ b/docs/ref/class-based-views/generic-date-based.txt @@ -339,7 +339,7 @@ views for displaying drilldown pages for date-based data. * ``'%U'``: Based on the United States week system where the week begins on Sunday. This is the default value. - * ``'%V'``: Similar to ``'%U'``, except it assumes that the week + * ``'%W'``: Similar to ``'%U'``, except it assumes that the week begins on Monday. This is not the same as the ISO 8601 week number. **Example myapp/views.py**:: From 4702c1ac98b284cacffcf5fbb0db9b85f0c2f8ba Mon Sep 17 00:00:00 2001 From: George Marshall Date: Sun, 6 Mar 2016 00:48:06 -0800 Subject: [PATCH 481/756] [1.9.x] Fixed #26331 -- Fixed test function names with typos Backport of 75614f6d4c1a3fe779a75eb3e787452cccd1d814 from master --- tests/cache/tests.py | 2 +- tests/signing/tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cache/tests.py b/tests/cache/tests.py index 7fd9808ca694..b1df2c3eb994 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -1329,7 +1329,7 @@ def test_caches_with_unset_timeout_set_expiring_key(self): self.assertIsNotNone(cache._expire_info[cache_key]) @override_settings(CACHES=NEVER_EXPIRING_CACHES_SETTINGS) - def text_caches_set_with_timeout_as_none_set_non_expiring_key(self): + def test_caches_set_with_timeout_as_none_set_non_expiring_key(self): """Memory caches that have the TIMEOUT parameter set to `None` will set a non expiring key by default. """ diff --git a/tests/signing/tests.py b/tests/signing/tests.py index a8de1b0cbc27..88f82b0e26a1 100644 --- a/tests/signing/tests.py +++ b/tests/signing/tests.py @@ -58,7 +58,7 @@ def test_sign_unsign(self): self.assertNotEqual(force_str(example), signed) self.assertEqual(example, signer.unsign(signed)) - def unsign_detects_tampering(self): + def test_unsign_detects_tampering(self): "unsign should raise an exception if the value has been tampered with" signer = signing.Signer('predictable-secret') value = 'Another string' From 809eb5ddeeca388b2a1d339f7d5ee1f29119ecea Mon Sep 17 00:00:00 2001 From: John-Mark Bell Date: Mon, 7 Mar 2016 12:06:46 +0000 Subject: [PATCH 482/756] [1.9.x] Fixed #26325 -- Made MultiPartParser ignore filenames that normalize to an empty string. Backport of 4b129ac81f4fa38004950d0b307f81d1e9b44af8 from master --- django/http/multipartparser.py | 5 +++-- docs/releases/1.8.12.txt | 4 +++- docs/releases/1.9.5.txt | 4 +++- tests/file_uploads/tests.py | 35 ++++++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py index 375584e4f30f..67d0fc48d50a 100644 --- a/django/http/multipartparser.py +++ b/django/http/multipartparser.py @@ -181,10 +181,11 @@ def parse(self): elif item_type == FILE: # This is a file, use the handler... file_name = disposition.get('filename') + if file_name: + file_name = force_text(file_name, encoding, errors='replace') + file_name = self.IE_sanitize(unescape_entities(file_name)) if not file_name: continue - file_name = force_text(file_name, encoding, errors='replace') - file_name = self.IE_sanitize(unescape_entities(file_name)) content_type, content_type_extra = meta_data.get('content-type', ('', {})) content_type = content_type.strip() diff --git a/docs/releases/1.8.12.txt b/docs/releases/1.8.12.txt index 332c6091a591..26735b827866 100644 --- a/docs/releases/1.8.12.txt +++ b/docs/releases/1.8.12.txt @@ -9,4 +9,6 @@ Django 1.8.12 fixes several bugs in 1.8.11. Bugfixes ======== -* ... +* Made ``MultiPartParser`` ignore filenames that normalize to an empty string + to fix crash in ``MemoryFileUploadHandler`` on specially crafted user input + (:ticket:`26325`). diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt index edff4db6bc4e..211cbba6ba10 100644 --- a/docs/releases/1.9.5.txt +++ b/docs/releases/1.9.5.txt @@ -9,4 +9,6 @@ Django 1.9.5 fixes several bugs in 1.9.4. Bugfixes ======== -* ... +* Made ``MultiPartParser`` ignore filenames that normalize to an empty string + to fix crash in ``MemoryFileUploadHandler`` on specially crafted user input + (:ticket:`26325`). diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py index ccecfce5c17d..09c2c41c8831 100644 --- a/tests/file_uploads/tests.py +++ b/tests/file_uploads/tests.py @@ -179,6 +179,41 @@ def test_unicode_name_rfc2231(self): response = self.client.request(**r) self.assertEqual(response.status_code, 200) + def test_blank_filenames(self): + """ + Receiving file upload when filename is blank (before and after + sanitization) should be okay. + """ + # The second value is normalized to an empty name by + # MultiPartParser.IE_sanitize() + filenames = ['', 'C:\\Windows\\'] + + payload = client.FakePayload() + for i, name in enumerate(filenames): + payload.write('\r\n'.join([ + '--' + client.BOUNDARY, + 'Content-Disposition: form-data; name="file%s"; filename="%s"' % (i, name), + 'Content-Type: application/octet-stream', + '', + 'You got pwnd.\r\n' + ])) + payload.write('\r\n--' + client.BOUNDARY + '--\r\n') + + r = { + 'CONTENT_LENGTH': len(payload), + 'CONTENT_TYPE': client.MULTIPART_CONTENT, + 'PATH_INFO': '/echo/', + 'REQUEST_METHOD': 'POST', + 'wsgi.input': payload, + } + response = self.client.request(**r) + self.assertEqual(response.status_code, 200) + + # Empty filenames should be ignored + received = json.loads(response.content.decode('utf-8')) + for i, name in enumerate(filenames): + self.assertIsNone(received.get('file%s' % i)) + def test_dangerous_file_names(self): """Uploaded file names should be sanitized before ever reaching the view.""" # This test simulates possible directory traversal attacks by a From 76926f343a91e47bf5dc801373e9da2747d08e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Suliga?= Date: Sun, 6 Mar 2016 11:34:23 +0100 Subject: [PATCH 483/756] [1.9.x] Fixed #26332 -- Fixed a race condition in BaseCache.get_or_set(). Backport of 96ec67a7cf89a136e793305343c5bba8521cdb47 from master --- django/core/cache/backends/base.py | 10 +++++----- docs/releases/1.9.5.txt | 4 ++++ tests/cache/tests.py | 9 ++++++++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py index 75a131ac3a07..12351c5bcd5e 100644 --- a/django/core/cache/backends/base.py +++ b/django/core/cache/backends/base.py @@ -154,8 +154,7 @@ def get_or_set(self, key, default=None, timeout=DEFAULT_TIMEOUT, version=None): also be any callable. If timeout is given, that timeout will be used for the key; otherwise the default cache timeout will be used. - Returns the value of the key stored or retrieved on success, - False on error. + Return the value of the key stored or retrieved. """ if default is None: raise ValueError('You need to specify a value.') @@ -163,9 +162,10 @@ def get_or_set(self, key, default=None, timeout=DEFAULT_TIMEOUT, version=None): if val is None: if callable(default): default = default() - val = self.add(key, default, timeout=timeout, version=version) - if val: - return self.get(key, default, version) + self.add(key, default, timeout=timeout, version=version) + # Fetch the value again to avoid a race condition if another caller + # added a value between the first get() and the add() above. + return self.get(key, default, version=version) return val def has_key(self, key, version=None): diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt index 211cbba6ba10..d14b1ac98e62 100644 --- a/docs/releases/1.9.5.txt +++ b/docs/releases/1.9.5.txt @@ -12,3 +12,7 @@ Bugfixes * Made ``MultiPartParser`` ignore filenames that normalize to an empty string to fix crash in ``MemoryFileUploadHandler`` on specially crafted user input (:ticket:`26325`). + +* Fixed a race condition in ``BaseCache.get_or_set()`` (:ticket:`26332`). It + now returns the ``default`` value instead of ``False`` if there's an error + when trying to add the value to the cache. diff --git a/tests/cache/tests.py b/tests/cache/tests.py index b1df2c3eb994..0a76b82bcc1c 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -30,7 +30,7 @@ from django.template.context_processors import csrf from django.template.response import TemplateResponse from django.test import ( - RequestFactory, SimpleTestCase, TestCase, TransactionTestCase, + RequestFactory, SimpleTestCase, TestCase, TransactionTestCase, mock, override_settings, ) from django.test.signals import setting_changed @@ -915,6 +915,13 @@ def test_get_or_set_version(self): self.assertEqual(cache.get_or_set('brian', 1979, version=2), 1979) self.assertIsNone(cache.get('brian', version=3)) + def test_get_or_set_racing(self): + with mock.patch('%s.%s' % (settings.CACHES['default']['BACKEND'], 'add')) as cache_add: + # Simulate cache.add() failing to add a value. In that case, the + # default value should be returned. + cache_add.return_value = False + self.assertEqual(cache.get_or_set('key', 'default'), 'default') + @override_settings(CACHES=caches_setting_for_tests( BACKEND='django.core.cache.backends.db.DatabaseCache', From e2483c9bdf685281221e01cdeb2f3fa52655b701 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 8 Mar 2016 13:15:58 -0500 Subject: [PATCH 484/756] [1.9.x] Fixed a dead link in django/contrib/sitemaps/__init__.py. Backport of 09e5409cb5b7ed427d4c751fbe2594b485794104 from master --- django/contrib/sitemaps/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py index 1b626cd98433..891890e1abe0 100644 --- a/django/contrib/sitemaps/__init__.py +++ b/django/contrib/sitemaps/__init__.py @@ -45,7 +45,7 @@ def ping_google(sitemap_url=None, ping_url=PING_URL): class Sitemap(object): # This limit is defined by Google. See the index documentation at - # http://sitemaps.org/protocol.php#index. + # http://www.sitemaps.org/protocol.html#index. limit = 50000 # If protocol is None, the URLs in the sitemap will use the protocol From 0e80ac467293c659588769ec0587a5bf5f084b49 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 8 Mar 2016 21:28:00 +0100 Subject: [PATCH 485/756] [1.9.x] Fixed #26256 -- Added note about primary key serialization Thanks Sonu kumar for the report and Tim Graham for the review. Backport of c5fda55edc from master. --- docs/topics/serialization.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index 5db8ca569b56..398880ca9398 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -59,7 +59,8 @@ specify a ``fields`` argument to the serializer:: data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name','size')) In this example, only the ``name`` and ``size`` attributes of each model will -be serialized. +be serialized. The primary key is always serialized as the ``pk`` element in the +resulting output; it never appears in the ``fields`` part. .. note:: From 0bae867cd2b25b8e2227bf454f3407cfae5693b5 Mon Sep 17 00:00:00 2001 From: Tim Osborn Date: Wed, 9 Mar 2016 14:45:06 +1100 Subject: [PATCH 486/756] [1.9.x] Fixed indenting in "Serving files in development" code example Backport of 8fb3a2877bac1ecf0e5665389d60eab637d1622e from master --- docs/ref/views.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/views.txt b/docs/ref/views.txt index 133a79814b45..fb3918064dfc 100644 --- a/docs/ref/views.txt +++ b/docs/ref/views.txt @@ -35,7 +35,7 @@ built-in handling for user-uploaded files, but you can have Django serve your url(r'^media/(?P.*)$', serve, { 'document_root': settings.MEDIA_ROOT, }), - ] + ] Note, the snippet assumes your :setting:`MEDIA_URL` has a value of ``'/media/'``. This will call the :func:`~django.views.static.serve` view, From 05ca9a68b59d5d683501e709c1ab629c3705fecb Mon Sep 17 00:00:00 2001 From: girish ramnani Date: Wed, 9 Mar 2016 01:07:34 +0530 Subject: [PATCH 487/756] [1.9.x] Fixed #26302 -- Removed incorrect statement about virtual hosts. Backport of 593ecfe1352bce7daa7a2ca530a46de2f0133319 from master --- docs/howto/deployment/wsgi/modwsgi.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/howto/deployment/wsgi/modwsgi.txt b/docs/howto/deployment/wsgi/modwsgi.txt index 988ff8a389d5..96c7e4829391 100644 --- a/docs/howto/deployment/wsgi/modwsgi.txt +++ b/docs/howto/deployment/wsgi/modwsgi.txt @@ -25,12 +25,11 @@ Basic configuration =================== Once you've got mod_wsgi installed and activated, edit your Apache server's -`httpd.conf`_ file (or a `virtual host`_ file) and add the following. If you -are using a version of Apache older than 2.4, replace ``Require all granted`` -with ``Allow from all`` and also add the line ``Order deny,allow`` above it. +`httpd.conf`_ file and add the following. If you are using a version of Apache +older than 2.4, replace ``Require all granted`` with ``Allow from all`` and +also add the line ``Order deny,allow`` above it. .. _httpd.conf: https://wiki.apache.org/httpd/DistrosDefaultLayout -.. _virtual host: https://httpd.apache.org/docs/current/en/vhosts/ .. code-block:: apache From e8d94cda9b4f30f6724f919882b61eb3279efafc Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 9 Mar 2016 10:00:27 -0500 Subject: [PATCH 488/756] [1.9.x] Wrapped some lines and added links to docs/ref/contrib/sitemaps.txt. Backport of a496d10a8c3b8b3080f0daa7348b33aed790a967 from master --- docs/ref/contrib/sitemaps.txt | 37 ++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index 8d8b411b5331..a5eac078244c 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -123,7 +123,7 @@ Note: corresponding to a sitemap property (:attr:`~Sitemap.location`, :attr:`~Sitemap.lastmod`, :attr:`~Sitemap.changefreq`, and :attr:`~Sitemap.priority`). -* :attr:`~Sitemap.lastmod` should return a Python ``datetime`` object. +* :attr:`~Sitemap.lastmod` should return a :class:`~datetime.datetime`. * There is no :attr:`~Sitemap.location` method in this example, but you can provide it in order to specify the URL for your object. By default, :attr:`~Sitemap.location()` calls ``get_absolute_url()`` on each object @@ -173,11 +173,11 @@ Note: **Optional.** Either a method or attribute. - If it's a method, it should take one argument -- an object as returned by - :attr:`~Sitemap.items()` -- and return that object's last-modified date/time, as a Python - ``datetime.datetime`` object. + If it's a method, it should take one argument -- an object as returned + by :attr:`~Sitemap.items()` -- and return that object's last-modified + date/time as a :class:`~datetime.datetime`. - If it's an attribute, its value should be a Python ``datetime.datetime`` object + If it's an attribute, its value should be a :class:`~datetime.datetime` representing the last-modified date/time for *every* object returned by :attr:`~Sitemap.items()`. @@ -192,13 +192,15 @@ Note: **Optional.** Either a method or attribute. - If it's a method, it should take one argument -- an object as returned by - :attr:`~Sitemap.items()` -- and return that object's change frequency, as a Python string. + If it's a method, it should take one argument -- an object as returned + by :attr:`~Sitemap.items()` -- and return that object's change + frequency as a string. - If it's an attribute, its value should be a string representing the change - frequency of *every* object returned by :attr:`~Sitemap.items()`. + If it's an attribute, its value should be a string representing the + change frequency of *every* object returned by :attr:`~Sitemap.items()`. - Possible values for :attr:`~Sitemap.changefreq`, whether you use a method or attribute, are: + Possible values for :attr:`~Sitemap.changefreq`, whether you use a + method or attribute, are: * ``'always'`` * ``'hourly'`` @@ -212,14 +214,17 @@ Note: **Optional.** Either a method or attribute. - If it's a method, it should take one argument -- an object as returned by - :attr:`~Sitemap.items()` -- and return that object's priority, as either a string or float. + If it's a method, it should take one argument -- an object as returned + by :attr:`~Sitemap.items()` -- and return that object's priority as + either a string or float. - If it's an attribute, its value should be either a string or float representing - the priority of *every* object returned by :attr:`~Sitemap.items()`. + If it's an attribute, its value should be either a string or float + representing the priority of *every* object returned by + :attr:`~Sitemap.items()`. - Example values for :attr:`~Sitemap.priority`: ``0.4``, ``1.0``. The default priority of a - page is ``0.5``. See the `sitemaps.org documentation`_ for more. + Example values for :attr:`~Sitemap.priority`: ``0.4``, ``1.0``. The + default priority of a page is ``0.5``. See the `sitemaps.org + documentation`_ for more. .. _sitemaps.org documentation: http://www.sitemaps.org/protocol.html#prioritydef From 43bb6727d09347f8c4c1a6541e2086a1a69811e1 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 9 Mar 2016 11:21:33 -0500 Subject: [PATCH 489/756] [1.9.x] Fixed #26339 -- Updated Question.was_published_recently() in tutorial 7 to reflect changes in tutorial 5. Backport of 602a38d87e4b0d9c5e43678c33208627ca84ce2a from master --- docs/intro/tutorial07.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/intro/tutorial07.txt b/docs/intro/tutorial07.txt index ecfd90d7f2c4..cc39abf2471b 100644 --- a/docs/intro/tutorial07.txt +++ b/docs/intro/tutorial07.txt @@ -232,7 +232,8 @@ attributes, as follows: class Question(models.Model): # ... def was_published_recently(self): - return self.pub_date >= timezone.now() - datetime.timedelta(days=1) + now = timezone.now() + return now - datetime.timedelta(days=1) <= self.pub_date <= now was_published_recently.admin_order_field = 'pub_date' was_published_recently.boolean = True was_published_recently.short_description = 'Published recently?' From 301edde86d07fe27a4aa002b1546d2a3002190dc Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 9 Mar 2016 12:18:21 -0500 Subject: [PATCH 490/756] [1.9.x] Fixed #26255 -- Fixed orphaned include() reference following tutorial reordering. Backport of 4323676ea5ab6994feb1385522665069d84f397b from master --- docs/intro/tutorial01.txt | 14 ++++++++++++++ docs/intro/tutorial03.txt | 28 +++++----------------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 0bdc593ec93b..de86571df305 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -298,6 +298,20 @@ an :func:`~django.conf.urls.include` in the ``urlpatterns`` list, so you have: url(r'^admin/', admin.site.urls), ] +The :func:`~django.conf.urls.include` function allows referencing other +URLconfs. Note that the regular expressions for the +:func:`~django.conf.urls.include` function doesn't have a ``$`` (end-of-string +match character) but rather a trailing slash. Whenever Django encounters +:func:`~django.conf.urls.include`, it chops off whatever part of the URL +matched up to that point and sends the remaining string to the included URLconf +for further processing. + +The idea behind :func:`~django.conf.urls.include` is to make it easy to +plug-and-play URLs. Since polls are in their own URLconf +(``polls/urls.py``), they can be placed under "/polls/", or under +"/fun_polls/", or under "/content/polls/", or any other path root, and the +app will still work. + .. admonition:: When to use :func:`~django.conf.urls.include()` You should always use ``include()`` when you include other URL patterns. diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index ef2ad2dbef2e..b3973a49e23a 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -106,29 +106,11 @@ placeholder results and voting pages. When somebody requests a page from your website -- say, "/polls/34/", Django will load the ``mysite.urls`` Python module because it's pointed to by the :setting:`ROOT_URLCONF` setting. It finds the variable named ``urlpatterns`` -and traverses the regular expressions in order. The -:func:`~django.conf.urls.include` functions we are using simply reference -other URLconfs. Note that the regular expressions for the -:func:`~django.conf.urls.include` functions don't have a ``$`` (end-of-string -match character) but rather a trailing slash. Whenever Django encounters -:func:`~django.conf.urls.include`, it chops off whatever part of the URL -matched up to that point and sends the remaining string to the included -URLconf for further processing. - -The idea behind :func:`~django.conf.urls.include` is to make it easy to -plug-and-play URLs. Since polls are in their own URLconf -(``polls/urls.py``), they can be placed under "/polls/", or under -"/fun_polls/", or under "/content/polls/", or any other path root, and the -app will still work. - -Here's what happens if a user goes to "/polls/34/" in this system: - -* Django will find the match at ``'^polls/'`` - -* Then, Django will strip off the matching text (``"polls/"``) and send the - remaining text -- ``"34/"`` -- to the 'polls.urls' URLconf for - further processing which matches ``r'^(?P[0-9]+)/$'`` resulting in a - call to the ``detail()`` view like so:: +and traverses the regular expressions in order. After finding the match at +``'^polls/'``, it strips off the matching text (``"polls/"``) and sends the +remaining text -- ``"34/"`` -- to the 'polls.urls' URLconf for further +processing. There it matches ``r'^(?P[0-9]+)/$'``, resulting in a +call to the ``detail()`` view like so:: detail(request=, question_id='34') From 5b6c751230c7ec33eebc564c15ab299ef8f6852a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 10 Mar 2016 09:22:09 -0500 Subject: [PATCH 491/756] [1.9.x] Fixed #26324 -- Fixed DurationField with fractional seconds on SQLite. Backport of 4f0cd0fd162122da96978b357ac9fc9534529410 from master --- django/db/models/fields/__init__.py | 3 ++- docs/releases/1.8.12.txt | 3 +++ docs/releases/1.9.5.txt | 3 +++ tests/model_fields/test_durationfield.py | 6 ++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index df2e3b45de01..cf7a3c32ac5e 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -1665,7 +1665,8 @@ def get_db_prep_value(self, value, connection, prepared=False): return value if value is None: return None - return value.total_seconds() * 1000000 + # Discard any fractional microseconds due to floating point arithmetic. + return int(round(value.total_seconds() * 1000000)) def get_db_converters(self, connection): converters = [] diff --git a/docs/releases/1.8.12.txt b/docs/releases/1.8.12.txt index 26735b827866..0052a90b0d52 100644 --- a/docs/releases/1.8.12.txt +++ b/docs/releases/1.8.12.txt @@ -12,3 +12,6 @@ Bugfixes * Made ``MultiPartParser`` ignore filenames that normalize to an empty string to fix crash in ``MemoryFileUploadHandler`` on specially crafted user input (:ticket:`26325`). + +* Fixed data loss on SQLite where ``DurationField`` values with fractional + seconds could be saved as ``None`` (:ticket:`26324`). diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt index d14b1ac98e62..55fd794d789d 100644 --- a/docs/releases/1.9.5.txt +++ b/docs/releases/1.9.5.txt @@ -16,3 +16,6 @@ Bugfixes * Fixed a race condition in ``BaseCache.get_or_set()`` (:ticket:`26332`). It now returns the ``default`` value instead of ``False`` if there's an error when trying to add the value to the cache. + +* Fixed data loss on SQLite where ``DurationField`` values with fractional + seconds could be saved as ``None`` (:ticket:`26324`). diff --git a/tests/model_fields/test_durationfield.py b/tests/model_fields/test_durationfield.py index fb26baadc9ba..82c5c875bf3a 100644 --- a/tests/model_fields/test_durationfield.py +++ b/tests/model_fields/test_durationfield.py @@ -22,6 +22,12 @@ def test_create_empty(self): loaded = NullDurationModel.objects.get() self.assertEqual(loaded.field, None) + def test_fractional_seconds(self): + value = datetime.timedelta(seconds=2.05) + d = DurationModel.objects.create(field=value) + d.refresh_from_db() + self.assertEqual(d.field, value) + class TestQuerying(TestCase): From c5e258eed30d326374e3b138a64d7cf8831fbe1a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 11 Mar 2016 12:53:12 -0500 Subject: [PATCH 492/756] [1.9.x] Removed unneeded GeoManagers in tests. Backport of 9027fac8414c30db640c4592ee083f12bb8ee5a6 from master --- django/contrib/gis/sitemaps/views.py | 3 +-- tests/gis_tests/geo3d/models.py | 2 -- tests/gis_tests/geoadmin/models.py | 2 -- tests/gis_tests/geoapp/models.py | 8 -------- tests/gis_tests/inspectapp/models.py | 4 ---- tests/gis_tests/layermap/models.py | 2 -- tests/gis_tests/models.py | 1 - tests/gis_tests/relatedapp/models.py | 3 --- tests/gis_tests/relatedapp/tests.py | 6 +++--- 9 files changed, 4 insertions(+), 27 deletions(-) diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index c5fd1aa904e4..948c3ec36c17 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -13,8 +13,7 @@ def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB """ This view generates KML for the given app label, model, and field name. - The model's default manager must be GeoManager, and the field name - must be that of a geographic field. + The field name must be that of a geographic field. """ placemarks = [] try: diff --git a/tests/gis_tests/geo3d/models.py b/tests/gis_tests/geo3d/models.py index fb741ca05c13..e816cb3dad24 100644 --- a/tests/gis_tests/geo3d/models.py +++ b/tests/gis_tests/geo3d/models.py @@ -47,8 +47,6 @@ class Polygon3D(NamedModel): class SimpleModel(models.Model): - objects = models.GeoManager() - class Meta: abstract = True required_db_features = ['gis_enabled'] diff --git a/tests/gis_tests/geoadmin/models.py b/tests/gis_tests/geoadmin/models.py index 99dbae5d918e..2a5bea6e5d29 100644 --- a/tests/gis_tests/geoadmin/models.py +++ b/tests/gis_tests/geoadmin/models.py @@ -10,8 +10,6 @@ class City(models.Model): name = models.CharField(max_length=30) point = models.PointField() - objects = models.GeoManager() - class Meta: app_label = 'geoadmin' required_db_features = ['gis_enabled'] diff --git a/tests/gis_tests/geoapp/models.py b/tests/gis_tests/geoapp/models.py index d6ca4f501075..36f15a021d1e 100644 --- a/tests/gis_tests/geoapp/models.py +++ b/tests/gis_tests/geoapp/models.py @@ -35,10 +35,6 @@ class PennsylvaniaCity(City): county = models.CharField(max_length=30) founded = models.DateTimeField(null=True) - # TODO: This should be implicitly inherited. - - objects = models.GeoManager() - class Meta: app_label = 'geoapp' required_db_features = ['gis_enabled'] @@ -77,8 +73,6 @@ class Meta: class Truth(models.Model): val = models.BooleanField(default=False) - objects = models.GeoManager() - class Meta: required_db_features = ['gis_enabled'] @@ -90,8 +84,6 @@ class Feature(NamedModel): class MinusOneSRID(models.Model): geom = models.PointField(srid=-1) # Minus one SRID. - objects = models.GeoManager() - class Meta: required_db_features = ['gis_enabled'] diff --git a/tests/gis_tests/inspectapp/models.py b/tests/gis_tests/inspectapp/models.py index 75c4bcfa229d..4936a0e704a9 100644 --- a/tests/gis_tests/inspectapp/models.py +++ b/tests/gis_tests/inspectapp/models.py @@ -13,8 +13,6 @@ class AllOGRFields(models.Model): geom = models.PolygonField() point = models.PointField() - objects = models.GeoManager() - class Meta: required_db_features = ['gis_enabled'] @@ -24,7 +22,5 @@ class Fields3D(models.Model): line = models.LineStringField(dim=3) poly = models.PolygonField(dim=3) - objects = models.GeoManager() - class Meta: required_db_features = ['gis_enabled'] diff --git a/tests/gis_tests/layermap/models.py b/tests/gis_tests/layermap/models.py index 3457e0e2d564..1e313049fbf4 100644 --- a/tests/gis_tests/layermap/models.py +++ b/tests/gis_tests/layermap/models.py @@ -7,8 +7,6 @@ class NamedModel(models.Model): name = models.CharField(max_length=25) - objects = models.GeoManager() - class Meta: abstract = True required_db_features = ['gis_enabled'] diff --git a/tests/gis_tests/models.py b/tests/gis_tests/models.py index c1572588456f..aaedf0f8a7b9 100644 --- a/tests/gis_tests/models.py +++ b/tests/gis_tests/models.py @@ -13,7 +13,6 @@ def __init__(self, dim=None, srid=None, geography=None, spatial_index=True, *arg # raised if GDAL isn't installed. models.OriginalRasterField = models.RasterField except ImproperlyConfigured: - models.GeoManager = models.Manager models.GeometryField = DummyField models.LineStringField = DummyField models.MultiPointField = DummyField diff --git a/tests/gis_tests/relatedapp/models.py b/tests/gis_tests/relatedapp/models.py index 606b40c52a6a..0052e1797f59 100644 --- a/tests/gis_tests/relatedapp/models.py +++ b/tests/gis_tests/relatedapp/models.py @@ -33,8 +33,6 @@ def __str__(self): class AugmentedLocation(Location): extra_text = models.TextField(blank=True) - objects = models.GeoManager() - class DirectoryEntry(SimpleModel): listing_text = models.CharField(max_length=50) @@ -55,7 +53,6 @@ def __str__(self): return self.name -# These use the GeoManager but do not have any geographic fields. class Author(SimpleModel): name = models.CharField(max_length=100) dob = models.DateField() diff --git a/tests/gis_tests/relatedapp/tests.py b/tests/gis_tests/relatedapp/tests.py index 19b0f89eab2e..402170d5c382 100644 --- a/tests/gis_tests/relatedapp/tests.py +++ b/tests/gis_tests/relatedapp/tests.py @@ -250,7 +250,7 @@ def test10_combine(self): # ORA-22901: cannot compare nested table or VARRAY or LOB attributes of an object type @no_oracle def test12a_count(self): - "Testing `Count` aggregate use with the `GeoManager` on geo-fields." + "Testing `Count` aggregate on geo-fields." # The City, 'Fort Worth' uses the same location as Dallas. dallas = City.objects.get(name='Dallas') @@ -259,7 +259,7 @@ def test12a_count(self): self.assertEqual(2, loc.num_cities) def test12b_count(self): - "Testing `Count` aggregate use with the `GeoManager` on non geo-fields. See #11087." + "Testing `Count` aggregate on non geo-fields." # Should only be one author (Trevor Paglen) returned by this query, and # the annotation should have 3 for the number of books, see #11087. # Also testing with a values(), see #11489. @@ -284,7 +284,7 @@ def test13c_count(self): # TODO: The phantom model does appear on Oracle. @no_oracle def test13_select_related_null_fk(self): - "Testing `select_related` on a nullable ForeignKey via `GeoManager`. See #11381." + "Testing `select_related` on a nullable ForeignKey." Book.objects.create(title='Without Author') b = Book.objects.select_related('author').get(title='Without Author') # Should be `None`, and not a 'dummy' model. From 16691ed24cce00ac0b90b4ecfcaa31a071727725 Mon Sep 17 00:00:00 2001 From: Noenglish Professorbut Date: Fri, 11 Mar 2016 17:17:01 -0800 Subject: [PATCH 493/756] [1.9.x] Fixed a few docstring typos. Backport of f8d20da0479b88db5bb5a2a30fa769cbf6d0a5bf from master --- django/contrib/gis/db/backends/postgis/operations.py | 2 +- django/contrib/gis/db/backends/spatialite/operations.py | 2 +- django/db/migrations/autodetector.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index d56b7bea91ca..318c88fd1d1e 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -199,7 +199,7 @@ def convert_extent3d(self, box3d, srid): def convert_geom(self, hex, geo_field): """ - Converts the geometry returned from PostGIS aggretates. + Converts the geometry returned from PostGIS aggregates. """ if hex: return Geometry(hex, srid=geo_field.srid) diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 127ea7b15d62..45e7a304fbcf 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -176,7 +176,7 @@ def convert_geom(self, wkt, geo_field): def geo_db_type(self, f): """ - Returns None because geometry columnas are added via the + Returns None because geometry columns are added via the `AddGeometryColumn` stored procedure on SpatiaLite. """ return None diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py index 513bb543d100..62ea80be202c 100644 --- a/django/db/migrations/autodetector.py +++ b/django/db/migrations/autodetector.py @@ -38,7 +38,7 @@ def __init__(self, from_state, to_state, questioner=None): def changes(self, graph, trim_to_apps=None, convert_apps=None, migration_name=None): """ - Main entry point to produce a list of appliable changes. + Main entry point to produce a list of applicable changes. Takes a graph to base names on and an optional set of apps to try and restrict to (restriction is not guaranteed) """ From 421ad283d3a0c9814e6cf5b87b393dce09f59474 Mon Sep 17 00:00:00 2001 From: Duane Hilton Date: Sat, 12 Mar 2016 09:15:57 -0700 Subject: [PATCH 494/756] [1.9.x] Fixed #26239 -- Added a note about how auto_now works with QuerySet.update(). Backport of ca5c05ddbe91f4aae38c4543240dbda5f1fb1db2 from master --- docs/ref/models/fields.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 20c50a0b12b0..9de9206bc8b8 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -490,6 +490,12 @@ optional arguments: for "last-modified" timestamps. Note that the current date is *always* used; it's not just a default value that you can override. + The field is only automatically updated when calling :meth:`Model.save() + `. The field isn't updated when making updates + to other fields in other ways such as :meth:`QuerySet.update() + `, though you can specify a custom + value for the field in an update like that. + .. attribute:: DateField.auto_now_add Automatically set the field to now when the object is first created. Useful From d3c2d82ddef4b2bcf9d3d0b1ac59d7d65f5a7406 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 12 Mar 2016 12:17:21 -0500 Subject: [PATCH 495/756] [1.9.x] Fixed #26345 -- Clarified which RangesFields always return a canonical form. Backport of b3610f38facb33704c1fd77590c6a2fa07c40fa7 from master --- docs/ref/contrib/postgres/fields.txt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt index 20b68b588a61..a70cebea08a3 100644 --- a/docs/ref/contrib/postgres/fields.txt +++ b/docs/ref/contrib/postgres/fields.txt @@ -586,7 +586,7 @@ suitable for. All of the range fields translate to :ref:`psycopg2 Range objects ` in python, but also accept tuples as input if no bounds information is necessary. The default is lower bound included, upper bound -excluded. +excluded; that is, ``[)``. ``IntegerRangeField`` --------------------- @@ -598,6 +598,10 @@ excluded. the database and a :class:`~psycopg2:psycopg2.extras.NumericRange` in Python. + Regardless of the bounds specified when saving the data, PostgreSQL always + returns a range in a canonical form that includes the lower bound and + excludes the upper bound; that is ``[)``. + ``BigIntegerRangeField`` ------------------------ @@ -608,6 +612,10 @@ excluded. in the database and a :class:`~psycopg2:psycopg2.extras.NumericRange` in Python. + Regardless of the bounds specified when saving the data, PostgreSQL always + returns a range in a canonical form that includes the lower bound and + excludes the upper bound; that is ``[)``. + ``FloatRangeField`` ------------------- @@ -636,6 +644,10 @@ excluded. :class:`~django.db.models.DateField`. Represented by a ``daterange`` in the database and a :class:`~psycopg2:psycopg2.extras.DateRange` in Python. + Regardless of the bounds specified when saving the data, PostgreSQL always + returns a range in a canonical form that includes the lower bound and + excludes the upper bound; that is ``[)``. + Querying Range Fields --------------------- From 9ed08da6c6bd45e18c743c80a086403a5d57a308 Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Sun, 13 Mar 2016 19:47:58 +0100 Subject: [PATCH 496/756] [1.9.x] Fixed typos in docs. Backport of 402da9ab7b2bae807b7ea30c23ef524b0aeb1903 from master --- docs/internals/howto-release-django.txt | 2 +- docs/ref/utils.txt | 2 +- docs/releases/1.0.txt | 2 +- docs/releases/security.txt | 2 +- docs/topics/conditional-view-processing.txt | 2 +- docs/topics/performance.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/internals/howto-release-django.txt b/docs/internals/howto-release-django.txt index 0da90ae374b2..7edd69211f2e 100644 --- a/docs/internals/howto-release-django.txt +++ b/docs/internals/howto-release-django.txt @@ -368,7 +368,7 @@ You're almost done! All that's left to do now is: New stable branch tasks ======================= -There are several items to do in the time following a the creation of a new +There are several items to do in the time following the creation of a new stable branch (often following an alpha release). Some of these tasks don't need to be done by the releaser. diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index e85d69d46230..a763fdc63939 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -157,7 +157,7 @@ The functions defined in this module share the following properties: decorate methods or classes; in the latter case, ``name`` is the name of the method to be decorated and is required. - ``decorator`` may also be a a list or tuple of functions. They are wrapped + ``decorator`` may also be a list or tuple of functions. They are wrapped in reverse order so that the call order is the order in which the functions appear in the list/tuple. diff --git a/docs/releases/1.0.txt b/docs/releases/1.0.txt index 162caaa3a961..3d0043a79770 100644 --- a/docs/releases/1.0.txt +++ b/docs/releases/1.0.txt @@ -5,7 +5,7 @@ Django 1.0 release notes Welcome to Django 1.0! We've been looking forward to this moment for over three years, and it's finally -here. Django 1.0 represents a the largest milestone in Django's development to +here. Django 1.0 represents the largest milestone in Django's development to date: a Web framework that a group of perfectionists can truly be proud of. Django 1.0 represents over three years of community development as an Open diff --git a/docs/releases/security.txt b/docs/releases/security.txt index c8d29ef7ba6b..58e16748f644 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -202,7 +202,7 @@ Versions affected September 9, 2011 - CVE-2011-4137 --------------------------------- -`CVE-2011-4137 `_: Denial-of-service via via ``URLField.verify_exists``. `Full description `__ +`CVE-2011-4137 `_: Denial-of-service via ``URLField.verify_exists``. `Full description `__ Versions affected ~~~~~~~~~~~~~~~~~ diff --git a/docs/topics/conditional-view-processing.txt b/docs/topics/conditional-view-processing.txt index 1cc15572f3e3..96c21e15dd9e 100644 --- a/docs/topics/conditional-view-processing.txt +++ b/docs/topics/conditional-view-processing.txt @@ -172,7 +172,7 @@ For example, consider the following exchange between the client and server: the version it is trying to update. 4. Server checks to see if the resource has changed, by computing the ETag the same way it does for a ``GET`` request (using the same function). - If the resource *has* changed, it will return a 412 status code code, + If the resource *has* changed, it will return a 412 status code, meaning "precondition failed". 5. Client sends a ``GET`` request to ``/foo/``, after receiving a 412 response, to retrieve an updated version of the content before updating diff --git a/docs/topics/performance.txt b/docs/topics/performance.txt index c529a0530bb2..27949193776b 100644 --- a/docs/topics/performance.txt +++ b/docs/topics/performance.txt @@ -171,7 +171,7 @@ final steps towards producing well-performing code, not a shortcut. :class:`~django.utils.functional.cached_property` ------------------------------------------------- -It's common to have to call a class instances's method more than once. If +It's common to have to call a class instance's method more than once. If that function is expensive, then doing so can be wasteful. Using the :class:`~django.utils.functional.cached_property` decorator saves the From 7b2ee75745a5304b222b71b3273ff14a40da47f4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 12 Mar 2016 17:44:08 -0500 Subject: [PATCH 497/756] [1.9.x] Fixed #26294 -- Clarified call_command()'s handling of args and options. Backport of 5695c142d282f4681a8d43bb55ec49f11f3fc40e from master --- docs/ref/django-admin.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index e9f3c1659d8f..2144bba219a9 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1749,10 +1749,15 @@ To call a management command from code use ``call_command``. the name of the command to call. ``*args`` - a list of arguments accepted by the command. + a list of arguments accepted by the command. Arguments are passed to the + argument parser, so you can use the same style as you would on the command + line. For example, ``call_command('flush', 'verbosity=0')``. ``**options`` - named options accepted on the command-line. + named options accepted on the command-line. Options are passed to the command + without triggering the argument parser, which means you'll need to pass the + correct type. For example, ``call_command('flush', verbosity=0)`` (zero must + be an integer rather than a string). Examples:: From 27bed94e9cb4eb712ddda465e6bbd3aca5f15ce4 Mon Sep 17 00:00:00 2001 From: Moritz Sichert Date: Sun, 13 Mar 2016 13:31:11 +0100 Subject: [PATCH 498/756] [1.9.x] Fixed #25804 -- Documented additions to Jinja2 context. Backport of 6aef986cdbff72809b39880132959ef74827492f from master --- docs/topics/templates.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt index 5e5dee516802..77c3b4ce4c8b 100644 --- a/docs/topics/templates.txt +++ b/docs/topics/templates.txt @@ -456,10 +456,13 @@ adds defaults that differ from Jinja2's for a few options: * ``'auto_reload'``: ``settings.DEBUG`` * ``'undefined'``: ``DebugUndefined if settings.DEBUG else Undefined`` -The default configuration is purposefully kept to a minimum. The ``Jinja2`` -backend doesn't create a Django-flavored environment. It doesn't know about -Django context processors, filters, and tags. In order to use Django-specific -APIs, you must configure them into the environment. +The default configuration is purposefully kept to a minimum. If a template is +rendered with a request (e.g. when using :py:func:`~django.shortcuts.render`), +the ``Jinja2`` backend adds the globals ``request``, ``csrf_input``, and +``csrf_token`` to the context. Apart from that, this backend doesn't create a +Django-flavored environment. It doesn't know about Django context processors, +filters, and tags. In order to use Django-specific APIs, you must configure +them into the environment. For example, you can create ``myproject/jinja2.py`` with this content:: From bf7a35c329858d1309e70f421a308f8aced1c444 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 13 Mar 2016 17:19:46 -0700 Subject: [PATCH 499/756] [1.9.x] Fixed test_dumpdata_progressbar to use the instantiated StringIO object. Backport of cacc7e85e17b3d00e7ed856d8bbadb8f870bb5d6 from master --- tests/fixtures/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py index 86164e03bcb6..31b6f16c8a07 100644 --- a/tests/fixtures/tests.py +++ b/tests/fixtures/tests.py @@ -478,6 +478,7 @@ def test_dumpdata_progressbar(self): options['verbosity'] = 0 new_io = six.StringIO() new_io.isatty = lambda: True + options.update({'stdout': new_io, 'stderr': new_io}) management.call_command('dumpdata', 'fixtures', **options) self.assertEqual(new_io.getvalue(), '') From b50e4ffe7eb9d8797d38f28ae3f51fb443056214 Mon Sep 17 00:00:00 2001 From: Adam Alton Date: Mon, 14 Mar 2016 17:59:19 +0000 Subject: [PATCH 500/756] [1.9.x] Removed unnecessary filter kwarg from .get() in a test. Backport of 38086c83aca881aa72bc2eba1e6eadaa76529ed0 from master --- tests/model_inheritance/tests.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/model_inheritance/tests.py b/tests/model_inheritance/tests.py index 3c4f71d93432..1869864a9a6f 100644 --- a/tests/model_inheritance/tests.py +++ b/tests/model_inheritance/tests.py @@ -226,10 +226,8 @@ def test_inherited_does_not_exist_exception(self): def test_inherited_multiple_objects_returned_exception(self): # MultipleObjectsReturned is also inherited. - self.assertRaises( - Place.MultipleObjectsReturned, - Restaurant.objects.get, id__lt=12321 - ) + with self.assertRaises(Place.MultipleObjectsReturned): + Restaurant.objects.get() def test_related_objects_for_inherited_models(self): # Related objects work just as they normally do. From c6424efbc6114eeefe7ec7545de7e127ed189e92 Mon Sep 17 00:00:00 2001 From: Vincenzo Pandolfo Date: Mon, 14 Mar 2016 20:21:05 +0000 Subject: [PATCH 501/756] [1.9.x] Fixed #26334 -- Removed whitespace stripping from contrib.auth password fields. Backport of d0fe6c915665fa3220e84bd691ba7002a357e5c5 from master --- django/contrib/auth/forms.py | 9 +++++- docs/releases/1.9.5.txt | 6 ++++ tests/auth_tests/test_forms.py | 56 ++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index b1cedc916c01..380dc2b001e0 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -69,9 +69,11 @@ class UserCreationForm(forms.ModelForm): 'password_mismatch': _("The two password fields didn't match."), } password1 = forms.CharField(label=_("Password"), + strip=False, widget=forms.PasswordInput) password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput, + strip=False, help_text=_("Enter the same password as before, for verification.")) class Meta: @@ -127,7 +129,7 @@ class AuthenticationForm(forms.Form): username/password logins. """ username = forms.CharField(max_length=254) - password = forms.CharField(label=_("Password"), widget=forms.PasswordInput) + password = forms.CharField(label=_("Password"), strip=False, widget=forms.PasswordInput) error_messages = { 'invalid_login': _("Please enter a correct %(username)s and password. " @@ -269,8 +271,10 @@ class SetPasswordForm(forms.Form): } new_password1 = forms.CharField(label=_("New password"), widget=forms.PasswordInput, + strip=False, help_text=password_validation.password_validators_help_text_html()) new_password2 = forms.CharField(label=_("New password confirmation"), + strip=False, widget=forms.PasswordInput) def __init__(self, user, *args, **kwargs): @@ -307,6 +311,7 @@ class PasswordChangeForm(SetPasswordForm): "Please enter it again."), }) old_password = forms.CharField(label=_("Old password"), + strip=False, widget=forms.PasswordInput) field_order = ['old_password', 'new_password1', 'new_password2'] @@ -335,11 +340,13 @@ class AdminPasswordChangeForm(forms.Form): password1 = forms.CharField( label=_("Password"), widget=forms.PasswordInput, + strip=False, help_text=password_validation.password_validators_help_text_html(), ) password2 = forms.CharField( label=_("Password (again)"), widget=forms.PasswordInput, + strip=False, help_text=_("Enter the same password as before, for verification."), ) diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt index 55fd794d789d..7e8efb6fec8c 100644 --- a/docs/releases/1.9.5.txt +++ b/docs/releases/1.9.5.txt @@ -19,3 +19,9 @@ Bugfixes * Fixed data loss on SQLite where ``DurationField`` values with fractional seconds could be saved as ``None`` (:ticket:`26324`). + +* The forms in ``contrib.auth`` no longer strip trailing and leading whitespace + from the password fields (:ticket:`26334`). The change requires users who set + their password to something with such whitespace after a site updated to + Django 1.9 to reset their password. It provides backwards-compatibility for + earlier versions of Django. diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py index 918fe3801a78..e902cdd339b2 100644 --- a/tests/auth_tests/test_forms.py +++ b/tests/auth_tests/test_forms.py @@ -169,6 +169,17 @@ class Meta(UserCreationForm.Meta): form = CustomUserCreationForm(data) self.assertTrue(form.is_valid()) + def test_password_whitespace_not_stripped(self): + data = { + 'username': 'testuser', + 'password1': ' testpassword ', + 'password2': ' testpassword ', + } + form = UserCreationForm(data) + self.assertTrue(form.is_valid()) + self.assertEqual(form.cleaned_data['password1'], data['password1']) + self.assertEqual(form.cleaned_data['password2'], data['password2']) + @override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher']) class AuthenticationFormTest(TestDataMixin, TestCase): @@ -279,6 +290,15 @@ class CustomAuthenticationForm(AuthenticationForm): form = CustomAuthenticationForm() self.assertEqual(form.fields['username'].label, "") + def test_password_whitespace_not_stripped(self): + data = { + 'username': 'testuser', + 'password': ' pass ', + } + form = AuthenticationForm(None, data) + form.is_valid() # Not necessary to have valid credentails for the test. + self.assertEqual(form.cleaned_data['password'], data['password']) + @override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher']) class SetPasswordFormTest(TestDataMixin, TestCase): @@ -330,6 +350,17 @@ def test_validates_password(self): form["new_password2"].errors ) + def test_password_whitespace_not_stripped(self): + user = User.objects.get(username='testclient') + data = { + 'new_password1': ' password ', + 'new_password2': ' password ', + } + form = SetPasswordForm(user, data) + self.assertTrue(form.is_valid()) + self.assertEqual(form.cleaned_data['new_password1'], data['new_password1']) + self.assertEqual(form.cleaned_data['new_password2'], data['new_password2']) + @override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher']) class PasswordChangeFormTest(TestDataMixin, TestCase): @@ -381,6 +412,20 @@ def test_field_order(self): self.assertEqual(list(PasswordChangeForm(user, {}).fields), ['old_password', 'new_password1', 'new_password2']) + def test_password_whitespace_not_stripped(self): + user = User.objects.get(username='testclient') + user.set_password(' oldpassword ') + data = { + 'old_password': ' oldpassword ', + 'new_password1': ' pass ', + 'new_password2': ' pass ', + } + form = PasswordChangeForm(user, data) + self.assertTrue(form.is_valid()) + self.assertEqual(form.cleaned_data['old_password'], data['old_password']) + self.assertEqual(form.cleaned_data['new_password1'], data['new_password1']) + self.assertEqual(form.cleaned_data['new_password2'], data['new_password2']) + @override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher']) class UserChangeFormTest(TestDataMixin, TestCase): @@ -673,3 +718,14 @@ def test_success(self, password_changed): self.assertEqual(password_changed.call_count, 0) form.save() self.assertEqual(password_changed.call_count, 1) + + def test_password_whitespace_not_stripped(self): + user = User.objects.get(username='testclient') + data = { + 'password1': ' pass ', + 'password2': ' pass ', + } + form = AdminPasswordChangeForm(user, data) + self.assertTrue(form.is_valid()) + self.assertEqual(form.cleaned_data['password1'], data['password1']) + self.assertEqual(form.cleaned_data['password2'], data['password2']) From 0dc3822f56a0db83f9fc0b1ed320bbeb477eca63 Mon Sep 17 00:00:00 2001 From: Duane Hilton Date: Mon, 14 Mar 2016 20:40:02 -0600 Subject: [PATCH 502/756] [1.9.x] Fixed #26290 -- Documented that a QuerySet for pagination should be ordered. Backport of f8b23e52e86307428da2cf928bf4f1d9fdbd2694 from master --- docs/topics/pagination.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/topics/pagination.txt b/docs/topics/pagination.txt index 7fb31324fb9f..a45b8fabd607 100644 --- a/docs/topics/pagination.txt +++ b/docs/topics/pagination.txt @@ -140,8 +140,11 @@ Required arguments ------------------ ``object_list`` - A list, tuple, Django ``QuerySet``, or other sliceable object with a - ``count()`` or ``__len__()`` method. + A list, tuple, ``QuerySet``, or other sliceable object with a ``count()`` + or ``__len__()`` method. For consistent pagination, ``QuerySet``\s should + be ordered, e.g. with an :meth:`~django.db.models.query.QuerySet.order_by` + clause or with a default :attr:`~django.db.models.Options.ordering` on the + model. ``per_page`` The maximum number of items to include on a page, not including orphans From 2ed2b508ceac3d5d7d98483fca96cfdbb8a9d98d Mon Sep 17 00:00:00 2001 From: Andrew Abraham Date: Tue, 15 Mar 2016 13:30:41 -0700 Subject: [PATCH 503/756] [1.9.x] Fixed DiscoverRunner failfast parameter default in docs. Backport of f2d9caa625084b0b96d60153f6b26ba43a1ab835 from master --- docs/topics/testing/advanced.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt index 5c84393182eb..92d1b61d21bb 100644 --- a/docs/topics/testing/advanced.txt +++ b/docs/topics/testing/advanced.txt @@ -372,7 +372,7 @@ behavior. This class defines the ``run_tests()`` entry point, plus a selection of other methods that are used to by ``run_tests()`` to set up, execute and tear down the test suite. -.. class:: DiscoverRunner(pattern='test*.py', top_level=None, verbosity=1, interactive=True, failfast=True, keepdb=False, reverse=False, debug_sql=False, **kwargs) +.. class:: DiscoverRunner(pattern='test*.py', top_level=None, verbosity=1, interactive=True, failfast=False, keepdb=False, reverse=False, debug_sql=False, **kwargs) ``DiscoverRunner`` will search for tests in any file matching ``pattern``. From f49cfb76c7b24a4fa5f8ac36dfa0a82ab66336c5 Mon Sep 17 00:00:00 2001 From: Alex Hill Date: Wed, 9 Mar 2016 11:35:39 +0800 Subject: [PATCH 504/756] [1.9.x] Fixed #26306 -- Fixed memory leak in cached template loader. Backport of ecb59cc6579402b68ddfd4499bf30edacf5963be from master --- django/template/backends/django.py | 17 +++++++++-- django/template/loaders/cached.py | 27 +++++++++++++++-- docs/releases/1.9.5.txt | 2 ++ tests/template_tests/test_loaders.py | 43 +++++++++++++++++++++++++--- 4 files changed, 79 insertions(+), 10 deletions(-) diff --git a/django/template/backends/django.py b/django/template/backends/django.py index deca90c28e68..70b39b543e95 100644 --- a/django/template/backends/django.py +++ b/django/template/backends/django.py @@ -97,13 +97,24 @@ def render(self, context=None, request=None): reraise(exc, self.backend) -def reraise(exc, backend): +def copy_exception(exc, backend=None): """ - Reraise TemplateDoesNotExist while maintaining template debug information. + Create a new TemplateDoesNotExist. Preserve its declared attributes and + template debug data but discard __traceback__, __context__, and __cause__ + to make this object suitable for keeping around (in a cache, for example). """ - new = exc.__class__(*exc.args, tried=exc.tried, backend=backend) + backend = backend or exc.backend + new = exc.__class__(*exc.args, tried=exc.tried, backend=backend, chain=exc.chain) if hasattr(exc, 'template_debug'): new.template_debug = exc.template_debug + return new + + +def reraise(exc, backend): + """ + Reraise TemplateDoesNotExist while maintaining template debug information. + """ + new = copy_exception(exc, backend) six.reraise(exc.__class__, new, sys.exc_info()[2]) diff --git a/django/template/loaders/cached.py b/django/template/loaders/cached.py index 5bf5c10586c3..70c55feb5142 100644 --- a/django/template/loaders/cached.py +++ b/django/template/loaders/cached.py @@ -7,6 +7,7 @@ import warnings from django.template import Origin, Template, TemplateDoesNotExist +from django.template.backends.django import copy_exception from django.utils.deprecation import RemovedInDjango20Warning from django.utils.encoding import force_bytes from django.utils.inspect import func_supports_parameter @@ -27,11 +28,31 @@ def get_contents(self, origin): return origin.loader.get_contents(origin) def get_template(self, template_name, template_dirs=None, skip=None): + """ + Perform the caching that gives this loader its name. Often many of the + templates attempted will be missing, so memory use is of concern here. + To keep it in check, caching behavior is a little complicated when a + template is not found. See ticket #26306 for more details. + + With template debugging disabled, cache the TemplateDoesNotExist class + for every missing template and raise a new instance of it after + fetching it from the cache. + + With template debugging enabled, a unique TemplateDoesNotExist object + is cached for each missing template to preserve debug data. When + raising an exception, Python sets __traceback__, __context__, and + __cause__ attributes on it. Those attributes can contain references to + all sorts of objects up the call chain and caching them creates a + memory leak. Thus, unraised copies of the exceptions are cached and + copies of those copies are raised after they're fetched from the cache. + """ key = self.cache_key(template_name, template_dirs, skip) cached = self.get_template_cache.get(key) if cached: - if isinstance(cached, TemplateDoesNotExist): - raise cached + if isinstance(cached, type) and issubclass(cached, TemplateDoesNotExist): + raise cached(template_name) + elif isinstance(cached, TemplateDoesNotExist): + raise copy_exception(cached) return cached try: @@ -39,7 +60,7 @@ def get_template(self, template_name, template_dirs=None, skip=None): template_name, template_dirs, skip, ) except TemplateDoesNotExist as e: - self.get_template_cache[key] = e + self.get_template_cache[key] = copy_exception(e) if self.engine.debug else TemplateDoesNotExist raise else: self.get_template_cache[key] = template diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt index 7e8efb6fec8c..d74a2d9abefe 100644 --- a/docs/releases/1.9.5.txt +++ b/docs/releases/1.9.5.txt @@ -25,3 +25,5 @@ Bugfixes their password to something with such whitespace after a site updated to Django 1.9 to reset their password. It provides backwards-compatibility for earlier versions of Django. + +* Fixed a memory leak in the cached template loader (:ticket:`26306`). diff --git a/tests/template_tests/test_loaders.py b/tests/template_tests/test_loaders.py index 11f20c6debff..35921e047204 100644 --- a/tests/template_tests/test_loaders.py +++ b/tests/template_tests/test_loaders.py @@ -49,11 +49,46 @@ def test_get_template(self): self.assertEqual(template.origin.template_name, 'index.html') self.assertEqual(template.origin.loader, self.engine.template_loaders[0].loaders[0]) - def test_get_template_missing(self): + def test_get_template_missing_debug_off(self): + """ + With template debugging disabled, the raw TemplateDoesNotExist class + should be cached when a template is missing. See ticket #26306 and + docstrings in the cached loader for details. + """ + self.engine.debug = False with self.assertRaises(TemplateDoesNotExist): - self.engine.get_template('doesnotexist.html') - e = self.engine.template_loaders[0].get_template_cache['doesnotexist.html'] - self.assertEqual(e.args[0], 'doesnotexist.html') + self.engine.get_template('prod-template-missing.html') + e = self.engine.template_loaders[0].get_template_cache['prod-template-missing.html'] + self.assertEqual(e, TemplateDoesNotExist) + + def test_get_template_missing_debug_on(self): + """ + With template debugging enabled, a TemplateDoesNotExist instance + should be cached when a template is missing. + """ + self.engine.debug = True + with self.assertRaises(TemplateDoesNotExist): + self.engine.get_template('debug-template-missing.html') + e = self.engine.template_loaders[0].get_template_cache['debug-template-missing.html'] + self.assertIsInstance(e, TemplateDoesNotExist) + self.assertEqual(e.args[0], 'debug-template-missing.html') + + @unittest.skipIf(six.PY2, "Python 2 doesn't set extra exception attributes") + def test_cached_exception_no_traceback(self): + """ + When a TemplateDoesNotExist instance is cached, the cached instance + should not contain the __traceback__, __context__, or __cause__ + attributes that Python sets when raising exceptions. + """ + self.engine.debug = True + with self.assertRaises(TemplateDoesNotExist): + self.engine.get_template('no-traceback-in-cache.html') + e = self.engine.template_loaders[0].get_template_cache['no-traceback-in-cache.html'] + + error_msg = "Cached TemplateDoesNotExist must not have been thrown." + self.assertIsNone(e.__traceback__, error_msg) + self.assertIsNone(e.__context__, error_msg) + self.assertIsNone(e.__cause__, error_msg) @ignore_warnings(category=RemovedInDjango20Warning) def test_load_template(self): From b4bb2ad13d178dd2db484c7721fd47aa3d285908 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Mon, 14 Mar 2016 05:17:05 +0200 Subject: [PATCH 505/756] [1.9.x] Fixed #26297 -- Fixed `collectstatic --clear` crash if storage doesn't implement path(). Backport of 28bcff82c5ed4694f4761c303294ffafbd7096ce from master --- .../management/commands/collectstatic.py | 14 ++++--- docs/releases/1.9.5.txt | 3 ++ tests/staticfiles_tests/storage.py | 37 +++++++++++++++++++ tests/staticfiles_tests/test_management.py | 5 +++ 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index e6b6198952f1..793c211f42e9 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -218,12 +218,16 @@ def clear_dir(self, path): smart_text(fpath), level=1) else: self.log("Deleting '%s'" % smart_text(fpath), level=1) - full_path = self.storage.path(fpath) - if not os.path.exists(full_path) and os.path.lexists(full_path): - # Delete broken symlinks - os.unlink(full_path) - else: + try: + full_path = self.storage.path(fpath) + except NotImplementedError: self.storage.delete(fpath) + else: + if not os.path.exists(full_path) and os.path.lexists(full_path): + # Delete broken symlinks + os.unlink(full_path) + else: + self.storage.delete(fpath) for d in dirs: self.clear_dir(os.path.join(path, d)) diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt index d74a2d9abefe..98dd1799a80d 100644 --- a/docs/releases/1.9.5.txt +++ b/docs/releases/1.9.5.txt @@ -27,3 +27,6 @@ Bugfixes earlier versions of Django. * Fixed a memory leak in the cached template loader (:ticket:`26306`). + +* Fixed a regression that caused ``collectstatic --clear`` to fail if the + storage doesn't implement ``path()`` (:ticket:`26297`). diff --git a/tests/staticfiles_tests/storage.py b/tests/staticfiles_tests/storage.py index fe4811a49573..925f4b7fed49 100644 --- a/tests/staticfiles_tests/storage.py +++ b/tests/staticfiles_tests/storage.py @@ -1,5 +1,8 @@ +import errno +import os from datetime import datetime +from django.conf import settings from django.contrib.staticfiles.storage import CachedStaticFilesStorage from django.core.files import storage @@ -22,6 +25,40 @@ def modified_time(self, name): return datetime.date(1970, 1, 1) +class PathNotImplementedStorage(storage.Storage): + + def _save(self, name, content): + return 'dummy' + + def _path(self, name): + return os.path.join(settings.STATIC_ROOT, name) + + def exists(self, name): + return os.path.exists(self._path(name)) + + def listdir(self, path): + path = self._path(path) + directories, files = [], [] + for entry in os.listdir(path): + if os.path.isdir(os.path.join(path, entry)): + directories.append(entry) + else: + files.append(entry) + return directories, files + + def delete(self, name): + name = self._path(name) + if os.path.exists(name): + try: + os.remove(name) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + def path(self, name): + raise NotImplementedError + + class SimpleCachedStaticFilesStorage(CachedStaticFilesStorage): def file_hash(self, name, content=None): diff --git a/tests/staticfiles_tests/test_management.py b/tests/staticfiles_tests/test_management.py index 94166f9888d2..b6c2a9fe87c0 100644 --- a/tests/staticfiles_tests/test_management.py +++ b/tests/staticfiles_tests/test_management.py @@ -166,6 +166,11 @@ def test_dir_not_exists(self, **kwargs): shutil.rmtree(six.text_type(settings.STATIC_ROOT)) super(TestCollectionClear, self).run_collectstatic(clear=True) + @override_settings(STATICFILES_STORAGE='staticfiles_tests.storage.PathNotImplementedStorage') + def test_handle_path_notimplemented(self): + self.run_collectstatic() + self.assertFileNotFound('cleared.txt') + class TestCollectionExcludeNoDefaultIgnore(CollectionTestCase, TestDefaults): """ From f6f24af1f43b722dda9818297c1c75bcabc8f5aa Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 18 Mar 2016 11:37:20 -0400 Subject: [PATCH 506/756] [1.9.x] Fixed #26375 -- Used a more generic name in a reusable template example. Backport of 1d0abeaf757518808aabe9c85a2beaaa340fbc43 from master --- docs/ref/class-based-views/generic-editing.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt index 3def731ee73d..3bcd9fea99dc 100644 --- a/docs/ref/class-based-views/generic-editing.txt +++ b/docs/ref/class-based-views/generic-editing.txt @@ -132,7 +132,7 @@ editing content:
    {% csrf_token %} {{ form.as_p }} - +
    ``UpdateView`` From 5f5d654609c7b9b70438d684bc01ac6cbfc656a2 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 18 Mar 2016 10:09:19 +0100 Subject: [PATCH 507/756] [1.9.x] Added missing stacklevel for management command deprecation warning --- django/core/management/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/core/management/base.py b/django/core/management/base.py index a2fe1f915d59..b9cc2c029ba4 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -262,7 +262,7 @@ def store_as_int(option, opt_str, value, parser): # Backwards compatibility: use deprecated optparse module warnings.warn("OptionParser usage for Django management commands " "is deprecated, use ArgumentParser instead", - RemovedInDjango110Warning) + RemovedInDjango110Warning, stacklevel=3) parser = OptionParser(prog=prog_name, usage=self.usage(subcommand), version=self.get_version()) From 2d178b3d2cbed7d7a21cf0de3bd9da9330c90755 Mon Sep 17 00:00:00 2001 From: Amine Date: Sat, 19 Mar 2016 20:34:43 +0100 Subject: [PATCH 508/756] [1.9.x] Fixed a broken link in docs/internals/contributing/writing-documentation.txt. Backport of 8b5a4fa941ba6e308b5ebd831b7953440fc0ee9b from master --- docs/internals/contributing/writing-documentation.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt index 6c4e583866c1..8cb50392f2ff 100644 --- a/docs/internals/contributing/writing-documentation.txt +++ b/docs/internals/contributing/writing-documentation.txt @@ -220,10 +220,8 @@ documentation: Django-specific markup ====================== -Besides the `Sphinx built-in markup`__, Django's docs defines some extra -description units: - -__ http://sphinx-doc.org/markup/ +Besides the :ref:`Sphinx built-in markup `, Django's +docs defines some extra description units: * Settings:: From 4e8c26531925227872ce29bc0cdee3329be4a216 Mon Sep 17 00:00:00 2001 From: Jason Parrott Date: Thu, 17 Mar 2016 22:45:00 +0900 Subject: [PATCH 509/756] [1.9.x] Fixed #26373 -- Fixed reverse lookup crash with a ForeignKey to_field in a subquery. Backport of 4c1c93032f4a015cbb4b33958603d18ac43515b4 from master --- django/db/models/query.py | 2 +- docs/releases/1.9.5.txt | 4 ++++ tests/queries/tests.py | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index cb02085cc577..3787e34abc1c 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1113,7 +1113,7 @@ def _prepare(self, field): # if they are set up to select only a single field. if len(self._fields or self.model._meta.concrete_fields) > 1: raise TypeError('Cannot use multi-field values as a filter value.') - else: + elif self.model != field.model: # If the query is used as a subquery for a ForeignKey with non-pk # target field, make sure to select the target field in the subquery. foreign_fields = getattr(field, 'foreign_related_fields', ()) diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt index 98dd1799a80d..37157b3e5630 100644 --- a/docs/releases/1.9.5.txt +++ b/docs/releases/1.9.5.txt @@ -30,3 +30,7 @@ Bugfixes * Fixed a regression that caused ``collectstatic --clear`` to fail if the storage doesn't implement ``path()`` (:ticket:`26297`). + +* Fixed a crash when using a reverse lookup with a subquery when a + ``ForeignKey`` has a ``to_field`` set to something other than the primary key + (:ticket:`26373`). diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 7c306c9f15b1..05564a590921 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -2474,6 +2474,10 @@ def test_in_subquery(self): set(Eaten.objects.filter(food__in=Food.objects.filter(name='apple').values('eaten__meal'))), set() ) + self.assertEqual( + set(Food.objects.filter(eaten__in=Eaten.objects.filter(meal='lunch'))), + {apple} + ) def test_reverse_in(self): apple = Food.objects.create(name="apple") From 379bab354472b882fed250d0dceb73644d52e220 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 17 Mar 2016 12:45:23 -0400 Subject: [PATCH 510/756] [1.9.x] Fixed #26265 -- Clarified RadioSelect container's HTML id. Backport of 53e8ab580f7c0fcfc589ba0b1b6cc2556080e0b2 from master --- docs/ref/forms/api.txt | 5 +++++ docs/ref/forms/widgets.txt | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index 072525ff53c3..94ecd2b5ccfe 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -835,6 +835,11 @@ The field-specific output honors the form object's ``auto_id`` setting:: Attributes of ``BoundField`` ---------------------------- +.. attribute:: BoundField.auto_id + + The HTML ID attribute for this ``BoundField``. Returns an empty string + if :attr:`Form.auto_id` is ``False``. + .. attribute:: BoundField.data This property returns the data for this :class:`~django.forms.BoundField` diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 9504d64f14e2..ba4c1becf90c 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -682,8 +682,8 @@ Selector and checkbox widgets simply includes ``{{ myform.beatles }}`` -- they'll be output in a ``
    '); +}; + +(function(){ + var newOptions={}; + //http://stackoverflow.com/a/2954896 + var toArray =Array.prototype.slice; + var scripts = toArray.call(document.scripts); + toArray.call(scripts[scripts.length - 1].attributes) + .forEach(function(es){ + if(es.nodeName === "data-cover-only"){ + newOptions.filter = es.nodeValue; + } + if(es.nodeName === "data-cover-never"){ + newOptions.antifilter = es.nodeValue; + } + if(es.nodeName === "data-cover-reporter"){ + newOptions.reporter = es.nodeValue; + } + if (es.nodeName === "data-cover-adapter"){ + newOptions.adapter = es.nodeValue; + } + if (es.nodeName === "data-cover-loader"){ + newOptions.loader = es.nodeValue; + } + if (es.nodeName === "data-cover-timeout"){ + newOptions.timeout = es.nodeValue; + } + if (es.nodeName === "data-cover-modulepattern") { + newOptions.modulePattern = es.nodeValue; + } + if (es.nodeName === "data-cover-reporter-options"){ + try{ + newOptions.reporter_options = JSON.parse(es.nodeValue); + }catch(e){ + if (blanket.options("debug")){ + throw new Error("Invalid reporter options. Must be a valid stringified JSON object."); + } + } + } + if (es.nodeName === "data-cover-testReadyCallback"){ + newOptions.testReadyCallback = es.nodeValue; + } + if (es.nodeName === "data-cover-customVariable"){ + newOptions.customVariable = es.nodeValue; + } + if (es.nodeName === "data-cover-flags"){ + var flags = " "+es.nodeValue+" "; + if (flags.indexOf(" ignoreError ") > -1){ + newOptions.ignoreScriptError = true; + } + if (flags.indexOf(" autoStart ") > -1){ + newOptions.autoStart = true; + } + if (flags.indexOf(" ignoreCors ") > -1){ + newOptions.ignoreCors = true; + } + if (flags.indexOf(" branchTracking ") > -1){ + newOptions.branchTracking = true; + } + if (flags.indexOf(" sourceURL ") > -1){ + newOptions.sourceURL = true; + } + if (flags.indexOf(" debug ") > -1){ + newOptions.debug = true; + } + if (flags.indexOf(" engineOnly ") > -1){ + newOptions.engineOnly = true; + } + if (flags.indexOf(" commonJS ") > -1){ + newOptions.commonJS = true; + } + if (flags.indexOf(" instrumentCache ") > -1){ + newOptions.instrumentCache = true; + } + } + }); + blanket.options(newOptions); + + if (typeof requirejs !== 'undefined'){ + blanket.options("existingRequireJS",true); + } + /* setup requirejs loader, if needed */ + + if (blanket.options("commonJS")){ + blanket._commonjs = {}; + } +})(); +(function(_blanket){ +_blanket.extend({ + utils: { + normalizeBackslashes: function(str) { + return str.replace(/\\/g, '/'); + }, + matchPatternAttribute: function(filename,pattern){ + if (typeof pattern === 'string'){ + if (pattern.indexOf("[") === 0){ + //treat as array + var pattenArr = pattern.slice(1,pattern.length-1).split(","); + return pattenArr.some(function(elem){ + return _blanket.utils.matchPatternAttribute(filename,_blanket.utils.normalizeBackslashes(elem.slice(1,-1))); + //return filename.indexOf(_blanket.utils.normalizeBackslashes(elem.slice(1,-1))) > -1; + }); + }else if ( pattern.indexOf("//") === 0){ + var ex = pattern.slice(2,pattern.lastIndexOf('/')); + var mods = pattern.slice(pattern.lastIndexOf('/')+1); + var regex = new RegExp(ex,mods); + return regex.test(filename); + }else if (pattern.indexOf("#") === 0){ + return window[pattern.slice(1)].call(window,filename); + }else{ + return filename.indexOf(_blanket.utils.normalizeBackslashes(pattern)) > -1; + } + }else if ( pattern instanceof Array ){ + return pattern.some(function(elem){ + return _blanket.utils.matchPatternAttribute(filename,elem); + }); + }else if (pattern instanceof RegExp){ + return pattern.test(filename); + }else if (typeof pattern === "function"){ + return pattern.call(window,filename); + } + }, + blanketEval: function(data){ + _blanket._addScript(data); + }, + collectPageScripts: function(){ + var toArray = Array.prototype.slice; + var scripts = toArray.call(document.scripts); + var selectedScripts=[],scriptNames=[]; + var filter = _blanket.options("filter"); + if(filter != null){ + //global filter in place, data-cover-only + var antimatch = _blanket.options("antifilter"); + selectedScripts = toArray.call(document.scripts) + .filter(function(s){ + return toArray.call(s.attributes).filter(function(sn){ + return sn.nodeName === "src" && _blanket.utils.matchPatternAttribute(sn.nodeValue,filter) && + (typeof antimatch === "undefined" || !_blanket.utils.matchPatternAttribute(sn.nodeValue,antimatch)); + }).length === 1; + }); + }else{ + selectedScripts = toArray.call(document.querySelectorAll("script[data-cover]")); + } + scriptNames = selectedScripts.map(function(s){ + return _blanket.utils.qualifyURL( + toArray.call(s.attributes).filter( + function(sn){ + return sn.nodeName === "src"; + })[0].nodeValue).replace(".js",""); + }); + if (!filter){ + _blanket.options("filter","['"+scriptNames.join("','")+"']"); + } + return scriptNames; + }, + loadAll: function(nextScript,cb,preprocessor){ + /** + * load dependencies + * @param {nextScript} factory for priority level + * @param {cb} the done callback + */ + var currScript=nextScript(); + var isLoaded = _blanket.utils.scriptIsLoaded( + currScript, + _blanket.utils.ifOrdered, + nextScript, + cb + ); + + if (!(_blanket.utils.cache[currScript] && _blanket.utils.cache[currScript].loaded)){ + var attach = function(){ + if (_blanket.options("debug")) {console.log("BLANKET-Mark script:"+currScript+", as loaded and move to next script.");} + isLoaded(); + }; + var whenDone = function(result){ + if (_blanket.options("debug")) {console.log("BLANKET-File loading finished");} + if (typeof result !== 'undefined'){ + if (_blanket.options("debug")) {console.log("BLANKET-Add file to DOM.");} + _blanket._addScript(result); + } + attach(); + }; + + _blanket.utils.attachScript( + { + url: currScript + }, + function (content){ + _blanket.utils.processFile( + content, + currScript, + whenDone, + whenDone + ); + } + ); + }else{ + isLoaded(); + } + }, + attachScript: function(options,cb){ + var timeout = _blanket.options("timeout") || 3000; + setTimeout(function(){ + if (!_blanket.utils.cache[options.url].loaded){ + throw new Error("error loading source script"); + } + },timeout); + _blanket.utils.getFile( + options.url, + cb, + function(){ throw new Error("error loading source script");} + ); + }, + ifOrdered: function(nextScript,cb){ + /** + * ordered loading callback + * @param {nextScript} factory for priority level + * @param {cb} the done callback + */ + var currScript = nextScript(true); + if (currScript){ + _blanket.utils.loadAll(nextScript,cb); + }else{ + cb(new Error("Error in loading chain.")); + } + }, + scriptIsLoaded: function(url,orderedCb,nextScript,cb){ + /** + * returns a callback that checks a loading list to see if a script is loaded. + * @param {orderedCb} callback if ordered loading is being done + * @param {nextScript} factory for next priority level + * @param {cb} the done callback + */ + if (_blanket.options("debug")) {console.log("BLANKET-Returning function");} + return function(){ + if (_blanket.options("debug")) {console.log("BLANKET-Marking file as loaded: "+url);} + + _blanket.utils.cache[url].loaded=true; + + if (_blanket.utils.allLoaded()){ + if (_blanket.options("debug")) {console.log("BLANKET-All files loaded");} + cb(); + }else if (orderedCb){ + //if it's ordered we need to + //traverse down to the next + //priority level + if (_blanket.options("debug")) {console.log("BLANKET-Load next file.");} + orderedCb(nextScript,cb); + } + }; + }, + cache: {}, + allLoaded: function (){ + /** + * check if depdencies are loaded in cache + */ + var cached = Object.keys(_blanket.utils.cache); + for (var i=0;i -1){ + callback(_blanket.blanketSession[key]); + foundInSession=true; + return; + } + } + } + if (!foundInSession){ + var xhr = _blanket.utils.createXhr(); + xhr.open('GET', url, true); + + //Allow overrides specified in config + if (onXhr) { + onXhr(xhr, url); + } + + xhr.onreadystatechange = function (evt) { + var status, err; + + //Do not explicitly handle errors, those should be + //visible via console output in the browser. + if (xhr.readyState === 4) { + status = xhr.status; + if ((status > 399 && status < 600) /*|| + (status === 0 && + navigator.userAgent.toLowerCase().indexOf('firefox') > -1) + */ ) { + //An http 4xx or 5xx error. Signal an error. + err = new Error(url + ' HTTP status: ' + status); + err.xhr = xhr; + errback(err); + } else { + callback(xhr.responseText); + } + } + }; + try{ + xhr.send(null); + }catch(e){ + if (e.code && (e.code === 101 || e.code === 1012) && _blanket.options("ignoreCors") === false){ + //running locally and getting error from browser + _blanket.showManualLoader(); + } else { + throw e; + } + } + } + } + } +}); + +(function(){ + var require = blanket.options("commonJS") ? blanket._commonjs.require : window.require; + var requirejs = blanket.options("commonJS") ? blanket._commonjs.requirejs : window.requirejs; + if (!_blanket.options("engineOnly") && _blanket.options("existingRequireJS")){ + + _blanket.utils.oldloader = requirejs.load; + + requirejs.load = function (context, moduleName, url) { + _blanket.requiringFile(url); + _blanket.utils.getFile(url, + function(content){ + _blanket.utils.processFile( + content, + url, + function newLoader(){ + context.completeLoad(moduleName); + }, + function oldLoader(){ + _blanket.utils.oldloader(context, moduleName, url); + } + ); + }, function (err) { + _blanket.requiringFile(); + throw err; + }); + }; + } +})(); + +})(blanket); + +(function(){ +if (typeof QUnit !== 'undefined'){ + //check to make sure requirejs is completed before we start the test runner + var allLoaded = function() { + return window.QUnit.config.queue.length > 0 && blanket.noConflict().requireFilesLoaded(); + }; + + if (!QUnit.config.urlConfig[0].tooltip){ + //older versions we run coverage automatically + //and we change how events are binded + QUnit.begin=function(){ + blanket.noConflict().setupCoverage(); + }; + + QUnit.done=function(failures, total) { + blanket.noConflict().onTestsDone(); + }; + QUnit.moduleStart=function( details ) { + blanket.noConflict().onModuleStart(); + }; + QUnit.testStart=function( details ) { + blanket.noConflict().onTestStart(); + }; + QUnit.testDone=function( details ) { + blanket.noConflict().onTestDone(details.total,details.passed); + }; + blanket.beforeStartTestRunner({ + condition: allLoaded, + callback: QUnit.start + }); + }else{ + QUnit.config.urlConfig.push({ + id: "coverage", + label: "Enable coverage", + tooltip: "Enable code coverage." + }); + + if ( QUnit.urlParams.coverage || blanket.options("autoStart") ) { + QUnit.begin(function(){ + blanket.noConflict().setupCoverage(); + }); + + QUnit.done(function(failures, total) { + blanket.noConflict().onTestsDone(); + }); + QUnit.moduleStart(function( details ) { + blanket.noConflict().onModuleStart(); + }); + QUnit.testStart(function( details ) { + blanket.noConflict().onTestStart(); + }); + QUnit.testDone(function( details ) { + blanket.noConflict().onTestDone(details.total,details.passed); + }); + blanket.noConflict().beforeStartTestRunner({ + condition: allLoaded, + callback: function(){ + if (!(blanket.options("existingRequireJS") && !blanket.options("autoStart"))){ + QUnit.start(); + } + } + }); + }else{ + if (blanket.options("existingRequireJS")){ requirejs.load = _blanket.utils.oldloader; } + blanket.noConflict().beforeStartTestRunner({ + condition: allLoaded, + callback: function(){ + if (!(blanket.options("existingRequireJS") && !blanket.options("autoStart"))){ + QUnit.start(); + } + }, + coverage:false + }); + } + } +} +})(); \ No newline at end of file diff --git a/js_tests/qunit/blanket.min.js b/js_tests/qunit/blanket.min.js deleted file mode 100644 index b5ce8aaff2b0..000000000000 --- a/js_tests/qunit/blanket.min.js +++ /dev/null @@ -1,39 +0,0 @@ -/*! blanket - v1.1.5 */ -"undefined"!=typeof QUnit&&(QUnit.config.autostart=!1),function(a){/* - Copyright (C) 2013 Ariya Hidayat - Copyright (C) 2013 Thaddee Tyl - Copyright (C) 2013 Mathias Bynens - Copyright (C) 2012 Ariya Hidayat - Copyright (C) 2012 Mathias Bynens - Copyright (C) 2012 Joost-Wim Boekesteijn - Copyright (C) 2012 Kris Kowal - Copyright (C) 2012 Yusuke Suzuki - Copyright (C) 2012 Arpad Borsos - Copyright (C) 2011 Ariya Hidayat - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -!function(b,c){"use strict";"function"==typeof a&&a.amd?a(["exports"],c):c("undefined"!=typeof exports?exports:b.esprima={})}(this,function(a){"use strict";function b(a,b){if(!a)throw new Error("ASSERT: "+b)}function c(a){return a>=48&&57>=a}function d(a){return"0123456789abcdefABCDEF".indexOf(a)>=0}function e(a){return"01234567".indexOf(a)>=0}function f(a){return 32===a||9===a||11===a||12===a||160===a||a>=5760&&[5760,6158,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8239,8287,12288,65279].indexOf(a)>=0}function g(a){return 10===a||13===a||8232===a||8233===a}function h(a){return 36===a||95===a||a>=65&&90>=a||a>=97&&122>=a||92===a||a>=128&&ec.NonAsciiIdentifierStart.test(String.fromCharCode(a))}function i(a){return 36===a||95===a||a>=65&&90>=a||a>=97&&122>=a||a>=48&&57>=a||92===a||a>=128&&ec.NonAsciiIdentifierPart.test(String.fromCharCode(a))}function j(a){switch(a){case"class":case"enum":case"export":case"extends":case"import":case"super":return!0;default:return!1}}function k(a){switch(a){case"implements":case"interface":case"package":case"private":case"protected":case"public":case"static":case"yield":case"let":return!0;default:return!1}}function l(a){return"eval"===a||"arguments"===a}function m(a){if(hc&&k(a))return!0;switch(a.length){case 2:return"if"===a||"in"===a||"do"===a;case 3:return"var"===a||"for"===a||"new"===a||"try"===a||"let"===a;case 4:return"this"===a||"else"===a||"case"===a||"void"===a||"with"===a||"enum"===a;case 5:return"while"===a||"break"===a||"catch"===a||"throw"===a||"const"===a||"yield"===a||"class"===a||"super"===a;case 6:return"return"===a||"typeof"===a||"delete"===a||"switch"===a||"export"===a||"import"===a;case 7:return"default"===a||"finally"===a||"extends"===a;case 8:return"function"===a||"continue"===a||"debugger"===a;case 10:return"instanceof"===a;default:return!1}}function n(a,c,d,e,f){var g;b("number"==typeof d,"Comment must have valid position"),oc.lastCommentStart>=d||(oc.lastCommentStart=d,g={type:a,value:c},pc.range&&(g.range=[d,e]),pc.loc&&(g.loc=f),pc.comments.push(g),pc.attachComment&&(pc.leadingComments.push(g),pc.trailingComments.push(g)))}function o(a){var b,c,d,e;for(b=ic-a,c={start:{line:jc,column:ic-kc-a}};lc>ic;)if(d=gc.charCodeAt(ic),++ic,g(d))return pc.comments&&(e=gc.slice(b+a,ic-1),c.end={line:jc,column:ic-kc-1},n("Line",e,b,ic-1,c)),13===d&&10===gc.charCodeAt(ic)&&++ic,++jc,void(kc=ic);pc.comments&&(e=gc.slice(b+a,ic),c.end={line:jc,column:ic-kc},n("Line",e,b,ic,c))}function p(){var a,b,c,d;for(pc.comments&&(a=ic-2,b={start:{line:jc,column:ic-kc-2}});lc>ic;)if(c=gc.charCodeAt(ic),g(c))13===c&&10===gc.charCodeAt(ic+1)&&++ic,++jc,++ic,kc=ic,ic>=lc&&O({},dc.UnexpectedToken,"ILLEGAL");else if(42===c){if(47===gc.charCodeAt(ic+1))return++ic,++ic,void(pc.comments&&(d=gc.slice(a+2,ic-2),b.end={line:jc,column:ic-kc},n("Block",d,a,ic,b)));++ic}else++ic;O({},dc.UnexpectedToken,"ILLEGAL")}function q(){var a,b;for(b=0===ic;lc>ic;)if(a=gc.charCodeAt(ic),f(a))++ic;else if(g(a))++ic,13===a&&10===gc.charCodeAt(ic)&&++ic,++jc,kc=ic,b=!0;else if(47===a)if(a=gc.charCodeAt(ic+1),47===a)++ic,++ic,o(2),b=!0;else{if(42!==a)break;++ic,++ic,p()}else if(b&&45===a){if(45!==gc.charCodeAt(ic+1)||62!==gc.charCodeAt(ic+2))break;ic+=3,o(3)}else{if(60!==a)break;if("!--"!==gc.slice(ic+1,ic+4))break;++ic,++ic,++ic,++ic,o(4)}}function r(a){var b,c,e,f=0;for(c="u"===a?4:2,b=0;c>b;++b){if(!(lc>ic&&d(gc[ic])))return"";e=gc[ic++],f=16*f+"0123456789abcdef".indexOf(e.toLowerCase())}return String.fromCharCode(f)}function s(){var a,b;for(a=gc.charCodeAt(ic++),b=String.fromCharCode(a),92===a&&(117!==gc.charCodeAt(ic)&&O({},dc.UnexpectedToken,"ILLEGAL"),++ic,a=r("u"),a&&"\\"!==a&&h(a.charCodeAt(0))||O({},dc.UnexpectedToken,"ILLEGAL"),b=a);lc>ic&&(a=gc.charCodeAt(ic),i(a));)++ic,b+=String.fromCharCode(a),92===a&&(b=b.substr(0,b.length-1),117!==gc.charCodeAt(ic)&&O({},dc.UnexpectedToken,"ILLEGAL"),++ic,a=r("u"),a&&"\\"!==a&&i(a.charCodeAt(0))||O({},dc.UnexpectedToken,"ILLEGAL"),b+=a);return b}function t(){var a,b;for(a=ic++;lc>ic;){if(b=gc.charCodeAt(ic),92===b)return ic=a,s();if(!i(b))break;++ic}return gc.slice(a,ic)}function u(){var a,b,c;return a=ic,b=92===gc.charCodeAt(ic)?s():t(),c=1===b.length?$b.Identifier:m(b)?$b.Keyword:"null"===b?$b.NullLiteral:"true"===b||"false"===b?$b.BooleanLiteral:$b.Identifier,{type:c,value:b,lineNumber:jc,lineStart:kc,start:a,end:ic}}function v(){var a,b,c,d,e=ic,f=gc.charCodeAt(ic),g=gc[ic];switch(f){case 46:case 40:case 41:case 59:case 44:case 123:case 125:case 91:case 93:case 58:case 63:case 126:return++ic,pc.tokenize&&(40===f?pc.openParenToken=pc.tokens.length:123===f&&(pc.openCurlyToken=pc.tokens.length)),{type:$b.Punctuator,value:String.fromCharCode(f),lineNumber:jc,lineStart:kc,start:e,end:ic};default:if(a=gc.charCodeAt(ic+1),61===a)switch(f){case 43:case 45:case 47:case 60:case 62:case 94:case 124:case 37:case 38:case 42:return ic+=2,{type:$b.Punctuator,value:String.fromCharCode(f)+String.fromCharCode(a),lineNumber:jc,lineStart:kc,start:e,end:ic};case 33:case 61:return ic+=2,61===gc.charCodeAt(ic)&&++ic,{type:$b.Punctuator,value:gc.slice(e,ic),lineNumber:jc,lineStart:kc,start:e,end:ic}}}return d=gc.substr(ic,4),">>>="===d?(ic+=4,{type:$b.Punctuator,value:d,lineNumber:jc,lineStart:kc,start:e,end:ic}):(c=d.substr(0,3),">>>"===c||"<<="===c||">>="===c?(ic+=3,{type:$b.Punctuator,value:c,lineNumber:jc,lineStart:kc,start:e,end:ic}):(b=c.substr(0,2),g===b[1]&&"+-<>&|".indexOf(g)>=0||"=>"===b?(ic+=2,{type:$b.Punctuator,value:b,lineNumber:jc,lineStart:kc,start:e,end:ic}):"<>=!+-*%&|^/".indexOf(g)>=0?(++ic,{type:$b.Punctuator,value:g,lineNumber:jc,lineStart:kc,start:e,end:ic}):void O({},dc.UnexpectedToken,"ILLEGAL")))}function w(a){for(var b="";lc>ic&&d(gc[ic]);)b+=gc[ic++];return 0===b.length&&O({},dc.UnexpectedToken,"ILLEGAL"),h(gc.charCodeAt(ic))&&O({},dc.UnexpectedToken,"ILLEGAL"),{type:$b.NumericLiteral,value:parseInt("0x"+b,16),lineNumber:jc,lineStart:kc,start:a,end:ic}}function x(a){for(var b="0"+gc[ic++];lc>ic&&e(gc[ic]);)b+=gc[ic++];return(h(gc.charCodeAt(ic))||c(gc.charCodeAt(ic)))&&O({},dc.UnexpectedToken,"ILLEGAL"),{type:$b.NumericLiteral,value:parseInt(b,8),octal:!0,lineNumber:jc,lineStart:kc,start:a,end:ic}}function y(){var a,d,f;if(f=gc[ic],b(c(f.charCodeAt(0))||"."===f,"Numeric literal must start with a decimal digit or a decimal point"),d=ic,a="","."!==f){if(a=gc[ic++],f=gc[ic],"0"===a){if("x"===f||"X"===f)return++ic,w(d);if(e(f))return x(d);f&&c(f.charCodeAt(0))&&O({},dc.UnexpectedToken,"ILLEGAL")}for(;c(gc.charCodeAt(ic));)a+=gc[ic++];f=gc[ic]}if("."===f){for(a+=gc[ic++];c(gc.charCodeAt(ic));)a+=gc[ic++];f=gc[ic]}if("e"===f||"E"===f)if(a+=gc[ic++],f=gc[ic],("+"===f||"-"===f)&&(a+=gc[ic++]),c(gc.charCodeAt(ic)))for(;c(gc.charCodeAt(ic));)a+=gc[ic++];else O({},dc.UnexpectedToken,"ILLEGAL");return h(gc.charCodeAt(ic))&&O({},dc.UnexpectedToken,"ILLEGAL"),{type:$b.NumericLiteral,value:parseFloat(a),lineNumber:jc,lineStart:kc,start:d,end:ic}}function z(){var a,c,d,f,h,i,j,k,l="",m=!1;for(j=jc,k=kc,a=gc[ic],b("'"===a||'"'===a,"String literal must starts with a quote"),c=ic,++ic;lc>ic;){if(d=gc[ic++],d===a){a="";break}if("\\"===d)if(d=gc[ic++],d&&g(d.charCodeAt(0)))++jc,"\r"===d&&"\n"===gc[ic]&&++ic,kc=ic;else switch(d){case"u":case"x":i=ic,h=r(d),h?l+=h:(ic=i,l+=d);break;case"n":l+="\n";break;case"r":l+="\r";break;case"t":l+=" ";break;case"b":l+="\b";break;case"f":l+="\f";break;case"v":l+=" ";break;default:e(d)?(f="01234567".indexOf(d),0!==f&&(m=!0),lc>ic&&e(gc[ic])&&(m=!0,f=8*f+"01234567".indexOf(gc[ic++]),"0123".indexOf(d)>=0&&lc>ic&&e(gc[ic])&&(f=8*f+"01234567".indexOf(gc[ic++]))),l+=String.fromCharCode(f)):l+=d}else{if(g(d.charCodeAt(0)))break;l+=d}}return""!==a&&O({},dc.UnexpectedToken,"ILLEGAL"),{type:$b.StringLiteral,value:l,octal:m,startLineNumber:j,startLineStart:k,lineNumber:jc,lineStart:kc,start:c,end:ic}}function A(a,b){var c;try{c=new RegExp(a,b)}catch(d){O({},dc.InvalidRegExp)}return c}function B(){var a,c,d,e,f;for(a=gc[ic],b("/"===a,"Regular expression literal must start with a slash"),c=gc[ic++],d=!1,e=!1;lc>ic;)if(a=gc[ic++],c+=a,"\\"===a)a=gc[ic++],g(a.charCodeAt(0))&&O({},dc.UnterminatedRegExp),c+=a;else if(g(a.charCodeAt(0)))O({},dc.UnterminatedRegExp);else if(d)"]"===a&&(d=!1);else{if("/"===a){e=!0;break}"["===a&&(d=!0)}return e||O({},dc.UnterminatedRegExp),f=c.substr(1,c.length-2),{value:f,literal:c}}function C(){var a,b,c,d;for(b="",c="";lc>ic&&(a=gc[ic],i(a.charCodeAt(0)));)if(++ic,"\\"===a&&lc>ic)if(a=gc[ic],"u"===a){if(++ic,d=ic,a=r("u"))for(c+=a,b+="\\u";ic>d;++d)b+=gc[d];else ic=d,c+="u",b+="\\u";P({},dc.UnexpectedToken,"ILLEGAL")}else b+="\\",P({},dc.UnexpectedToken,"ILLEGAL");else c+=a,b+=a;return{value:c,literal:b}}function D(){var a,b,c,d;return nc=null,q(),a=ic,b=B(),c=C(),d=A(b.value,c.value),pc.tokenize?{type:$b.RegularExpression,value:d,lineNumber:jc,lineStart:kc,start:a,end:ic}:{literal:b.literal+c.literal,value:d,start:a,end:ic}}function E(){var a,b,c,d;return q(),a=ic,b={start:{line:jc,column:ic-kc}},c=D(),b.end={line:jc,column:ic-kc},pc.tokenize||(pc.tokens.length>0&&(d=pc.tokens[pc.tokens.length-1],d.range[0]===a&&"Punctuator"===d.type&&("/"===d.value||"/="===d.value)&&pc.tokens.pop()),pc.tokens.push({type:"RegularExpression",value:c.literal,range:[a,ic],loc:b})),c}function F(a){return a.type===$b.Identifier||a.type===$b.Keyword||a.type===$b.BooleanLiteral||a.type===$b.NullLiteral}function G(){var a,b;if(a=pc.tokens[pc.tokens.length-1],!a)return E();if("Punctuator"===a.type){if("]"===a.value)return v();if(")"===a.value)return b=pc.tokens[pc.openParenToken-1],!b||"Keyword"!==b.type||"if"!==b.value&&"while"!==b.value&&"for"!==b.value&&"with"!==b.value?v():E();if("}"===a.value){if(pc.tokens[pc.openCurlyToken-3]&&"Keyword"===pc.tokens[pc.openCurlyToken-3].type){if(b=pc.tokens[pc.openCurlyToken-4],!b)return v()}else{if(!pc.tokens[pc.openCurlyToken-4]||"Keyword"!==pc.tokens[pc.openCurlyToken-4].type)return v();if(b=pc.tokens[pc.openCurlyToken-5],!b)return E()}return ac.indexOf(b.value)>=0?v():E()}return E()}return"Keyword"===a.type?E():v()}function H(){var a;return q(),ic>=lc?{type:$b.EOF,lineNumber:jc,lineStart:kc,start:ic,end:ic}:(a=gc.charCodeAt(ic),h(a)?u():40===a||41===a||59===a?v():39===a||34===a?z():46===a?c(gc.charCodeAt(ic+1))?y():v():c(a)?y():pc.tokenize&&47===a?G():v())}function I(){var a,b,c;return q(),a={start:{line:jc,column:ic-kc}},b=H(),a.end={line:jc,column:ic-kc},b.type!==$b.EOF&&(c=gc.slice(b.start,b.end),pc.tokens.push({type:_b[b.type],value:c,range:[b.start,b.end],loc:a})),b}function J(){var a;return a=nc,ic=a.end,jc=a.lineNumber,kc=a.lineStart,nc="undefined"!=typeof pc.tokens?I():H(),ic=a.end,jc=a.lineNumber,kc=a.lineStart,a}function K(){var a,b,c;a=ic,b=jc,c=kc,nc="undefined"!=typeof pc.tokens?I():H(),ic=a,jc=b,kc=c}function L(a,b){this.line=a,this.column=b}function M(a,b,c,d){this.start=new L(a,b),this.end=new L(c,d)}function N(){var a,b,c,d;return a=ic,b=jc,c=kc,q(),d=jc!==b,ic=a,jc=b,kc=c,d}function O(a,c){var d,e=Array.prototype.slice.call(arguments,2),f=c.replace(/%(\d)/g,function(a,c){return b(c>="===a||">>>="===a||"&="===a||"^="===a||"|="===a)}function W(){var a;return 59===gc.charCodeAt(ic)||T(";")?void J():(a=jc,q(),void(jc===a&&(nc.type===$b.EOF||T("}")||Q(nc))))}function X(a){return a.type===bc.Identifier||a.type===bc.MemberExpression}function Y(){var a,b=[];for(a=nc,R("[");!T("]");)T(",")?(J(),b.push(null)):(b.push(pb()),T("]")||R(","));return J(),mc.markEnd(mc.createArrayExpression(b),a)}function Z(a,b){var c,d,e;return c=hc,e=nc,d=Qb(),b&&hc&&l(a[0].name)&&P(b,dc.StrictParamName),hc=c,mc.markEnd(mc.createFunctionExpression(null,a,[],d),e)}function $(){var a,b;return b=nc,a=J(),a.type===$b.StringLiteral||a.type===$b.NumericLiteral?(hc&&a.octal&&P(a,dc.StrictOctalLiteral),mc.markEnd(mc.createLiteral(a),b)):mc.markEnd(mc.createIdentifier(a.value),b)}function _(){var a,b,c,d,e,f;return a=nc,f=nc,a.type===$b.Identifier?(c=$(),"get"!==a.value||T(":")?"set"!==a.value||T(":")?(R(":"),d=pb(),mc.markEnd(mc.createProperty("init",c,d),f)):(b=$(),R("("),a=nc,a.type!==$b.Identifier?(R(")"),P(a,dc.UnexpectedToken,a.value),d=Z([])):(e=[tb()],R(")"),d=Z(e,a)),mc.markEnd(mc.createProperty("set",b,d),f)):(b=$(),R("("),R(")"),d=Z([]),mc.markEnd(mc.createProperty("get",b,d),f))):a.type!==$b.EOF&&a.type!==$b.Punctuator?(b=$(),R(":"),d=pb(),mc.markEnd(mc.createProperty("init",b,d),f)):void Q(a)}function ab(){var a,b,c,d,e,f=[],g={},h=String;for(e=nc,R("{");!T("}");)a=_(),b=a.key.type===bc.Identifier?a.key.name:h(a.key.value),d="init"===a.kind?cc.Data:"get"===a.kind?cc.Get:cc.Set,c="$"+b,Object.prototype.hasOwnProperty.call(g,c)?(g[c]===cc.Data?hc&&d===cc.Data?P({},dc.StrictDuplicateProperty):d!==cc.Data&&P({},dc.AccessorDataProperty):d===cc.Data?P({},dc.AccessorDataProperty):g[c]&d&&P({},dc.AccessorGetSet),g[c]|=d):g[c]=d,f.push(a),T("}")||R(",");return R("}"),mc.markEnd(mc.createObjectExpression(f),e)}function bb(){var a;return R("("),a=qb(),R(")"),a}function cb(){var a,b,c,d;if(T("("))return bb();if(T("["))return Y();if(T("{"))return ab();if(a=nc.type,d=nc,a===$b.Identifier)c=mc.createIdentifier(J().value);else if(a===$b.StringLiteral||a===$b.NumericLiteral)hc&&nc.octal&&P(nc,dc.StrictOctalLiteral),c=mc.createLiteral(J());else if(a===$b.Keyword){if(U("function"))return Tb();U("this")?(J(),c=mc.createThisExpression()):Q(J())}else a===$b.BooleanLiteral?(b=J(),b.value="true"===b.value,c=mc.createLiteral(b)):a===$b.NullLiteral?(b=J(),b.value=null,c=mc.createLiteral(b)):T("/")||T("/=")?(c=mc.createLiteral("undefined"!=typeof pc.tokens?E():D()),K()):Q(J());return mc.markEnd(c,d)}function db(){var a=[];if(R("("),!T(")"))for(;lc>ic&&(a.push(pb()),!T(")"));)R(",");return R(")"),a}function eb(){var a,b;return b=nc,a=J(),F(a)||Q(a),mc.markEnd(mc.createIdentifier(a.value),b)}function fb(){return R("."),eb()}function gb(){var a;return R("["),a=qb(),R("]"),a}function hb(){var a,b,c;return c=nc,S("new"),a=jb(),b=T("(")?db():[],mc.markEnd(mc.createNewExpression(a,b),c)}function ib(){var a,b,c,d,e;for(e=nc,a=oc.allowIn,oc.allowIn=!0,b=U("new")?hb():cb(),oc.allowIn=a;;){if(T("."))d=fb(),b=mc.createMemberExpression(".",b,d);else if(T("("))c=db(),b=mc.createCallExpression(b,c);else{if(!T("["))break;d=gb(),b=mc.createMemberExpression("[",b,d)}mc.markEnd(b,e)}return b}function jb(){var a,b,c,d;for(d=nc,a=oc.allowIn,b=U("new")?hb():cb(),oc.allowIn=a;T(".")||T("[");)T("[")?(c=gb(),b=mc.createMemberExpression("[",b,c)):(c=fb(),b=mc.createMemberExpression(".",b,c)),mc.markEnd(b,d);return b}function kb(){var a,b,c=nc;return a=ib(),nc.type===$b.Punctuator&&(!T("++")&&!T("--")||N()||(hc&&a.type===bc.Identifier&&l(a.name)&&P({},dc.StrictLHSPostfix),X(a)||P({},dc.InvalidLHSInAssignment),b=J(),a=mc.markEnd(mc.createPostfixExpression(b.value,a),c))),a}function lb(){var a,b,c;return nc.type!==$b.Punctuator&&nc.type!==$b.Keyword?b=kb():T("++")||T("--")?(c=nc,a=J(),b=lb(),hc&&b.type===bc.Identifier&&l(b.name)&&P({},dc.StrictLHSPrefix),X(b)||P({},dc.InvalidLHSInAssignment),b=mc.createUnaryExpression(a.value,b),b=mc.markEnd(b,c)):T("+")||T("-")||T("~")||T("!")?(c=nc,a=J(),b=lb(),b=mc.createUnaryExpression(a.value,b),b=mc.markEnd(b,c)):U("delete")||U("void")||U("typeof")?(c=nc,a=J(),b=lb(),b=mc.createUnaryExpression(a.value,b),b=mc.markEnd(b,c),hc&&"delete"===b.operator&&b.argument.type===bc.Identifier&&P({},dc.StrictDelete)):b=kb(),b}function mb(a,b){var c=0;if(a.type!==$b.Punctuator&&a.type!==$b.Keyword)return 0;switch(a.value){case"||":c=1;break;case"&&":c=2;break;case"|":c=3;break;case"^":c=4;break;case"&":c=5;break;case"==":case"!=":case"===":case"!==":c=6;break;case"<":case">":case"<=":case">=":case"instanceof":c=7;break;case"in":c=b?7:0;break;case"<<":case">>":case">>>":c=8;break;case"+":case"-":c=9;break;case"*":case"/":case"%":c=11}return c}function nb(){var a,b,c,d,e,f,g,h,i,j;if(a=nc,i=lb(),d=nc,e=mb(d,oc.allowIn),0===e)return i;for(d.prec=e,J(),b=[a,nc],g=lb(),f=[i,d,g];(e=mb(nc,oc.allowIn))>0;){for(;f.length>2&&e<=f[f.length-2].prec;)g=f.pop(),h=f.pop().value,i=f.pop(),c=mc.createBinaryExpression(h,i,g),b.pop(),a=b[b.length-1],mc.markEnd(c,a),f.push(c);d=J(),d.prec=e,f.push(d),b.push(nc),c=lb(),f.push(c)}for(j=f.length-1,c=f[j],b.pop();j>1;)c=mc.createBinaryExpression(f[j-1].value,f[j-2],c),j-=2,a=b.pop(),mc.markEnd(c,a);return c}function ob(){var a,b,c,d,e;return e=nc,a=nb(),T("?")&&(J(),b=oc.allowIn,oc.allowIn=!0,c=pb(),oc.allowIn=b,R(":"),d=pb(),a=mc.createConditionalExpression(a,c,d),mc.markEnd(a,e)),a}function pb(){var a,b,c,d,e;return a=nc,e=nc,d=b=ob(),V()&&(X(b)||P({},dc.InvalidLHSInAssignment),hc&&b.type===bc.Identifier&&l(b.name)&&P(a,dc.StrictLHSAssignment),a=J(),c=pb(),d=mc.markEnd(mc.createAssignmentExpression(a.value,b,c),e)),d}function qb(){var a,b=nc;if(a=pb(),T(",")){for(a=mc.createSequenceExpression([a]);lc>ic&&T(",");)J(),a.expressions.push(pb());mc.markEnd(a,b)}return a}function rb(){for(var a,b=[];lc>ic&&!T("}")&&(a=Ub(),"undefined"!=typeof a);)b.push(a);return b}function sb(){var a,b;return b=nc,R("{"),a=rb(),R("}"),mc.markEnd(mc.createBlockStatement(a),b)}function tb(){var a,b;return b=nc,a=J(),a.type!==$b.Identifier&&Q(a),mc.markEnd(mc.createIdentifier(a.value),b)}function ub(a){var b,c,d=null;return c=nc,b=tb(),hc&&l(b.name)&&P({},dc.StrictVarName),"const"===a?(R("="),d=pb()):T("=")&&(J(),d=pb()),mc.markEnd(mc.createVariableDeclarator(b,d),c)}function vb(a){var b=[];do{if(b.push(ub(a)),!T(","))break;J()}while(lc>ic);return b}function wb(){var a;return S("var"),a=vb(),W(),mc.createVariableDeclaration(a,"var")}function xb(a){var b,c;return c=nc,S(a),b=vb(a),W(),mc.markEnd(mc.createVariableDeclaration(b,a),c)}function yb(){return R(";"),mc.createEmptyStatement()}function zb(){var a=qb();return W(),mc.createExpressionStatement(a)}function Ab(){var a,b,c;return S("if"),R("("),a=qb(),R(")"),b=Pb(),U("else")?(J(),c=Pb()):c=null,mc.createIfStatement(a,b,c)}function Bb(){var a,b,c;return S("do"),c=oc.inIteration,oc.inIteration=!0,a=Pb(),oc.inIteration=c,S("while"),R("("),b=qb(),R(")"),T(";")&&J(),mc.createDoWhileStatement(a,b)}function Cb(){var a,b,c;return S("while"),R("("),a=qb(),R(")"),c=oc.inIteration,oc.inIteration=!0,b=Pb(),oc.inIteration=c,mc.createWhileStatement(a,b)}function Db(){var a,b,c;return c=nc,a=J(),b=vb(),mc.markEnd(mc.createVariableDeclaration(b,a.value),c)}function Eb(){var a,b,c,d,e,f,g;return a=b=c=null,S("for"),R("("),T(";")?J():(U("var")||U("let")?(oc.allowIn=!1,a=Db(),oc.allowIn=!0,1===a.declarations.length&&U("in")&&(J(),d=a,e=qb(),a=null)):(oc.allowIn=!1,a=qb(),oc.allowIn=!0,U("in")&&(X(a)||P({},dc.InvalidLHSInForIn),J(),d=a,e=qb(),a=null)),"undefined"==typeof d&&R(";")),"undefined"==typeof d&&(T(";")||(b=qb()),R(";"),T(")")||(c=qb())),R(")"),g=oc.inIteration,oc.inIteration=!0,f=Pb(),oc.inIteration=g,"undefined"==typeof d?mc.createForStatement(a,b,c,f):mc.createForInStatement(d,e,f)}function Fb(){var a,b=null;return S("continue"),59===gc.charCodeAt(ic)?(J(),oc.inIteration||O({},dc.IllegalContinue),mc.createContinueStatement(null)):N()?(oc.inIteration||O({},dc.IllegalContinue),mc.createContinueStatement(null)):(nc.type===$b.Identifier&&(b=tb(),a="$"+b.name,Object.prototype.hasOwnProperty.call(oc.labelSet,a)||O({},dc.UnknownLabel,b.name)),W(),null!==b||oc.inIteration||O({},dc.IllegalContinue),mc.createContinueStatement(b))}function Gb(){var a,b=null;return S("break"),59===gc.charCodeAt(ic)?(J(),oc.inIteration||oc.inSwitch||O({},dc.IllegalBreak),mc.createBreakStatement(null)):N()?(oc.inIteration||oc.inSwitch||O({},dc.IllegalBreak),mc.createBreakStatement(null)):(nc.type===$b.Identifier&&(b=tb(),a="$"+b.name,Object.prototype.hasOwnProperty.call(oc.labelSet,a)||O({},dc.UnknownLabel,b.name)),W(),null!==b||oc.inIteration||oc.inSwitch||O({},dc.IllegalBreak),mc.createBreakStatement(b))}function Hb(){var a=null;return S("return"),oc.inFunctionBody||P({},dc.IllegalReturn),32===gc.charCodeAt(ic)&&h(gc.charCodeAt(ic+1))?(a=qb(),W(),mc.createReturnStatement(a)):N()?mc.createReturnStatement(null):(T(";")||T("}")||nc.type===$b.EOF||(a=qb()),W(),mc.createReturnStatement(a))}function Ib(){var a,b;return hc&&(q(),P({},dc.StrictModeWith)),S("with"),R("("),a=qb(),R(")"),b=Pb(),mc.createWithStatement(a,b)}function Jb(){var a,b,c,d=[];for(c=nc,U("default")?(J(),a=null):(S("case"),a=qb()),R(":");lc>ic&&!(T("}")||U("default")||U("case"));)b=Pb(),d.push(b);return mc.markEnd(mc.createSwitchCase(a,d),c)}function Kb(){var a,b,c,d,e;if(S("switch"),R("("),a=qb(),R(")"),R("{"),b=[],T("}"))return J(),mc.createSwitchStatement(a,b);for(d=oc.inSwitch,oc.inSwitch=!0,e=!1;lc>ic&&!T("}");)c=Jb(),null===c.test&&(e&&O({},dc.MultipleDefaultsInSwitch),e=!0),b.push(c);return oc.inSwitch=d,R("}"),mc.createSwitchStatement(a,b)}function Lb(){var a;return S("throw"),N()&&O({},dc.NewlineAfterThrow),a=qb(),W(),mc.createThrowStatement(a)}function Mb(){var a,b,c;return c=nc,S("catch"),R("("),T(")")&&Q(nc),a=tb(),hc&&l(a.name)&&P({},dc.StrictCatchVariable),R(")"),b=sb(),mc.markEnd(mc.createCatchClause(a,b),c)}function Nb(){var a,b=[],c=null;return S("try"),a=sb(),U("catch")&&b.push(Mb()),U("finally")&&(J(),c=sb()),0!==b.length||c||O({},dc.NoCatchOrFinally),mc.createTryStatement(a,[],b,c)}function Ob(){return S("debugger"),W(),mc.createDebuggerStatement()}function Pb(){var a,b,c,d,e=nc.type;if(e===$b.EOF&&Q(nc),e===$b.Punctuator&&"{"===nc.value)return sb();if(d=nc,e===$b.Punctuator)switch(nc.value){case";":return mc.markEnd(yb(),d);case"(":return mc.markEnd(zb(),d)}if(e===$b.Keyword)switch(nc.value){case"break":return mc.markEnd(Gb(),d);case"continue":return mc.markEnd(Fb(),d);case"debugger":return mc.markEnd(Ob(),d);case"do":return mc.markEnd(Bb(),d);case"for":return mc.markEnd(Eb(),d);case"function":return mc.markEnd(Sb(),d);case"if":return mc.markEnd(Ab(),d);case"return":return mc.markEnd(Hb(),d);case"switch":return mc.markEnd(Kb(),d);case"throw":return mc.markEnd(Lb(),d);case"try":return mc.markEnd(Nb(),d);case"var":return mc.markEnd(wb(),d);case"while":return mc.markEnd(Cb(),d);case"with":return mc.markEnd(Ib(),d)}return a=qb(),a.type===bc.Identifier&&T(":")?(J(),c="$"+a.name,Object.prototype.hasOwnProperty.call(oc.labelSet,c)&&O({},dc.Redeclaration,"Label",a.name),oc.labelSet[c]=!0,b=Pb(),delete oc.labelSet[c],mc.markEnd(mc.createLabeledStatement(a,b),d)):(W(),mc.markEnd(mc.createExpressionStatement(a),d))}function Qb(){var a,b,c,d,e,f,g,h,i,j=[];for(i=nc,R("{");lc>ic&&nc.type===$b.StringLiteral&&(b=nc,a=Ub(),j.push(a),a.expression.type===bc.Literal);)c=gc.slice(b.start+1,b.end-1),"use strict"===c?(hc=!0,d&&P(d,dc.StrictOctalLiteral)):!d&&b.octal&&(d=b);for(e=oc.labelSet,f=oc.inIteration,g=oc.inSwitch,h=oc.inFunctionBody,oc.labelSet={},oc.inIteration=!1,oc.inSwitch=!1,oc.inFunctionBody=!0;lc>ic&&!T("}")&&(a=Ub(),"undefined"!=typeof a);)j.push(a);return R("}"),oc.labelSet=e,oc.inIteration=f,oc.inSwitch=g,oc.inFunctionBody=h,mc.markEnd(mc.createBlockStatement(j),i)}function Rb(a){var b,c,d,e,f,g,h=[];if(R("("),!T(")"))for(e={};lc>ic&&(c=nc,b=tb(),f="$"+c.value,hc?(l(c.value)&&(d=c,g=dc.StrictParamName),Object.prototype.hasOwnProperty.call(e,f)&&(d=c,g=dc.StrictParamDupe)):a||(l(c.value)?(a=c,g=dc.StrictParamName):k(c.value)?(a=c,g=dc.StrictReservedWord):Object.prototype.hasOwnProperty.call(e,f)&&(a=c,g=dc.StrictParamDupe)),h.push(b),e[f]=!0,!T(")"));)R(",");return R(")"),{params:h,stricted:d,firstRestricted:a,message:g}}function Sb(){var a,b,c,d,e,f,g,h,i,j=[];return i=nc,S("function"),c=nc,a=tb(),hc?l(c.value)&&P(c,dc.StrictFunctionName):l(c.value)?(f=c,g=dc.StrictFunctionName):k(c.value)&&(f=c,g=dc.StrictReservedWord),e=Rb(f),j=e.params,d=e.stricted,f=e.firstRestricted,e.message&&(g=e.message),h=hc,b=Qb(),hc&&f&&O(f,g),hc&&d&&P(d,g),hc=h,mc.markEnd(mc.createFunctionDeclaration(a,j,[],b),i)}function Tb(){var a,b,c,d,e,f,g,h,i=null,j=[];return h=nc,S("function"),T("(")||(a=nc,i=tb(),hc?l(a.value)&&P(a,dc.StrictFunctionName):l(a.value)?(c=a,d=dc.StrictFunctionName):k(a.value)&&(c=a,d=dc.StrictReservedWord)),e=Rb(c),j=e.params,b=e.stricted,c=e.firstRestricted,e.message&&(d=e.message),g=hc,f=Qb(),hc&&c&&O(c,d),hc&&b&&P(b,d),hc=g,mc.markEnd(mc.createFunctionExpression(i,j,[],f),h)}function Ub(){if(nc.type===$b.Keyword)switch(nc.value){case"const":case"let":return xb(nc.value);case"function":return Sb();default:return Pb()}return nc.type!==$b.EOF?Pb():void 0}function Vb(){for(var a,b,c,d,e=[];lc>ic&&(b=nc,b.type===$b.StringLiteral)&&(a=Ub(),e.push(a),a.expression.type===bc.Literal);)c=gc.slice(b.start+1,b.end-1),"use strict"===c?(hc=!0,d&&P(d,dc.StrictOctalLiteral)):!d&&b.octal&&(d=b);for(;lc>ic&&(a=Ub(),"undefined"!=typeof a);)e.push(a);return e}function Wb(){var a,b;return q(),K(),b=nc,hc=!1,a=Vb(),mc.markEnd(mc.createProgram(a),b)}function Xb(){var a,b,c,d=[];for(a=0;a0?1:0,kc=0,lc=gc.length,nc=null,oc={allowIn:!0,labelSet:{},inFunctionBody:!1,inIteration:!1,inSwitch:!1,lastCommentStart:-1},pc={},b=b||{},b.tokens=!0,pc.tokens=[],pc.tokenize=!0,pc.openParenToken=-1,pc.openCurlyToken=-1,pc.range="boolean"==typeof b.range&&b.range,pc.loc="boolean"==typeof b.loc&&b.loc,"boolean"==typeof b.comment&&b.comment&&(pc.comments=[]),"boolean"==typeof b.tolerant&&b.tolerant&&(pc.errors=[]);try{if(K(),nc.type===$b.EOF)return pc.tokens;for(d=J();nc.type!==$b.EOF;)try{d=J()}catch(f){if(d=nc,pc.errors){pc.errors.push(f);break}throw f}Xb(),e=pc.tokens,"undefined"!=typeof pc.comments&&(e.comments=pc.comments),"undefined"!=typeof pc.errors&&(e.errors=pc.errors)}catch(g){throw g}finally{pc={}}return e}function Zb(a,b){var c,d;d=String,"string"==typeof a||a instanceof String||(a=d(a)),mc=fc,gc=a,ic=0,jc=gc.length>0?1:0,kc=0,lc=gc.length,nc=null,oc={allowIn:!0,labelSet:{},inFunctionBody:!1,inIteration:!1,inSwitch:!1,lastCommentStart:-1},pc={},"undefined"!=typeof b&&(pc.range="boolean"==typeof b.range&&b.range,pc.loc="boolean"==typeof b.loc&&b.loc,pc.attachComment="boolean"==typeof b.attachComment&&b.attachComment,pc.loc&&null!==b.source&&void 0!==b.source&&(pc.source=d(b.source)),"boolean"==typeof b.tokens&&b.tokens&&(pc.tokens=[]),"boolean"==typeof b.comment&&b.comment&&(pc.comments=[]),"boolean"==typeof b.tolerant&&b.tolerant&&(pc.errors=[]),pc.attachComment&&(pc.range=!0,pc.comments=[],pc.bottomRightStack=[],pc.trailingComments=[],pc.leadingComments=[]));try{c=Wb(),"undefined"!=typeof pc.comments&&(c.comments=pc.comments),"undefined"!=typeof pc.tokens&&(Xb(),c.tokens=pc.tokens),"undefined"!=typeof pc.errors&&(c.errors=pc.errors)}catch(e){throw e}finally{pc={}}return c}var $b,_b,ac,bc,cc,dc,ec,fc,gc,hc,ic,jc,kc,lc,mc,nc,oc,pc;$b={BooleanLiteral:1,EOF:2,Identifier:3,Keyword:4,NullLiteral:5,NumericLiteral:6,Punctuator:7,StringLiteral:8,RegularExpression:9},_b={},_b[$b.BooleanLiteral]="Boolean",_b[$b.EOF]="",_b[$b.Identifier]="Identifier",_b[$b.Keyword]="Keyword",_b[$b.NullLiteral]="Null",_b[$b.NumericLiteral]="Numeric",_b[$b.Punctuator]="Punctuator",_b[$b.StringLiteral]="String",_b[$b.RegularExpression]="RegularExpression",ac=["(","{","[","in","typeof","instanceof","new","return","case","delete","throw","void","=","+=","-=","*=","/=","%=","<<=",">>=",">>>=","&=","|=","^=",",","+","-","*","/","%","++","--","<<",">>",">>>","&","|","^","!","~","&&","||","?",":","===","==",">=","<=","<",">","!=","!=="],bc={AssignmentExpression:"AssignmentExpression",ArrayExpression:"ArrayExpression",BlockStatement:"BlockStatement",BinaryExpression:"BinaryExpression",BreakStatement:"BreakStatement",CallExpression:"CallExpression",CatchClause:"CatchClause",ConditionalExpression:"ConditionalExpression",ContinueStatement:"ContinueStatement",DoWhileStatement:"DoWhileStatement",DebuggerStatement:"DebuggerStatement",EmptyStatement:"EmptyStatement",ExpressionStatement:"ExpressionStatement",ForStatement:"ForStatement",ForInStatement:"ForInStatement",FunctionDeclaration:"FunctionDeclaration",FunctionExpression:"FunctionExpression",Identifier:"Identifier",IfStatement:"IfStatement",Literal:"Literal",LabeledStatement:"LabeledStatement",LogicalExpression:"LogicalExpression",MemberExpression:"MemberExpression",NewExpression:"NewExpression",ObjectExpression:"ObjectExpression",Program:"Program",Property:"Property",ReturnStatement:"ReturnStatement",SequenceExpression:"SequenceExpression",SwitchStatement:"SwitchStatement",SwitchCase:"SwitchCase",ThisExpression:"ThisExpression",ThrowStatement:"ThrowStatement",TryStatement:"TryStatement",UnaryExpression:"UnaryExpression",UpdateExpression:"UpdateExpression",VariableDeclaration:"VariableDeclaration",VariableDeclarator:"VariableDeclarator",WhileStatement:"WhileStatement",WithStatement:"WithStatement"},cc={Data:1,Get:2,Set:4},dc={UnexpectedToken:"Unexpected token %0",UnexpectedNumber:"Unexpected number",UnexpectedString:"Unexpected string",UnexpectedIdentifier:"Unexpected identifier",UnexpectedReserved:"Unexpected reserved word",UnexpectedEOS:"Unexpected end of input",NewlineAfterThrow:"Illegal newline after throw",InvalidRegExp:"Invalid regular expression",UnterminatedRegExp:"Invalid regular expression: missing /",InvalidLHSInAssignment:"Invalid left-hand side in assignment",InvalidLHSInForIn:"Invalid left-hand side in for-in",MultipleDefaultsInSwitch:"More than one default clause in switch statement",NoCatchOrFinally:"Missing catch or finally after try",UnknownLabel:"Undefined label '%0'",Redeclaration:"%0 '%1' has already been declared",IllegalContinue:"Illegal continue statement",IllegalBreak:"Illegal break statement",IllegalReturn:"Illegal return statement",StrictModeWith:"Strict mode code may not include a with statement",StrictCatchVariable:"Catch variable may not be eval or arguments in strict mode",StrictVarName:"Variable name may not be eval or arguments in strict mode",StrictParamName:"Parameter name eval or arguments is not allowed in strict mode",StrictParamDupe:"Strict mode function may not have duplicate parameter names",StrictFunctionName:"Function name may not be eval or arguments in strict mode",StrictOctalLiteral:"Octal literals are not allowed in strict mode.",StrictDelete:"Delete of an unqualified identifier in strict mode.",StrictDuplicateProperty:"Duplicate data property in object literal not allowed in strict mode",AccessorDataProperty:"Object literal may not have data and accessor property with the same name",AccessorGetSet:"Object literal may not have multiple get/set accessors with the same name",StrictLHSAssignment:"Assignment to eval or arguments is not allowed in strict mode",StrictLHSPostfix:"Postfix increment/decrement may not have eval or arguments operand in strict mode",StrictLHSPrefix:"Prefix increment/decrement may not have eval or arguments operand in strict mode",StrictReservedWord:"Use of future reserved word in strict mode"},ec={NonAsciiIdentifierStart:new RegExp("[\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc]"),NonAsciiIdentifierPart:new RegExp("[\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0300-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u0483-\u0487\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u05d0-\u05ea\u05f0-\u05f2\u0610-\u061a\u0620-\u0669\u066e-\u06d3\u06d5-\u06dc\u06df-\u06e8\u06ea-\u06fc\u06ff\u0710-\u074a\u074d-\u07b1\u07c0-\u07f5\u07fa\u0800-\u082d\u0840-\u085b\u08a0\u08a2-\u08ac\u08e4-\u08fe\u0900-\u0963\u0966-\u096f\u0971-\u0977\u0979-\u097f\u0981-\u0983\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bc-\u09c4\u09c7\u09c8\u09cb-\u09ce\u09d7\u09dc\u09dd\u09df-\u09e3\u09e6-\u09f1\u0a01-\u0a03\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a59-\u0a5c\u0a5e\u0a66-\u0a75\u0a81-\u0a83\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abc-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ad0\u0ae0-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3c-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5c\u0b5d\u0b5f-\u0b63\u0b66-\u0b6f\u0b71\u0b82\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd0\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c58\u0c59\u0c60-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbc-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0cde\u0ce0-\u0ce3\u0ce6-\u0cef\u0cf1\u0cf2\u0d02\u0d03\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d-\u0d44\u0d46-\u0d48\u0d4a-\u0d4e\u0d57\u0d60-\u0d63\u0d66-\u0d6f\u0d7a-\u0d7f\u0d82\u0d83\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e01-\u0e3a\u0e40-\u0e4e\u0e50-\u0e59\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb9\u0ebb-\u0ebd\u0ec0-\u0ec4\u0ec6\u0ec8-\u0ecd\u0ed0-\u0ed9\u0edc-\u0edf\u0f00\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e-\u0f47\u0f49-\u0f6c\u0f71-\u0f84\u0f86-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1049\u1050-\u109d\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u135d-\u135f\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176c\u176e-\u1770\u1772\u1773\u1780-\u17d3\u17d7\u17dc\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1820-\u1877\u1880-\u18aa\u18b0-\u18f5\u1900-\u191c\u1920-\u192b\u1930-\u193b\u1946-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u19d0-\u19d9\u1a00-\u1a1b\u1a20-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1aa7\u1b00-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1bf3\u1c00-\u1c37\u1c40-\u1c49\u1c4d-\u1c7d\u1cd0-\u1cd2\u1cd4-\u1cf6\u1d00-\u1de6\u1dfc-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u200c\u200d\u203f\u2040\u2054\u2071\u207f\u2090-\u209c\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d7f-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2de0-\u2dff\u2e2f\u3005-\u3007\u3021-\u302f\u3031-\u3035\u3038-\u303c\u3041-\u3096\u3099\u309a\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua62b\ua640-\ua66f\ua674-\ua67d\ua67f-\ua697\ua69f-\ua6f1\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua827\ua840-\ua873\ua880-\ua8c4\ua8d0-\ua8d9\ua8e0-\ua8f7\ua8fb\ua900-\ua92d\ua930-\ua953\ua960-\ua97c\ua980-\ua9c0\ua9cf-\ua9d9\uaa00-\uaa36\uaa40-\uaa4d\uaa50-\uaa59\uaa60-\uaa76\uaa7a\uaa7b\uaa80-\uaac2\uaadb-\uaadd\uaae0-\uaaef\uaaf2-\uaaf6\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabea\uabec\uabed\uabf0-\uabf9\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\ufe70-\ufe74\ufe76-\ufefc\uff10-\uff19\uff21-\uff3a\uff3f\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc]")},fc={name:"SyntaxTree",processComment:function(a){var b,c; -if(!(a.type===bc.Program&&a.body.length>0)){for(pc.trailingComments.length>0?pc.trailingComments[0].range[0]>=a.range[1]?(c=pc.trailingComments,pc.trailingComments=[]):pc.trailingComments.length=0:pc.bottomRightStack.length>0&&pc.bottomRightStack[pc.bottomRightStack.length-1].trailingComments&&pc.bottomRightStack[pc.bottomRightStack.length-1].trailingComments[0].range[0]>=a.range[1]&&(c=pc.bottomRightStack[pc.bottomRightStack.length-1].trailingComments,delete pc.bottomRightStack[pc.bottomRightStack.length-1].trailingComments);pc.bottomRightStack.length>0&&pc.bottomRightStack[pc.bottomRightStack.length-1].range[0]>=a.range[0];)b=pc.bottomRightStack.pop();b?b.leadingComments&&b.leadingComments[b.leadingComments.length-1].range[1]<=a.range[0]&&(a.leadingComments=b.leadingComments,delete b.leadingComments):pc.leadingComments.length>0&&pc.leadingComments[pc.leadingComments.length-1].range[1]<=a.range[0]&&(a.leadingComments=pc.leadingComments,pc.leadingComments=[]),c&&(a.trailingComments=c),pc.bottomRightStack.push(a)}},markEnd:function(a,b){return pc.range&&(a.range=[b.start,ic]),pc.loc&&(a.loc=new M(void 0===b.startLineNumber?b.lineNumber:b.startLineNumber,b.start-(void 0===b.startLineStart?b.lineStart:b.startLineStart),jc,ic-kc),this.postProcess(a)),pc.attachComment&&this.processComment(a),a},postProcess:function(a){return pc.source&&(a.loc.source=pc.source),a},createArrayExpression:function(a){return{type:bc.ArrayExpression,elements:a}},createAssignmentExpression:function(a,b,c){return{type:bc.AssignmentExpression,operator:a,left:b,right:c}},createBinaryExpression:function(a,b,c){var d="||"===a||"&&"===a?bc.LogicalExpression:bc.BinaryExpression;return{type:d,operator:a,left:b,right:c}},createBlockStatement:function(a){return{type:bc.BlockStatement,body:a}},createBreakStatement:function(a){return{type:bc.BreakStatement,label:a}},createCallExpression:function(a,b){return{type:bc.CallExpression,callee:a,arguments:b}},createCatchClause:function(a,b){return{type:bc.CatchClause,param:a,body:b}},createConditionalExpression:function(a,b,c){return{type:bc.ConditionalExpression,test:a,consequent:b,alternate:c}},createContinueStatement:function(a){return{type:bc.ContinueStatement,label:a}},createDebuggerStatement:function(){return{type:bc.DebuggerStatement}},createDoWhileStatement:function(a,b){return{type:bc.DoWhileStatement,body:a,test:b}},createEmptyStatement:function(){return{type:bc.EmptyStatement}},createExpressionStatement:function(a){return{type:bc.ExpressionStatement,expression:a}},createForStatement:function(a,b,c,d){return{type:bc.ForStatement,init:a,test:b,update:c,body:d}},createForInStatement:function(a,b,c){return{type:bc.ForInStatement,left:a,right:b,body:c,each:!1}},createFunctionDeclaration:function(a,b,c,d){return{type:bc.FunctionDeclaration,id:a,params:b,defaults:c,body:d,rest:null,generator:!1,expression:!1}},createFunctionExpression:function(a,b,c,d){return{type:bc.FunctionExpression,id:a,params:b,defaults:c,body:d,rest:null,generator:!1,expression:!1}},createIdentifier:function(a){return{type:bc.Identifier,name:a}},createIfStatement:function(a,b,c){return{type:bc.IfStatement,test:a,consequent:b,alternate:c}},createLabeledStatement:function(a,b){return{type:bc.LabeledStatement,label:a,body:b}},createLiteral:function(a){return{type:bc.Literal,value:a.value,raw:gc.slice(a.start,a.end)}},createMemberExpression:function(a,b,c){return{type:bc.MemberExpression,computed:"["===a,object:b,property:c}},createNewExpression:function(a,b){return{type:bc.NewExpression,callee:a,arguments:b}},createObjectExpression:function(a){return{type:bc.ObjectExpression,properties:a}},createPostfixExpression:function(a,b){return{type:bc.UpdateExpression,operator:a,argument:b,prefix:!1}},createProgram:function(a){return{type:bc.Program,body:a}},createProperty:function(a,b,c){return{type:bc.Property,key:b,value:c,kind:a}},createReturnStatement:function(a){return{type:bc.ReturnStatement,argument:a}},createSequenceExpression:function(a){return{type:bc.SequenceExpression,expressions:a}},createSwitchCase:function(a,b){return{type:bc.SwitchCase,test:a,consequent:b}},createSwitchStatement:function(a,b){return{type:bc.SwitchStatement,discriminant:a,cases:b}},createThisExpression:function(){return{type:bc.ThisExpression}},createThrowStatement:function(a){return{type:bc.ThrowStatement,argument:a}},createTryStatement:function(a,b,c,d){return{type:bc.TryStatement,block:a,guardedHandlers:b,handlers:c,finalizer:d}},createUnaryExpression:function(a,b){return"++"===a||"--"===a?{type:bc.UpdateExpression,operator:a,argument:b,prefix:!0}:{type:bc.UnaryExpression,operator:a,argument:b,prefix:!0}},createVariableDeclaration:function(a,b){return{type:bc.VariableDeclaration,declarations:a,kind:b}},createVariableDeclarator:function(a,b){return{type:bc.VariableDeclarator,id:a,init:b}},createWhileStatement:function(a,b){return{type:bc.WhileStatement,test:a,body:b}},createWithStatement:function(a,b){return{type:bc.WithStatement,object:a,body:b}}},a.version="1.2.2",a.tokenize=Yb,a.parse=Zb,a.Syntax=function(){var a,b={};"function"==typeof Object.create&&(b=Object.create(null));for(a in bc)bc.hasOwnProperty(a)&&(b[a]=bc[a]);return"function"==typeof Object.freeze&&Object.freeze(b),b}()})}(null),/*! - * falafel (c) James Halliday / MIT License - * https://github.com/substack/node-falafel - */ -function(a,b){function c(a,b,c){function d(b){c[a.range[0]]=b;for(var d=a.range[0]+1;dparseInt(b,10)}).forEach(function(b){e+=f+"['"+a+"']["+b+"]=0;\n"}),c&&_blanket._branchingArraySetup.sort(function(a,b){return a.line>b.line}).sort(function(a,b){return a.column>b.column}).forEach(function(b){b.file===a&&(e+="if (typeof "+f+"['"+a+"'].branchData["+b.line+"] === 'undefined'){\n",e+=f+"['"+a+"'].branchData["+b.line+"]=[];\n",e+="}",e+=f+"['"+a+"'].branchData["+b.line+"]["+b.column+"] = [];\n",e+=f+"['"+a+"'].branchData["+b.line+"]["+b.column+"].consequent = "+JSON.stringify(b.consequent)+";\n",e+=f+"['"+a+"'].branchData["+b.line+"]["+b.column+"].alternate = "+JSON.stringify(b.alternate)+";\n")}),e+="}"},_blockifyIf:function(a){if(c.indexOf(a.type)>-1){var b=a.consequent||a.body,d=a.alternate;d&&"BlockStatement"!==d.type&&d.update("{\n"+d.source()+"}\n"),b&&"BlockStatement"!==b.type&&b.update("{\n"+b.source()+"}\n")}},_trackBranch:function(a,b){var c=a.loc.start.line,d=a.loc.start.column;_blanket._branchingArraySetup.push({line:c,column:d,file:b,consequent:a.consequent.loc,alternate:a.alternate.loc});var e="_$branchFcn('"+b+"',"+c+","+d+","+a.test.source()+")?"+a.consequent.source()+":"+a.alternate.source();a.update(e)},_addTracking:function(a){var c=_blanket.getCovVar();return function(d){if(_blanket._blockifyIf(d),b.indexOf(d.type)>-1&&"LabeledStatement"!==d.parent.type){if(_blanket._checkDefs(d,a),"VariableDeclaration"===d.type&&("ForStatement"===d.parent.type||"ForInStatement"===d.parent.type))return;if(!d.loc||!d.loc.start)throw new Error("The instrumenter encountered a node with no location: "+Object.keys(d));d.update(c+"['"+a+"']["+d.loc.start.line+"]++;\n"+d.source()),_blanket._trackingArraySetup.push(d.loc.start.line)}else _blanket.options("branchTracking")&&"ConditionalExpression"===d.type&&_blanket._trackBranch(d,a)}},_checkDefs:function(a,b){if(inBrowser){if("VariableDeclaration"===a.type&&a.declarations&&a.declarations.forEach(function(c){if("window"===c.id.name)throw new Error("Instrumentation error, you cannot redefine the 'window' variable in "+b+":"+a.loc.start.line)}),"FunctionDeclaration"===a.type&&a.params&&a.params.forEach(function(c){if("window"===c.name)throw new Error("Instrumentation error, you cannot redefine the 'window' variable in "+b+":"+a.loc.start.line)}),"ExpressionStatement"===a.type&&a.expression&&a.expression.left&&a.expression.left.object&&a.expression.left.property&&a.expression.left.object.name+"."+a.expression.left.property.name===_blanket.getCovVar())throw new Error("Instrumentation error, you cannot redefine the coverage variable in "+b+":"+a.loc.start.line)}else if("ExpressionStatement"===a.type&&a.expression&&a.expression.left&&!a.expression.left.object&&!a.expression.left.property&&a.expression.left.name===_blanket.getCovVar())throw new Error("Instrumentation error, you cannot redefine the coverage variable in "+b+":"+a.loc.start.line)},setupCoverage:function(){e.instrumentation="blanket",e.stats={suites:0,tests:0,passes:0,pending:0,failures:0,start:new Date}},_checkIfSetup:function(){if(!e.stats)throw new Error("You must call blanket.setupCoverage() first.")},onTestStart:function(){_blanket.options("debug")&&console.log("BLANKET-Test event started"),this._checkIfSetup(),e.stats.tests++,e.stats.pending++},onTestDone:function(a,b){this._checkIfSetup(),b===a?e.stats.passes++:e.stats.failures++,e.stats.pending--},onModuleStart:function(){this._checkIfSetup(),e.stats.suites++},onTestsDone:function(){_blanket.options("debug")&&console.log("BLANKET-Test event done"),this._checkIfSetup(),e.stats.end=new Date,inBrowser?this.report(e):(_blanket.options("branchTracking")||delete(inBrowser?window:global)[_blanket.getCovVar()].branchFcn,this.options("reporter").call(this,e))}}}(),function(a){var b=a.options;a.extend({outstandingRequireFiles:[],options:function(c,d){var e={};if("string"!=typeof c)b(c),e=c;else{if("undefined"==typeof d)return b(c);b(c,d),e[c]=d}e.adapter&&a._loadFile(e.adapter),e.loader&&a._loadFile(e.loader)},requiringFile:function(b,c){"undefined"==typeof b?a.outstandingRequireFiles=[]:"undefined"==typeof c?a.outstandingRequireFiles.push(b):a.outstandingRequireFiles.splice(a.outstandingRequireFiles.indexOf(b),1)},requireFilesLoaded:function(){return 0===a.outstandingRequireFiles.length},showManualLoader:function(){if(!document.getElementById("blanketLoaderDialog")){var a="
    ";a+=" 
    ",a+="
    ",a+="
    ",a+="Error: Blanket.js encountered a cross origin request error while instrumenting the source files. ",a+="

    This is likely caused by the source files being referenced locally (using the file:// protocol). ",a+="

    Some solutions include starting Chrome with special flags, running a server locally, or using a browser without these CORS restrictions (Safari).",a+="
    ","undefined"!=typeof FileReader&&(a+="
    Or, try the experimental loader. When prompted, simply click on the directory containing all the source files you want covered.",a+="Start Loader",a+=""),a+="
    Close",a+="
    ",a+="
    ";var b=".blanketDialogWrapper {";b+="display:block;",b+="position:fixed;",b+="z-index:40001; }",b+=".blanketDialogOverlay {",b+="position:fixed;",b+="width:100%;",b+="height:100%;",b+="background-color:black;",b+="opacity:.5; ",b+="-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=50)'; ",b+="filter:alpha(opacity=50); ",b+="z-index:40001; }",b+=".blanketDialogVerticalOffset { ",b+="position:fixed;",b+="top:30%;",b+="width:100%;",b+="z-index:40002; }",b+=".blanketDialogBox { ",b+="width:405px; ",b+="position:relative;",b+="margin:0 auto;",b+="background-color:white;",b+="padding:10px;",b+="border:1px solid black; }";var c=document.createElement("style");c.innerHTML=b,document.head.appendChild(c);var d=document.createElement("div");d.id="blanketLoaderDialog",d.className="blanketDialogWrapper",d.innerHTML=a,document.body.insertBefore(d,document.body.firstChild)}},manualFileLoader:function(a){function b(a){var b=new FileReader;b.onload=g,b.readAsText(a)}var c=Array.prototype.slice;a=c.call(a).filter(function(a){return""!==a.type});var d=a.length-1,e=0,f={};sessionStorage.blanketSessionLoader&&(f=JSON.parse(sessionStorage.blanketSessionLoader));var g=function(c){var g=c.currentTarget.result,h=a[e],i=h.webkitRelativePath&&""!==h.webkitRelativePath?h.webkitRelativePath:h.name;f[i]=g,e++,e===d?(sessionStorage.setItem("blanketSessionLoader",JSON.stringify(f)),document.location.reload()):b(a[e])};b(a[e])},_loadFile:function(b){if("undefined"!=typeof b){var c=new XMLHttpRequest;c.open("GET",b,!1),c.send(),a._addScript(c.responseText)}},_addScript:function(a){var b=document.createElement("script");b.type="text/javascript",b.text=a,(document.body||document.getElementsByTagName("head")[0]).appendChild(b)},hasAdapter:function(){return null!==a.options("adapter")},report:function(b){document.getElementById("blanketLoaderDialog")||(a.blanketSession=null),b.files=window._$blanket;blanket.options("commonJS")?blanket._commonjs.require:window.require;if(!b.files||!Object.keys(b.files).length)return void(a.options("debug")&&console.log("BLANKET-Reporting No files were instrumented."));if("undefined"!=typeof b.files.branchFcn&&delete b.files.branchFcn,"string"==typeof a.options("reporter"))a._loadFile(a.options("reporter")),a.customReporter(b,a.options("reporter_options"));else if("function"==typeof a.options("reporter"))a.options("reporter")(b,a.options("reporter_options"));else{if("function"!=typeof a.defaultReporter)throw new Error("no reporter defined.");a.defaultReporter(b,a.options("reporter_options"))}},_bindStartTestRunner:function(a,b){a?a(b):window.addEventListener("load",b,!1)},_loadSourceFiles:function(b){blanket.options("commonJS")?blanket._commonjs.require:window.require;a.options("debug")&&console.log("BLANKET-Collecting page scripts");var c=a.utils.collectPageScripts();if(0===c.length)b();else{sessionStorage.blanketSessionLoader&&(a.blanketSession=JSON.parse(sessionStorage.blanketSessionLoader)),c.forEach(function(b){a.utils.cache[b]={loaded:!1}});var d=-1;a.utils.loadAll(function(a){return a?"undefined"!=typeof c[d+1]:(d++,d>=c.length?null:c[d])},b)}},beforeStartTestRunner:function(b){b=b||{},b.checkRequirejs="undefined"==typeof b.checkRequirejs?!0:b.checkRequirejs,b.callback=b.callback||function(){},b.coverage="undefined"==typeof b.coverage?!0:b.coverage,b.coverage?a._bindStartTestRunner(b.bindEvent,function(){a._loadSourceFiles(function(){var c=function(){return b.condition?b.condition():a.requireFilesLoaded()},d=function(){if(c()){a.options("debug")&&console.log("BLANKET-All files loaded, init start test runner callback.");var e=a.options("testReadyCallback");e?"function"==typeof e?e(b.callback):"string"==typeof e&&(a._addScript(e),b.callback()):b.callback()}else setTimeout(d,13)};d()})}):b.callback()},utils:{qualifyURL:function(a){var b=document.createElement("a");return b.href=a,b.href}}})}(blanket),blanket.defaultReporter=function(a){function b(a){var b=document.getElementById(a);b.style.display="block"===b.style.display?"none":"block"}function c(a){return a.replace(/\&/g,"&").replace(//g,">").replace(/\"/g,""").replace(/\'/g,"'")}function d(a,b){var c=b?0:1;return"undefined"==typeof a||null===typeof a||"undefined"==typeof a[c]?!1:a[c].length>0}function e(a,b,f,g,h){var i="",j="";if(q.length>0)if(i+="",q[0][0].end.line===h){if(i+=c(b.slice(0,q[0][0].end.column))+"",b=b.slice(q[0][0].end.column),q.shift(),q.length>0)if(i+="",q[0][0].end.line===h){if(i+=c(b.slice(0,q[0][0].end.column))+"",b=b.slice(q[0][0].end.column),q.shift(),!f)return{src:i+c(b),cols:f}}else{if(!f)return{src:i+c(b)+"",cols:f};j=""}else if(!f)return{src:i+c(b),cols:f}}else{if(!f)return{src:i+c(b)+"",cols:f};j=""}var k=f[a],l=k.consequent;if(l.start.line>h)q.unshift([k.alternate,k]),q.unshift([l,k]),b=c(b);else{var m="";if(i+=c(b.slice(0,l.start.column-g))+m,f.length>a+1&&f[a+1].consequent.start.line===h&&f[a+1].consequent.start.column-g";var o=k.alternate;if(o.start.line>h)i+=c(b.slice(l.end.column-g)),q.unshift([o,k]);else{if(i+=c(b.slice(l.end.column-g,o.start.column-g)),m="",i+=m,f.length>a+1&&f[a+1].consequent.start.line===h&&f[a+1].consequent.start.column-g",i+=c(b.slice(o.end.column-g)),b=i}}return{src:b+j,cols:f}}var f="#blanket-main {margin:2px;background:#EEE;color:#333;clear:both;font-family:'Helvetica Neue Light', 'HelveticaNeue-Light', 'Helvetica Neue', Calibri, Helvetica, Arial, sans-serif; font-size:17px;} #blanket-main a {color:#333;text-decoration:none;} #blanket-main a:hover {text-decoration:underline;} .blanket {margin:0;padding:5px;clear:both;border-bottom: 1px solid #FFFFFF;} .bl-error {color:red;}.bl-success {color:#5E7D00;} .bl-file{width:auto;} .bl-cl{float:left;} .blanket div.rs {margin-left:50px; width:150px; float:right} .bl-nb {padding-right:10px;} #blanket-main a.bl-logo {color: #EB1764;cursor: pointer;font-weight: bold;text-decoration: none} .bl-source{ overflow-x:scroll; background-color: #FFFFFF; border: 1px solid #CBCBCB; color: #363636; margin: 25px 20px; width: 80%;} .bl-source div{white-space: pre;font-family: monospace;} .bl-source > div > span:first-child{background-color: #EAEAEA;color: #949494;display: inline-block;padding: 0 10px;text-align: center;width: 30px;} .bl-source .miss{background-color:#e6c3c7} .bl-source span.branchWarning{color:#000;background-color:yellow;} .bl-source span.branchOkay{color:#000;background-color:transparent;}",g=60,h=document.head,i=0,j=document.body,k=Object.keys(a.files).some(function(b){return"undefined"!=typeof a.files[b].branchData}),l="
    results
    Coverage (%)
    Covered/Total Smts.
    "+(k?"
    Covered/Total Branches
    ":"")+"
    ",m="
    {{fileNumber}}.{{file}}
    {{percentage}} %
    {{numberCovered}}/{{totalSmts}}
    "+(k?"
    {{passedBranches}}/{{totalBranches}}
    ":"")+"
    ";grandTotalTemplate="
    {{rowTitle}}
    {{percentage}} %
    {{numberCovered}}/{{totalSmts}}
    "+(k?"
    {{passedBranches}}/{{totalBranches}}
    ":"")+"
    ";var n=document.createElement("script");n.type="text/javascript",n.text=b.toString().replace("function "+b.name,"function blanket_toggleSource"),j.appendChild(n);var o=function(a,b){return Math.round(a/b*100*100)/100},p=function(a,b,c){var d=document.createElement(a);d.innerHTML=c,b.appendChild(d)},q=[],r=function(a){return"undefined"!=typeof a},s=a.files,t={totalSmts:0,numberOfFilesCovered:0,passedBranches:0,totalBranches:0,moduleTotalStatements:{},moduleTotalCoveredStatements:{},moduleTotalBranches:{},moduleTotalCoveredBranches:{}},u=_blanket.options("modulePattern"),v=u?new RegExp(u):null;for(var w in s)if(s.hasOwnProperty(w)){i++;var x,y=s[w],z=0,A=0,B=[];for(x=0;x0||"undefined"!=typeof y.branchData)if("undefined"!=typeof y.branchData[x+1]){var D=y.branchData[x+1].filter(r),E=0;C=e(E,C,D,0,x+1).src}else C=q.length?e(0,C,null,0,x+1).src:c(C);else C=c(C);var F="";y[x+1]?(A+=1,z+=1,F="hit"):0===y[x+1]&&(z++,F="miss"),B[x+1]="
    "+(x+1)+""+C+"
    "}t.totalSmts+=z,t.numberOfFilesCovered+=A;var G=0,H=0;if("undefined"!=typeof y.branchData)for(var I=0;I0&&"undefined"!=typeof y.branchData[I][J][1]&&y.branchData[I][J][1].length>0&&H++);if(t.passedBranches+=H,t.totalBranches+=G,v){var K=w.match(v)[1];t.moduleTotalStatements.hasOwnProperty(K)||(t.moduleTotalStatements[K]=0,t.moduleTotalCoveredStatements[K]=0),t.moduleTotalStatements[K]+=z,t.moduleTotalCoveredStatements[K]+=A,t.moduleTotalBranches.hasOwnProperty(K)||(t.moduleTotalBranches[K]=0,t.moduleTotalCoveredBranches[K]=0),t.moduleTotalBranches[K]+=G,t.moduleTotalCoveredBranches[K]+=H}var L=o(A,z),M=m.replace("{{file}}",w).replace("{{percentage}}",L).replace("{{numberCovered}}",A).replace(/\{\{fileNumber\}\}/g,i).replace("{{totalSmts}}",z).replace("{{totalBranches}}",G).replace("{{passedBranches}}",H).replace("{{source}}",B.join(" "));M=g>L?M.replace("{{statusclass}}","bl-error"):M.replace("{{statusclass}}","bl-success"),l+=M}var N=function(a,b,c,d,e){var f=o(b,a),h=g>f?"bl-error":"bl-success",i=e?"Total for module: "+e:"Global total",j=grandTotalTemplate.replace("{{rowTitle}}",i).replace("{{percentage}}",f).replace("{{numberCovered}}",b).replace("{{totalSmts}}",a).replace("{{passedBranches}}",d).replace("{{totalBranches}}",c).replace("{{statusclass}}",h);l+=j};if(v)for(var O in t.moduleTotalStatements)if(t.moduleTotalStatements.hasOwnProperty(O)){var P=t.moduleTotalStatements[O],Q=t.moduleTotalCoveredStatements[O],R=t.moduleTotalBranches[O],S=t.moduleTotalCoveredBranches[O];N(P,Q,R,S,O)}N(t.totalSmts,t.numberOfFilesCovered,t.totalBranches,t.passedBranches,null),l+="
    ",p("style",h,f),document.getElementById("blanket-main")?document.getElementById("blanket-main").innerHTML=l.slice(23,-6):p("div",j,l)},function(){var a={},b=Array.prototype.slice,c=b.call(document.scripts);b.call(c[c.length-1].attributes).forEach(function(b){if("data-cover-only"===b.nodeName&&(a.filter=b.nodeValue),"data-cover-never"===b.nodeName&&(a.antifilter=b.nodeValue),"data-cover-reporter"===b.nodeName&&(a.reporter=b.nodeValue),"data-cover-adapter"===b.nodeName&&(a.adapter=b.nodeValue),"data-cover-loader"===b.nodeName&&(a.loader=b.nodeValue),"data-cover-timeout"===b.nodeName&&(a.timeout=b.nodeValue),"data-cover-modulepattern"===b.nodeName&&(a.modulePattern=b.nodeValue),"data-cover-reporter-options"===b.nodeName)try{a.reporter_options=JSON.parse(b.nodeValue)}catch(c){if(blanket.options("debug"))throw new Error("Invalid reporter options. Must be a valid stringified JSON object.")}if("data-cover-testReadyCallback"===b.nodeName&&(a.testReadyCallback=b.nodeValue),"data-cover-customVariable"===b.nodeName&&(a.customVariable=b.nodeValue),"data-cover-flags"===b.nodeName){var d=" "+b.nodeValue+" ";d.indexOf(" ignoreError ")>-1&&(a.ignoreScriptError=!0),d.indexOf(" autoStart ")>-1&&(a.autoStart=!0),d.indexOf(" ignoreCors ")>-1&&(a.ignoreCors=!0),d.indexOf(" branchTracking ")>-1&&(a.branchTracking=!0),d.indexOf(" sourceURL ")>-1&&(a.sourceURL=!0),d.indexOf(" debug ")>-1&&(a.debug=!0),d.indexOf(" engineOnly ")>-1&&(a.engineOnly=!0),d.indexOf(" commonJS ")>-1&&(a.commonJS=!0),d.indexOf(" instrumentCache ")>-1&&(a.instrumentCache=!0)}}),blanket.options(a),"undefined"!=typeof requirejs&&blanket.options("existingRequireJS",!0),blanket.options("commonJS")&&(blanket._commonjs={})}(),function(a){a.extend({utils:{normalizeBackslashes:function(a){return a.replace(/\\/g,"/")},matchPatternAttribute:function(b,c){if("string"==typeof c){if(0===c.indexOf("[")){var d=c.slice(1,c.length-1).split(",");return d.some(function(c){return a.utils.matchPatternAttribute(b,a.utils.normalizeBackslashes(c.slice(1,-1)))})}if(0===c.indexOf("//")){var e=c.slice(2,c.lastIndexOf("/")),f=c.slice(c.lastIndexOf("/")+1),g=new RegExp(e,f);return g.test(b)}return 0===c.indexOf("#")?window[c.slice(1)].call(window,b):b.indexOf(a.utils.normalizeBackslashes(c))>-1}return c instanceof Array?c.some(function(c){return a.utils.matchPatternAttribute(b,c)}):c instanceof RegExp?c.test(b):"function"==typeof c?c.call(window,b):void 0},blanketEval:function(b){a._addScript(b)},collectPageScripts:function(){var b=Array.prototype.slice,c=(b.call(document.scripts),[]),d=[],e=a.options("filter");if(null!=e){var f=a.options("antifilter");c=b.call(document.scripts).filter(function(c){return 1===b.call(c.attributes).filter(function(b){return"src"===b.nodeName&&a.utils.matchPatternAttribute(b.nodeValue,e)&&("undefined"==typeof f||!a.utils.matchPatternAttribute(b.nodeValue,f))}).length})}else c=b.call(document.querySelectorAll("script[data-cover]"));return d=c.map(function(c){return a.utils.qualifyURL(b.call(c.attributes).filter(function(a){return"src"===a.nodeName})[0].nodeValue)}),e||a.options("filter","['"+d.join("','")+"']"),d},loadAll:function(b,c){var d=b(),e=a.utils.scriptIsLoaded(d,a.utils.ifOrdered,b,c);if(a.utils.cache[d]&&a.utils.cache[d].loaded)e();else{var f=function(){a.options("debug")&&console.log("BLANKET-Mark script:"+d+", as loaded and move to next script."),e()},g=function(b){a.options("debug")&&console.log("BLANKET-File loading finished"),"undefined"!=typeof b&&(a.options("debug")&&console.log("BLANKET-Add file to DOM."),a._addScript(b)),f()};a.utils.attachScript({url:d},function(b){a.utils.processFile(b,d,g,g)})}},attachScript:function(b,c){var d=a.options("timeout")||3e3;setTimeout(function(){if(!a.utils.cache[b.url].loaded)throw new Error("error loading source script")},d),a.utils.getFile(b.url,c,function(){throw new Error("error loading source script")})},ifOrdered:function(b,c){var d=b(!0);d?a.utils.loadAll(b,c):c(new Error("Error in loading chain."))},scriptIsLoaded:function(b,c,d,e){return a.options("debug")&&console.log("BLANKET-Returning function"),function(){a.options("debug")&&console.log("BLANKET-Marking file as loaded: "+b),a.utils.cache[b].loaded=!0,a.utils.allLoaded()?(a.options("debug")&&console.log("BLANKET-All files loaded"),e()):c&&(a.options("debug")&&console.log("BLANKET-Load next file."),c(d,e))}},cache:{},allLoaded:function(){for(var b=Object.keys(a.utils.cache),c=0;cb;b+=1){c=progIds[b];try{new ActiveXObject(c);break}catch(d){}}this.createXhr=function(){return new a(c)}}},craeteXhr:function(){throw new Error("cacheXhrConstructor is supposed to overwrite this function.")},getFile:function(b,c,d,e){var f=!1;if(a.blanketSession)for(var g=Object.keys(a.blanketSession),h=0;h-1)return c(a.blanketSession[i]),void(f=!0)}if(!f){var j=a.utils.createXhr();j.open("GET",b,!0),e&&e(j,b),j.onreadystatechange=function(){var a,e;4===j.readyState&&(a=j.status,a>399&&600>a?(e=new Error(b+" HTTP status: "+a),e.xhr=j,d(e)):c(j.responseText))};try{j.send(null)}catch(k){if(!k.code||101!==k.code&&1012!==k.code||a.options("ignoreCors")!==!1)throw k;a.showManualLoader()}}}}}),function(){var b=(blanket.options("commonJS")?blanket._commonjs.require:window.require,blanket.options("commonJS")?blanket._commonjs.requirejs:window.requirejs);!a.options("engineOnly")&&a.options("existingRequireJS")&&(a.utils.oldloader=b.load,b.load=function(b,c,d){a.requiringFile(d),a.utils.getFile(d,function(e){a.utils.processFile(e,d,function(){b.completeLoad(c)},function(){a.utils.oldloader(b,c,d)})},function(b){throw a.requiringFile(),b})}),a.utils.cacheXhrConstructor()}()}(blanket),function(){if("undefined"!=typeof QUnit){var a=function(){return window.QUnit.config.queue.length>0&&blanket.noConflict().requireFilesLoaded()};QUnit.config.urlConfig[0].tooltip?(QUnit.config.urlConfig.push({id:"coverage",label:"Enable coverage",tooltip:"Enable code coverage."}),QUnit.urlParams.coverage||blanket.options("autoStart")?(QUnit.begin(function(){blanket.noConflict().setupCoverage()}),QUnit.done(function(){blanket.noConflict().onTestsDone()}),QUnit.moduleStart(function(){blanket.noConflict().onModuleStart()}),QUnit.testStart(function(){blanket.noConflict().onTestStart()}),QUnit.testDone(function(a){blanket.noConflict().onTestDone(a.total,a.passed)}),blanket.noConflict().beforeStartTestRunner({condition:a,callback:function(){(!blanket.options("existingRequireJS")||blanket.options("autoStart"))&&QUnit.start()}})):(blanket.options("existingRequireJS")&&(requirejs.load=_blanket.utils.oldloader),blanket.noConflict().beforeStartTestRunner({condition:a,callback:function(){(!blanket.options("existingRequireJS")||blanket.options("autoStart"))&&QUnit.start()},coverage:!1}))):(QUnit.begin=function(){blanket.noConflict().setupCoverage()},QUnit.done=function(){blanket.noConflict().onTestsDone()},QUnit.moduleStart=function(){blanket.noConflict().onModuleStart()},QUnit.testStart=function(){blanket.noConflict().onTestStart()},QUnit.testDone=function(a){blanket.noConflict().onTestDone(a.total,a.passed)},blanket.beforeStartTestRunner({condition:a,callback:QUnit.start}))}}(); diff --git a/js_tests/tests.html b/js_tests/tests.html index 13c6bcd6938c..38246ef15fa5 100644 --- a/js_tests/tests.html +++ b/js_tests/tests.html @@ -42,7 +42,7 @@ - + From 307de7d9e80e1148685b981eede0bd2fdbe91d46 Mon Sep 17 00:00:00 2001 From: Pyie Zone Date: Mon, 6 Jun 2016 09:23:46 -0400 Subject: [PATCH 644/756] [1.9.x] Refs #16501, #26474 -- Added xregexp.js source file. Backport of 054e74420b7a31bac67d4993b462eea7b9b7a5ba from master --- .eslintignore | 3 +- django/contrib/admin/options.py | 2 +- .../static/admin/js/vendor/xregexp/xregexp.js | 2308 +++++++++++++++++ 3 files changed, 2311 insertions(+), 2 deletions(-) create mode 100755 django/contrib/admin/static/admin/js/vendor/xregexp/xregexp.js diff --git a/.eslintignore b/.eslintignore index cb0447577ba7..7db926630514 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ -**/{*.min,jquery}.js +**/*.min.js +**/vendor/**/*.js django/contrib/gis/templates/**/*.js node_modules/**.js diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 526400580a5b..d97661e44729 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -572,7 +572,7 @@ def media(self): 'actions%s.js' % extra, 'urlify.js', 'prepopulate%s.js' % extra, - 'vendor/xregexp/xregexp.min.js', + 'vendor/xregexp/xregexp%s.js' % extra, ] return forms.Media(js=[static('admin/js/%s' % url) for url in js]) diff --git a/django/contrib/admin/static/admin/js/vendor/xregexp/xregexp.js b/django/contrib/admin/static/admin/js/vendor/xregexp/xregexp.js new file mode 100755 index 000000000000..7a4454e6902e --- /dev/null +++ b/django/contrib/admin/static/admin/js/vendor/xregexp/xregexp.js @@ -0,0 +1,2308 @@ + +/***** xregexp.js *****/ + +/*! + * XRegExp v2.0.0 + * (c) 2007-2012 Steven Levithan + * MIT License + */ + +/** + * XRegExp provides augmented, extensible JavaScript regular expressions. You get new syntax, + * flags, and methods beyond what browsers support natively. XRegExp is also a regex utility belt + * with tools to make your client-side grepping simpler and more powerful, while freeing you from + * worrying about pesky cross-browser inconsistencies and the dubious `lastIndex` property. See + * XRegExp's documentation (http://xregexp.com/) for more details. + * @module xregexp + * @requires N/A + */ +var XRegExp; + +// Avoid running twice; that would reset tokens and could break references to native globals +XRegExp = XRegExp || (function (undef) { + "use strict"; + +/*-------------------------------------- + * Private variables + *------------------------------------*/ + + var self, + addToken, + add, + +// Optional features; can be installed and uninstalled + features = { + natives: false, + extensibility: false + }, + +// Store native methods to use and restore ("native" is an ES3 reserved keyword) + nativ = { + exec: RegExp.prototype.exec, + test: RegExp.prototype.test, + match: String.prototype.match, + replace: String.prototype.replace, + split: String.prototype.split + }, + +// Storage for fixed/extended native methods + fixed = {}, + +// Storage for cached regexes + cache = {}, + +// Storage for addon tokens + tokens = [], + +// Token scopes + defaultScope = "default", + classScope = "class", + +// Regexes that match native regex syntax + nativeTokens = { + // Any native multicharacter token in default scope (includes octals, excludes character classes) + "default": /^(?:\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\d*|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S])|\(\?[:=!]|[?*+]\?|{\d+(?:,\d*)?}\??)/, + // Any native multicharacter token in character class scope (includes octals) + "class": /^(?:\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S]))/ + }, + +// Any backreference in replacement strings + replacementToken = /\$(?:{([\w$]+)}|(\d\d?|[\s\S]))/g, + +// Any character with a later instance in the string + duplicateFlags = /([\s\S])(?=[\s\S]*\1)/g, + +// Any greedy/lazy quantifier + quantifier = /^(?:[?*+]|{\d+(?:,\d*)?})\??/, + +// Check for correct `exec` handling of nonparticipating capturing groups + compliantExecNpcg = nativ.exec.call(/()??/, "")[1] === undef, + +// Check for flag y support (Firefox 3+) + hasNativeY = RegExp.prototype.sticky !== undef, + +// Used to kill infinite recursion during XRegExp construction + isInsideConstructor = false, + +// Storage for known flags, including addon flags + registeredFlags = "gim" + (hasNativeY ? "y" : ""); + +/*-------------------------------------- + * Private helper functions + *------------------------------------*/ + +/** + * Attaches XRegExp.prototype properties and named capture supporting data to a regex object. + * @private + * @param {RegExp} regex Regex to augment. + * @param {Array} captureNames Array with capture names, or null. + * @param {Boolean} [isNative] Whether the regex was created by `RegExp` rather than `XRegExp`. + * @returns {RegExp} Augmented regex. + */ + function augment(regex, captureNames, isNative) { + var p; + // Can't auto-inherit these since the XRegExp constructor returns a nonprimitive value + for (p in self.prototype) { + if (self.prototype.hasOwnProperty(p)) { + regex[p] = self.prototype[p]; + } + } + regex.xregexp = {captureNames: captureNames, isNative: !!isNative}; + return regex; + } + +/** + * Returns native `RegExp` flags used by a regex object. + * @private + * @param {RegExp} regex Regex to check. + * @returns {String} Native flags in use. + */ + function getNativeFlags(regex) { + //return nativ.exec.call(/\/([a-z]*)$/i, String(regex))[1]; + return (regex.global ? "g" : "") + + (regex.ignoreCase ? "i" : "") + + (regex.multiline ? "m" : "") + + (regex.extended ? "x" : "") + // Proposed for ES6, included in AS3 + (regex.sticky ? "y" : ""); // Proposed for ES6, included in Firefox 3+ + } + +/** + * Copies a regex object while preserving special properties for named capture and augmenting with + * `XRegExp.prototype` methods. The copy has a fresh `lastIndex` property (set to zero). Allows + * adding and removing flags while copying the regex. + * @private + * @param {RegExp} regex Regex to copy. + * @param {String} [addFlags] Flags to be added while copying the regex. + * @param {String} [removeFlags] Flags to be removed while copying the regex. + * @returns {RegExp} Copy of the provided regex, possibly with modified flags. + */ + function copy(regex, addFlags, removeFlags) { + if (!self.isRegExp(regex)) { + throw new TypeError("type RegExp expected"); + } + var flags = nativ.replace.call(getNativeFlags(regex) + (addFlags || ""), duplicateFlags, ""); + if (removeFlags) { + // Would need to escape `removeFlags` if this was public + flags = nativ.replace.call(flags, new RegExp("[" + removeFlags + "]+", "g"), ""); + } + if (regex.xregexp && !regex.xregexp.isNative) { + // Compiling the current (rather than precompilation) source preserves the effects of nonnative source flags + regex = augment(self(regex.source, flags), + regex.xregexp.captureNames ? regex.xregexp.captureNames.slice(0) : null); + } else { + // Augment with `XRegExp.prototype` methods, but use native `RegExp` (avoid searching for special tokens) + regex = augment(new RegExp(regex.source, flags), null, true); + } + return regex; + } + +/* + * Returns the last index at which a given value can be found in an array, or `-1` if it's not + * present. The array is searched backwards. + * @private + * @param {Array} array Array to search. + * @param {*} value Value to locate in the array. + * @returns {Number} Last zero-based index at which the item is found, or -1. + */ + function lastIndexOf(array, value) { + var i = array.length; + if (Array.prototype.lastIndexOf) { + return array.lastIndexOf(value); // Use the native method if available + } + while (i--) { + if (array[i] === value) { + return i; + } + } + return -1; + } + +/** + * Determines whether an object is of the specified type. + * @private + * @param {*} value Object to check. + * @param {String} type Type to check for, in lowercase. + * @returns {Boolean} Whether the object matches the type. + */ + function isType(value, type) { + return Object.prototype.toString.call(value).toLowerCase() === "[object " + type + "]"; + } + +/** + * Prepares an options object from the given value. + * @private + * @param {String|Object} value Value to convert to an options object. + * @returns {Object} Options object. + */ + function prepareOptions(value) { + value = value || {}; + if (value === "all" || value.all) { + value = {natives: true, extensibility: true}; + } else if (isType(value, "string")) { + value = self.forEach(value, /[^\s,]+/, function (m) { + this[m] = true; + }, {}); + } + return value; + } + +/** + * Runs built-in/custom tokens in reverse insertion order, until a match is found. + * @private + * @param {String} pattern Original pattern from which an XRegExp object is being built. + * @param {Number} pos Position to search for tokens within `pattern`. + * @param {Number} scope Current regex scope. + * @param {Object} context Context object assigned to token handler functions. + * @returns {Object} Object with properties `output` (the substitution string returned by the + * successful token handler) and `match` (the token's match array), or null. + */ + function runTokens(pattern, pos, scope, context) { + var i = tokens.length, + result = null, + match, + t; + // Protect against constructing XRegExps within token handler and trigger functions + isInsideConstructor = true; + // Must reset `isInsideConstructor`, even if a `trigger` or `handler` throws + try { + while (i--) { // Run in reverse order + t = tokens[i]; + if ((t.scope === "all" || t.scope === scope) && (!t.trigger || t.trigger.call(context))) { + t.pattern.lastIndex = pos; + match = fixed.exec.call(t.pattern, pattern); // Fixed `exec` here allows use of named backreferences, etc. + if (match && match.index === pos) { + result = { + output: t.handler.call(context, match, scope), + match: match + }; + break; + } + } + } + } catch (err) { + throw err; + } finally { + isInsideConstructor = false; + } + return result; + } + +/** + * Enables or disables XRegExp syntax and flag extensibility. + * @private + * @param {Boolean} on `true` to enable; `false` to disable. + */ + function setExtensibility(on) { + self.addToken = addToken[on ? "on" : "off"]; + features.extensibility = on; + } + +/** + * Enables or disables native method overrides. + * @private + * @param {Boolean} on `true` to enable; `false` to disable. + */ + function setNatives(on) { + RegExp.prototype.exec = (on ? fixed : nativ).exec; + RegExp.prototype.test = (on ? fixed : nativ).test; + String.prototype.match = (on ? fixed : nativ).match; + String.prototype.replace = (on ? fixed : nativ).replace; + String.prototype.split = (on ? fixed : nativ).split; + features.natives = on; + } + +/*-------------------------------------- + * Constructor + *------------------------------------*/ + +/** + * Creates an extended regular expression object for matching text with a pattern. Differs from a + * native regular expression in that additional syntax and flags are supported. The returned object + * is in fact a native `RegExp` and works with all native methods. + * @class XRegExp + * @constructor + * @param {String|RegExp} pattern Regex pattern string, or an existing `RegExp` object to copy. + * @param {String} [flags] Any combination of flags: + *
  • `g` - global + *
  • `i` - ignore case + *
  • `m` - multiline anchors + *
  • `n` - explicit capture + *
  • `s` - dot matches all (aka singleline) + *
  • `x` - free-spacing and line comments (aka extended) + *
  • `y` - sticky (Firefox 3+ only) + * Flags cannot be provided when constructing one `RegExp` from another. + * @returns {RegExp} Extended regular expression object. + * @example + * + * // With named capture and flag x + * date = XRegExp('(? [0-9]{4}) -? # year \n\ + * (? [0-9]{2}) -? # month \n\ + * (? [0-9]{2}) # day ', 'x'); + * + * // Passing a regex object to copy it. The copy maintains special properties for named capture, + * // is augmented with `XRegExp.prototype` methods, and has a fresh `lastIndex` property (set to + * // zero). Native regexes are not recompiled using XRegExp syntax. + * XRegExp(/regex/); + */ + self = function (pattern, flags) { + if (self.isRegExp(pattern)) { + if (flags !== undef) { + throw new TypeError("can't supply flags when constructing one RegExp from another"); + } + return copy(pattern); + } + // Tokens become part of the regex construction process, so protect against infinite recursion + // when an XRegExp is constructed within a token handler function + if (isInsideConstructor) { + throw new Error("can't call the XRegExp constructor within token definition functions"); + } + + var output = [], + scope = defaultScope, + tokenContext = { + hasNamedCapture: false, + captureNames: [], + hasFlag: function (flag) { + return flags.indexOf(flag) > -1; + } + }, + pos = 0, + tokenResult, + match, + chr; + pattern = pattern === undef ? "" : String(pattern); + flags = flags === undef ? "" : String(flags); + + if (nativ.match.call(flags, duplicateFlags)) { // Don't use test/exec because they would update lastIndex + throw new SyntaxError("invalid duplicate regular expression flag"); + } + // Strip/apply leading mode modifier with any combination of flags except g or y: (?imnsx) + pattern = nativ.replace.call(pattern, /^\(\?([\w$]+)\)/, function ($0, $1) { + if (nativ.test.call(/[gy]/, $1)) { + throw new SyntaxError("can't use flag g or y in mode modifier"); + } + flags = nativ.replace.call(flags + $1, duplicateFlags, ""); + return ""; + }); + self.forEach(flags, /[\s\S]/, function (m) { + if (registeredFlags.indexOf(m[0]) < 0) { + throw new SyntaxError("invalid regular expression flag " + m[0]); + } + }); + + while (pos < pattern.length) { + // Check for custom tokens at the current position + tokenResult = runTokens(pattern, pos, scope, tokenContext); + if (tokenResult) { + output.push(tokenResult.output); + pos += (tokenResult.match[0].length || 1); + } else { + // Check for native tokens (except character classes) at the current position + match = nativ.exec.call(nativeTokens[scope], pattern.slice(pos)); + if (match) { + output.push(match[0]); + pos += match[0].length; + } else { + chr = pattern.charAt(pos); + if (chr === "[") { + scope = classScope; + } else if (chr === "]") { + scope = defaultScope; + } + // Advance position by one character + output.push(chr); + ++pos; + } + } + } + + return augment(new RegExp(output.join(""), nativ.replace.call(flags, /[^gimy]+/g, "")), + tokenContext.hasNamedCapture ? tokenContext.captureNames : null); + }; + +/*-------------------------------------- + * Public methods/properties + *------------------------------------*/ + +// Installed and uninstalled states for `XRegExp.addToken` + addToken = { + on: function (regex, handler, options) { + options = options || {}; + if (regex) { + tokens.push({ + pattern: copy(regex, "g" + (hasNativeY ? "y" : "")), + handler: handler, + scope: options.scope || defaultScope, + trigger: options.trigger || null + }); + } + // Providing `customFlags` with null `regex` and `handler` allows adding flags that do + // nothing, but don't throw an error + if (options.customFlags) { + registeredFlags = nativ.replace.call(registeredFlags + options.customFlags, duplicateFlags, ""); + } + }, + off: function () { + throw new Error("extensibility must be installed before using addToken"); + } + }; + +/** + * Extends or changes XRegExp syntax and allows custom flags. This is used internally and can be + * used to create XRegExp addons. `XRegExp.install('extensibility')` must be run before calling + * this function, or an error is thrown. If more than one token can match the same string, the last + * added wins. + * @memberOf XRegExp + * @param {RegExp} regex Regex object that matches the new token. + * @param {Function} handler Function that returns a new pattern string (using native regex syntax) + * to replace the matched token within all future XRegExp regexes. Has access to persistent + * properties of the regex being built, through `this`. Invoked with two arguments: + *
  • The match array, with named backreference properties. + *
  • The regex scope where the match was found. + * @param {Object} [options] Options object with optional properties: + *
  • `scope` {String} Scopes where the token applies: 'default', 'class', or 'all'. + *
  • `trigger` {Function} Function that returns `true` when the token should be applied; e.g., + * if a flag is set. If `false` is returned, the matched string can be matched by other tokens. + * Has access to persistent properties of the regex being built, through `this` (including + * function `this.hasFlag`). + *
  • `customFlags` {String} Nonnative flags used by the token's handler or trigger functions. + * Prevents XRegExp from throwing an invalid flag error when the specified flags are used. + * @example + * + * // Basic usage: Adds \a for ALERT character + * XRegExp.addToken( + * /\\a/, + * function () {return '\\x07';}, + * {scope: 'all'} + * ); + * XRegExp('\\a[\\a-\\n]+').test('\x07\n\x07'); // -> true + */ + self.addToken = addToken.off; + +/** + * Caches and returns the result of calling `XRegExp(pattern, flags)`. On any subsequent call with + * the same pattern and flag combination, the cached copy is returned. + * @memberOf XRegExp + * @param {String} pattern Regex pattern string. + * @param {String} [flags] Any combination of XRegExp flags. + * @returns {RegExp} Cached XRegExp object. + * @example + * + * while (match = XRegExp.cache('.', 'gs').exec(str)) { + * // The regex is compiled once only + * } + */ + self.cache = function (pattern, flags) { + var key = pattern + "/" + (flags || ""); + return cache[key] || (cache[key] = self(pattern, flags)); + }; + +/** + * Escapes any regular expression metacharacters, for use when matching literal strings. The result + * can safely be used at any point within a regex that uses any flags. + * @memberOf XRegExp + * @param {String} str String to escape. + * @returns {String} String with regex metacharacters escaped. + * @example + * + * XRegExp.escape('Escaped? <.>'); + * // -> 'Escaped\?\ <\.>' + */ + self.escape = function (str) { + return nativ.replace.call(str, /[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + }; + +/** + * Executes a regex search in a specified string. Returns a match array or `null`. If the provided + * regex uses named capture, named backreference properties are included on the match array. + * Optional `pos` and `sticky` arguments specify the search start position, and whether the match + * must start at the specified position only. The `lastIndex` property of the provided regex is not + * used, but is updated for compatibility. Also fixes browser bugs compared to the native + * `RegExp.prototype.exec` and can be used reliably cross-browser. + * @memberOf XRegExp + * @param {String} str String to search. + * @param {RegExp} regex Regex to search with. + * @param {Number} [pos=0] Zero-based index at which to start the search. + * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position + * only. The string `'sticky'` is accepted as an alternative to `true`. + * @returns {Array} Match array with named backreference properties, or null. + * @example + * + * // Basic use, with named backreference + * var match = XRegExp.exec('U+2620', XRegExp('U\\+(?[0-9A-F]{4})')); + * match.hex; // -> '2620' + * + * // With pos and sticky, in a loop + * var pos = 2, result = [], match; + * while (match = XRegExp.exec('<1><2><3><4>5<6>', /<(\d)>/, pos, 'sticky')) { + * result.push(match[1]); + * pos = match.index + match[0].length; + * } + * // result -> ['2', '3', '4'] + */ + self.exec = function (str, regex, pos, sticky) { + var r2 = copy(regex, "g" + (sticky && hasNativeY ? "y" : ""), (sticky === false ? "y" : "")), + match; + r2.lastIndex = pos = pos || 0; + match = fixed.exec.call(r2, str); // Fixed `exec` required for `lastIndex` fix, etc. + if (sticky && match && match.index !== pos) { + match = null; + } + if (regex.global) { + regex.lastIndex = match ? r2.lastIndex : 0; + } + return match; + }; + +/** + * Executes a provided function once per regex match. + * @memberOf XRegExp + * @param {String} str String to search. + * @param {RegExp} regex Regex to search with. + * @param {Function} callback Function to execute for each match. Invoked with four arguments: + *
  • The match array, with named backreference properties. + *
  • The zero-based match index. + *
  • The string being traversed. + *
  • The regex object being used to traverse the string. + * @param {*} [context] Object to use as `this` when executing `callback`. + * @returns {*} Provided `context` object. + * @example + * + * // Extracts every other digit from a string + * XRegExp.forEach('1a2345', /\d/, function (match, i) { + * if (i % 2) this.push(+match[0]); + * }, []); + * // -> [2, 4] + */ + self.forEach = function (str, regex, callback, context) { + var pos = 0, + i = -1, + match; + while ((match = self.exec(str, regex, pos))) { + callback.call(context, match, ++i, str, regex); + pos = match.index + (match[0].length || 1); + } + return context; + }; + +/** + * Copies a regex object and adds flag `g`. The copy maintains special properties for named + * capture, is augmented with `XRegExp.prototype` methods, and has a fresh `lastIndex` property + * (set to zero). Native regexes are not recompiled using XRegExp syntax. + * @memberOf XRegExp + * @param {RegExp} regex Regex to globalize. + * @returns {RegExp} Copy of the provided regex with flag `g` added. + * @example + * + * var globalCopy = XRegExp.globalize(/regex/); + * globalCopy.global; // -> true + */ + self.globalize = function (regex) { + return copy(regex, "g"); + }; + +/** + * Installs optional features according to the specified options. + * @memberOf XRegExp + * @param {Object|String} options Options object or string. + * @example + * + * // With an options object + * XRegExp.install({ + * // Overrides native regex methods with fixed/extended versions that support named + * // backreferences and fix numerous cross-browser bugs + * natives: true, + * + * // Enables extensibility of XRegExp syntax and flags + * extensibility: true + * }); + * + * // With an options string + * XRegExp.install('natives extensibility'); + * + * // Using a shortcut to install all optional features + * XRegExp.install('all'); + */ + self.install = function (options) { + options = prepareOptions(options); + if (!features.natives && options.natives) { + setNatives(true); + } + if (!features.extensibility && options.extensibility) { + setExtensibility(true); + } + }; + +/** + * Checks whether an individual optional feature is installed. + * @memberOf XRegExp + * @param {String} feature Name of the feature to check. One of: + *
  • `natives` + *
  • `extensibility` + * @returns {Boolean} Whether the feature is installed. + * @example + * + * XRegExp.isInstalled('natives'); + */ + self.isInstalled = function (feature) { + return !!(features[feature]); + }; + +/** + * Returns `true` if an object is a regex; `false` if it isn't. This works correctly for regexes + * created in another frame, when `instanceof` and `constructor` checks would fail. + * @memberOf XRegExp + * @param {*} value Object to check. + * @returns {Boolean} Whether the object is a `RegExp` object. + * @example + * + * XRegExp.isRegExp('string'); // -> false + * XRegExp.isRegExp(/regex/i); // -> true + * XRegExp.isRegExp(RegExp('^', 'm')); // -> true + * XRegExp.isRegExp(XRegExp('(?s).')); // -> true + */ + self.isRegExp = function (value) { + return isType(value, "regexp"); + }; + +/** + * Retrieves the matches from searching a string using a chain of regexes that successively search + * within previous matches. The provided `chain` array can contain regexes and objects with `regex` + * and `backref` properties. When a backreference is specified, the named or numbered backreference + * is passed forward to the next regex or returned. + * @memberOf XRegExp + * @param {String} str String to search. + * @param {Array} chain Regexes that each search for matches within preceding results. + * @returns {Array} Matches by the last regex in the chain, or an empty array. + * @example + * + * // Basic usage; matches numbers within tags + * XRegExp.matchChain('1 2 3 4 a 56', [ + * XRegExp('(?is).*?'), + * /\d+/ + * ]); + * // -> ['2', '4', '56'] + * + * // Passing forward and returning specific backreferences + * html = 'XRegExp\ + * Google'; + * XRegExp.matchChain(html, [ + * {regex: //i, backref: 1}, + * {regex: XRegExp('(?i)^https?://(?[^/?#]+)'), backref: 'domain'} + * ]); + * // -> ['xregexp.com', 'www.google.com'] + */ + self.matchChain = function (str, chain) { + return (function recurseChain(values, level) { + var item = chain[level].regex ? chain[level] : {regex: chain[level]}, + matches = [], + addMatch = function (match) { + matches.push(item.backref ? (match[item.backref] || "") : match[0]); + }, + i; + for (i = 0; i < values.length; ++i) { + self.forEach(values[i], item.regex, addMatch); + } + return ((level === chain.length - 1) || !matches.length) ? + matches : + recurseChain(matches, level + 1); + }([str], 0)); + }; + +/** + * Returns a new string with one or all matches of a pattern replaced. The pattern can be a string + * or regex, and the replacement can be a string or a function to be called for each match. To + * perform a global search and replace, use the optional `scope` argument or include flag `g` if + * using a regex. Replacement strings can use `${n}` for named and numbered backreferences. + * Replacement functions can use named backreferences via `arguments[0].name`. Also fixes browser + * bugs compared to the native `String.prototype.replace` and can be used reliably cross-browser. + * @memberOf XRegExp + * @param {String} str String to search. + * @param {RegExp|String} search Search pattern to be replaced. + * @param {String|Function} replacement Replacement string or a function invoked to create it. + * Replacement strings can include special replacement syntax: + *
  • $$ - Inserts a literal '$'. + *
  • $&, $0 - Inserts the matched substring. + *
  • $` - Inserts the string that precedes the matched substring (left context). + *
  • $' - Inserts the string that follows the matched substring (right context). + *
  • $n, $nn - Where n/nn are digits referencing an existent capturing group, inserts + * backreference n/nn. + *
  • ${n} - Where n is a name or any number of digits that reference an existent capturing + * group, inserts backreference n. + * Replacement functions are invoked with three or more arguments: + *
  • The matched substring (corresponds to $& above). Named backreferences are accessible as + * properties of this first argument. + *
  • 0..n arguments, one for each backreference (corresponding to $1, $2, etc. above). + *
  • The zero-based index of the match within the total search string. + *
  • The total string being searched. + * @param {String} [scope='one'] Use 'one' to replace the first match only, or 'all'. If not + * explicitly specified and using a regex with flag `g`, `scope` is 'all'. + * @returns {String} New string with one or all matches replaced. + * @example + * + * // Regex search, using named backreferences in replacement string + * var name = XRegExp('(?\\w+) (?\\w+)'); + * XRegExp.replace('John Smith', name, '${last}, ${first}'); + * // -> 'Smith, John' + * + * // Regex search, using named backreferences in replacement function + * XRegExp.replace('John Smith', name, function (match) { + * return match.last + ', ' + match.first; + * }); + * // -> 'Smith, John' + * + * // Global string search/replacement + * XRegExp.replace('RegExp builds RegExps', 'RegExp', 'XRegExp', 'all'); + * // -> 'XRegExp builds XRegExps' + */ + self.replace = function (str, search, replacement, scope) { + var isRegex = self.isRegExp(search), + search2 = search, + result; + if (isRegex) { + if (scope === undef && search.global) { + scope = "all"; // Follow flag g when `scope` isn't explicit + } + // Note that since a copy is used, `search`'s `lastIndex` isn't updated *during* replacement iterations + search2 = copy(search, scope === "all" ? "g" : "", scope === "all" ? "" : "g"); + } else if (scope === "all") { + search2 = new RegExp(self.escape(String(search)), "g"); + } + result = fixed.replace.call(String(str), search2, replacement); // Fixed `replace` required for named backreferences, etc. + if (isRegex && search.global) { + search.lastIndex = 0; // Fixes IE, Safari bug (last tested IE 9, Safari 5.1) + } + return result; + }; + +/** + * Splits a string into an array of strings using a regex or string separator. Matches of the + * separator are not included in the result array. However, if `separator` is a regex that contains + * capturing groups, backreferences are spliced into the result each time `separator` is matched. + * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably + * cross-browser. + * @memberOf XRegExp + * @param {String} str String to split. + * @param {RegExp|String} separator Regex or string to use for separating the string. + * @param {Number} [limit] Maximum number of items to include in the result array. + * @returns {Array} Array of substrings. + * @example + * + * // Basic use + * XRegExp.split('a b c', ' '); + * // -> ['a', 'b', 'c'] + * + * // With limit + * XRegExp.split('a b c', ' ', 2); + * // -> ['a', 'b'] + * + * // Backreferences in result array + * XRegExp.split('..word1..', /([a-z]+)(\d+)/i); + * // -> ['..', 'word', '1', '..'] + */ + self.split = function (str, separator, limit) { + return fixed.split.call(str, separator, limit); + }; + +/** + * Executes a regex search in a specified string. Returns `true` or `false`. Optional `pos` and + * `sticky` arguments specify the search start position, and whether the match must start at the + * specified position only. The `lastIndex` property of the provided regex is not used, but is + * updated for compatibility. Also fixes browser bugs compared to the native + * `RegExp.prototype.test` and can be used reliably cross-browser. + * @memberOf XRegExp + * @param {String} str String to search. + * @param {RegExp} regex Regex to search with. + * @param {Number} [pos=0] Zero-based index at which to start the search. + * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position + * only. The string `'sticky'` is accepted as an alternative to `true`. + * @returns {Boolean} Whether the regex matched the provided value. + * @example + * + * // Basic use + * XRegExp.test('abc', /c/); // -> true + * + * // With pos and sticky + * XRegExp.test('abc', /c/, 0, 'sticky'); // -> false + */ + self.test = function (str, regex, pos, sticky) { + // Do this the easy way :-) + return !!self.exec(str, regex, pos, sticky); + }; + +/** + * Uninstalls optional features according to the specified options. + * @memberOf XRegExp + * @param {Object|String} options Options object or string. + * @example + * + * // With an options object + * XRegExp.uninstall({ + * // Restores native regex methods + * natives: true, + * + * // Disables additional syntax and flag extensions + * extensibility: true + * }); + * + * // With an options string + * XRegExp.uninstall('natives extensibility'); + * + * // Using a shortcut to uninstall all optional features + * XRegExp.uninstall('all'); + */ + self.uninstall = function (options) { + options = prepareOptions(options); + if (features.natives && options.natives) { + setNatives(false); + } + if (features.extensibility && options.extensibility) { + setExtensibility(false); + } + }; + +/** + * Returns an XRegExp object that is the union of the given patterns. Patterns can be provided as + * regex objects or strings. Metacharacters are escaped in patterns provided as strings. + * Backreferences in provided regex objects are automatically renumbered to work correctly. Native + * flags used by provided regexes are ignored in favor of the `flags` argument. + * @memberOf XRegExp + * @param {Array} patterns Regexes and strings to combine. + * @param {String} [flags] Any combination of XRegExp flags. + * @returns {RegExp} Union of the provided regexes and strings. + * @example + * + * XRegExp.union(['a+b*c', /(dogs)\1/, /(cats)\1/], 'i'); + * // -> /a\+b\*c|(dogs)\1|(cats)\2/i + * + * XRegExp.union([XRegExp('(?dogs)\\k'), XRegExp('(?cats)\\k')]); + * // -> XRegExp('(?dogs)\\k|(?cats)\\k') + */ + self.union = function (patterns, flags) { + var parts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*]/g, + numCaptures = 0, + numPriorCaptures, + captureNames, + rewrite = function (match, paren, backref) { + var name = captureNames[numCaptures - numPriorCaptures]; + if (paren) { // Capturing group + ++numCaptures; + if (name) { // If the current capture has a name + return "(?<" + name + ">"; + } + } else if (backref) { // Backreference + return "\\" + (+backref + numPriorCaptures); + } + return match; + }, + output = [], + pattern, + i; + if (!(isType(patterns, "array") && patterns.length)) { + throw new TypeError("patterns must be a nonempty array"); + } + for (i = 0; i < patterns.length; ++i) { + pattern = patterns[i]; + if (self.isRegExp(pattern)) { + numPriorCaptures = numCaptures; + captureNames = (pattern.xregexp && pattern.xregexp.captureNames) || []; + // Rewrite backreferences. Passing to XRegExp dies on octals and ensures patterns + // are independently valid; helps keep this simple. Named captures are put back + output.push(self(pattern.source).source.replace(parts, rewrite)); + } else { + output.push(self.escape(pattern)); + } + } + return self(output.join("|"), flags); + }; + +/** + * The XRegExp version number. + * @static + * @memberOf XRegExp + * @type String + */ + self.version = "2.0.0"; + +/*-------------------------------------- + * Fixed/extended native methods + *------------------------------------*/ + +/** + * Adds named capture support (with backreferences returned as `result.name`), and fixes browser + * bugs in the native `RegExp.prototype.exec`. Calling `XRegExp.install('natives')` uses this to + * override the native method. Use via `XRegExp.exec` without overriding natives. + * @private + * @param {String} str String to search. + * @returns {Array} Match array with named backreference properties, or null. + */ + fixed.exec = function (str) { + var match, name, r2, origLastIndex, i; + if (!this.global) { + origLastIndex = this.lastIndex; + } + match = nativ.exec.apply(this, arguments); + if (match) { + // Fix browsers whose `exec` methods don't consistently return `undefined` for + // nonparticipating capturing groups + if (!compliantExecNpcg && match.length > 1 && lastIndexOf(match, "") > -1) { + r2 = new RegExp(this.source, nativ.replace.call(getNativeFlags(this), "g", "")); + // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed + // matching due to characters outside the match + nativ.replace.call(String(str).slice(match.index), r2, function () { + var i; + for (i = 1; i < arguments.length - 2; ++i) { + if (arguments[i] === undef) { + match[i] = undef; + } + } + }); + } + // Attach named capture properties + if (this.xregexp && this.xregexp.captureNames) { + for (i = 1; i < match.length; ++i) { + name = this.xregexp.captureNames[i - 1]; + if (name) { + match[name] = match[i]; + } + } + } + // Fix browsers that increment `lastIndex` after zero-length matches + if (this.global && !match[0].length && (this.lastIndex > match.index)) { + this.lastIndex = match.index; + } + } + if (!this.global) { + this.lastIndex = origLastIndex; // Fixes IE, Opera bug (last tested IE 9, Opera 11.6) + } + return match; + }; + +/** + * Fixes browser bugs in the native `RegExp.prototype.test`. Calling `XRegExp.install('natives')` + * uses this to override the native method. + * @private + * @param {String} str String to search. + * @returns {Boolean} Whether the regex matched the provided value. + */ + fixed.test = function (str) { + // Do this the easy way :-) + return !!fixed.exec.call(this, str); + }; + +/** + * Adds named capture support (with backreferences returned as `result.name`), and fixes browser + * bugs in the native `String.prototype.match`. Calling `XRegExp.install('natives')` uses this to + * override the native method. + * @private + * @param {RegExp} regex Regex to search with. + * @returns {Array} If `regex` uses flag g, an array of match strings or null. Without flag g, the + * result of calling `regex.exec(this)`. + */ + fixed.match = function (regex) { + if (!self.isRegExp(regex)) { + regex = new RegExp(regex); // Use native `RegExp` + } else if (regex.global) { + var result = nativ.match.apply(this, arguments); + regex.lastIndex = 0; // Fixes IE bug + return result; + } + return fixed.exec.call(regex, this); + }; + +/** + * Adds support for `${n}` tokens for named and numbered backreferences in replacement text, and + * provides named backreferences to replacement functions as `arguments[0].name`. Also fixes + * browser bugs in replacement text syntax when performing a replacement using a nonregex search + * value, and the value of a replacement regex's `lastIndex` property during replacement iterations + * and upon completion. Note that this doesn't support SpiderMonkey's proprietary third (`flags`) + * argument. Calling `XRegExp.install('natives')` uses this to override the native method. Use via + * `XRegExp.replace` without overriding natives. + * @private + * @param {RegExp|String} search Search pattern to be replaced. + * @param {String|Function} replacement Replacement string or a function invoked to create it. + * @returns {String} New string with one or all matches replaced. + */ + fixed.replace = function (search, replacement) { + var isRegex = self.isRegExp(search), captureNames, result, str, origLastIndex; + if (isRegex) { + if (search.xregexp) { + captureNames = search.xregexp.captureNames; + } + if (!search.global) { + origLastIndex = search.lastIndex; + } + } else { + search += ""; + } + if (isType(replacement, "function")) { + result = nativ.replace.call(String(this), search, function () { + var args = arguments, i; + if (captureNames) { + // Change the `arguments[0]` string primitive to a `String` object that can store properties + args[0] = new String(args[0]); + // Store named backreferences on the first argument + for (i = 0; i < captureNames.length; ++i) { + if (captureNames[i]) { + args[0][captureNames[i]] = args[i + 1]; + } + } + } + // Update `lastIndex` before calling `replacement`. + // Fixes IE, Chrome, Firefox, Safari bug (last tested IE 9, Chrome 17, Firefox 11, Safari 5.1) + if (isRegex && search.global) { + search.lastIndex = args[args.length - 2] + args[0].length; + } + return replacement.apply(null, args); + }); + } else { + str = String(this); // Ensure `args[args.length - 1]` will be a string when given nonstring `this` + result = nativ.replace.call(str, search, function () { + var args = arguments; // Keep this function's `arguments` available through closure + return nativ.replace.call(String(replacement), replacementToken, function ($0, $1, $2) { + var n; + // Named or numbered backreference with curly brackets + if ($1) { + /* XRegExp behavior for `${n}`: + * 1. Backreference to numbered capture, where `n` is 1+ digits. `0`, `00`, etc. is the entire match. + * 2. Backreference to named capture `n`, if it exists and is not a number overridden by numbered capture. + * 3. Otherwise, it's an error. + */ + n = +$1; // Type-convert; drop leading zeros + if (n <= args.length - 3) { + return args[n] || ""; + } + n = captureNames ? lastIndexOf(captureNames, $1) : -1; + if (n < 0) { + throw new SyntaxError("backreference to undefined group " + $0); + } + return args[n + 1] || ""; + } + // Else, special variable or numbered backreference (without curly brackets) + if ($2 === "$") return "$"; + if ($2 === "&" || +$2 === 0) return args[0]; // $&, $0 (not followed by 1-9), $00 + if ($2 === "`") return args[args.length - 1].slice(0, args[args.length - 2]); + if ($2 === "'") return args[args.length - 1].slice(args[args.length - 2] + args[0].length); + // Else, numbered backreference (without curly brackets) + $2 = +$2; // Type-convert; drop leading zero + /* XRegExp behavior: + * - Backreferences without curly brackets end after 1 or 2 digits. Use `${..}` for more digits. + * - `$1` is an error if there are no capturing groups. + * - `$10` is an error if there are less than 10 capturing groups. Use `${1}0` instead. + * - `$01` is equivalent to `$1` if a capturing group exists, otherwise it's an error. + * - `$0` (not followed by 1-9), `$00`, and `$&` are the entire match. + * Native behavior, for comparison: + * - Backreferences end after 1 or 2 digits. Cannot use backreference to capturing group 100+. + * - `$1` is a literal `$1` if there are no capturing groups. + * - `$10` is `$1` followed by a literal `0` if there are less than 10 capturing groups. + * - `$01` is equivalent to `$1` if a capturing group exists, otherwise it's a literal `$01`. + * - `$0` is a literal `$0`. `$&` is the entire match. + */ + if (!isNaN($2)) { + if ($2 > args.length - 3) { + throw new SyntaxError("backreference to undefined group " + $0); + } + return args[$2] || ""; + } + throw new SyntaxError("invalid token " + $0); + }); + }); + } + if (isRegex) { + if (search.global) { + search.lastIndex = 0; // Fixes IE, Safari bug (last tested IE 9, Safari 5.1) + } else { + search.lastIndex = origLastIndex; // Fixes IE, Opera bug (last tested IE 9, Opera 11.6) + } + } + return result; + }; + +/** + * Fixes browser bugs in the native `String.prototype.split`. Calling `XRegExp.install('natives')` + * uses this to override the native method. Use via `XRegExp.split` without overriding natives. + * @private + * @param {RegExp|String} separator Regex or string to use for separating the string. + * @param {Number} [limit] Maximum number of items to include in the result array. + * @returns {Array} Array of substrings. + */ + fixed.split = function (separator, limit) { + if (!self.isRegExp(separator)) { + return nativ.split.apply(this, arguments); // use faster native method + } + var str = String(this), + origLastIndex = separator.lastIndex, + output = [], + lastLastIndex = 0, + lastLength; + /* Values for `limit`, per the spec: + * If undefined: pow(2,32) - 1 + * If 0, Infinity, or NaN: 0 + * If positive number: limit = floor(limit); if (limit >= pow(2,32)) limit -= pow(2,32); + * If negative number: pow(2,32) - floor(abs(limit)) + * If other: Type-convert, then use the above rules + */ + limit = (limit === undef ? -1 : limit) >>> 0; + self.forEach(str, separator, function (match) { + if ((match.index + match[0].length) > lastLastIndex) { // != `if (match[0].length)` + output.push(str.slice(lastLastIndex, match.index)); + if (match.length > 1 && match.index < str.length) { + Array.prototype.push.apply(output, match.slice(1)); + } + lastLength = match[0].length; + lastLastIndex = match.index + lastLength; + } + }); + if (lastLastIndex === str.length) { + if (!nativ.test.call(separator, "") || lastLength) { + output.push(""); + } + } else { + output.push(str.slice(lastLastIndex)); + } + separator.lastIndex = origLastIndex; + return output.length > limit ? output.slice(0, limit) : output; + }; + +/*-------------------------------------- + * Built-in tokens + *------------------------------------*/ + +// Shortcut + add = addToken.on; + +/* Letter identity escapes that natively match literal characters: \p, \P, etc. + * Should be SyntaxErrors but are allowed in web reality. XRegExp makes them errors for cross- + * browser consistency and to reserve their syntax, but lets them be superseded by XRegExp addons. + */ + add(/\\([ABCE-RTUVXYZaeg-mopqyz]|c(?![A-Za-z])|u(?![\dA-Fa-f]{4})|x(?![\dA-Fa-f]{2}))/, + function (match, scope) { + // \B is allowed in default scope only + if (match[1] === "B" && scope === defaultScope) { + return match[0]; + } + throw new SyntaxError("invalid escape " + match[0]); + }, + {scope: "all"}); + +/* Empty character class: [] or [^] + * Fixes a critical cross-browser syntax inconsistency. Unless this is standardized (per the spec), + * regex syntax can't be accurately parsed because character class endings can't be determined. + */ + add(/\[(\^?)]/, + function (match) { + // For cross-browser compatibility with ES3, convert [] to \b\B and [^] to [\s\S]. + // (?!) should work like \b\B, but is unreliable in Firefox + return match[1] ? "[\\s\\S]" : "\\b\\B"; + }); + +/* Comment pattern: (?# ) + * Inline comments are an alternative to the line comments allowed in free-spacing mode (flag x). + */ + add(/(?:\(\?#[^)]*\))+/, + function (match) { + // Keep tokens separated unless the following token is a quantifier + return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)"; + }); + +/* Named backreference: \k + * Backreference names can use the characters A-Z, a-z, 0-9, _, and $ only. + */ + add(/\\k<([\w$]+)>/, + function (match) { + var index = isNaN(match[1]) ? (lastIndexOf(this.captureNames, match[1]) + 1) : +match[1], + endIndex = match.index + match[0].length; + if (!index || index > this.captureNames.length) { + throw new SyntaxError("backreference to undefined group " + match[0]); + } + // Keep backreferences separate from subsequent literal numbers + return "\\" + index + ( + endIndex === match.input.length || isNaN(match.input.charAt(endIndex)) ? "" : "(?:)" + ); + }); + +/* Whitespace and line comments, in free-spacing mode (aka extended mode, flag x) only. + */ + add(/(?:\s+|#.*)+/, + function (match) { + // Keep tokens separated unless the following token is a quantifier + return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)"; + }, + { + trigger: function () { + return this.hasFlag("x"); + }, + customFlags: "x" + }); + +/* Dot, in dotall mode (aka singleline mode, flag s) only. + */ + add(/\./, + function () { + return "[\\s\\S]"; + }, + { + trigger: function () { + return this.hasFlag("s"); + }, + customFlags: "s" + }); + +/* Named capturing group; match the opening delimiter only: (? + * Capture names can use the characters A-Z, a-z, 0-9, _, and $ only. Names can't be integers. + * Supports Python-style (?P as an alternate syntax to avoid issues in recent Opera (which + * natively supports the Python-style syntax). Otherwise, XRegExp might treat numbered + * backreferences to Python-style named capture as octals. + */ + add(/\(\?P?<([\w$]+)>/, + function (match) { + if (!isNaN(match[1])) { + // Avoid incorrect lookups, since named backreferences are added to match arrays + throw new SyntaxError("can't use integer as capture name " + match[0]); + } + this.captureNames.push(match[1]); + this.hasNamedCapture = true; + return "("; + }); + +/* Numbered backreference or octal, plus any following digits: \0, \11, etc. + * Octals except \0 not followed by 0-9 and backreferences to unopened capture groups throw an + * error. Other matches are returned unaltered. IE <= 8 doesn't support backreferences greater than + * \99 in regex syntax. + */ + add(/\\(\d+)/, + function (match, scope) { + if (!(scope === defaultScope && /^[1-9]/.test(match[1]) && +match[1] <= this.captureNames.length) && + match[1] !== "0") { + throw new SyntaxError("can't use octal escape or backreference to undefined group " + match[0]); + } + return match[0]; + }, + {scope: "all"}); + +/* Capturing group; match the opening parenthesis only. + * Required for support of named capturing groups. Also adds explicit capture mode (flag n). + */ + add(/\((?!\?)/, + function () { + if (this.hasFlag("n")) { + return "(?:"; + } + this.captureNames.push(null); + return "("; + }, + {customFlags: "n"}); + +/*-------------------------------------- + * Expose XRegExp + *------------------------------------*/ + +// For CommonJS enviroments + if (typeof exports !== "undefined") { + exports.XRegExp = self; + } + + return self; + +}()); + + +/***** unicode-base.js *****/ + +/*! + * XRegExp Unicode Base v1.0.0 + * (c) 2008-2012 Steven Levithan + * MIT License + * Uses Unicode 6.1 + */ + +/** + * Adds support for the `\p{L}` or `\p{Letter}` Unicode category. Addon packages for other Unicode + * categories, scripts, blocks, and properties are available separately. All Unicode tokens can be + * inverted using `\P{..}` or `\p{^..}`. Token names are case insensitive, and any spaces, hyphens, + * and underscores are ignored. + * @requires XRegExp + */ +(function (XRegExp) { + "use strict"; + + var unicode = {}; + +/*-------------------------------------- + * Private helper functions + *------------------------------------*/ + +// Generates a standardized token name (lowercase, with hyphens, spaces, and underscores removed) + function slug(name) { + return name.replace(/[- _]+/g, "").toLowerCase(); + } + +// Expands a list of Unicode code points and ranges to be usable in a regex character class + function expand(str) { + return str.replace(/\w{4}/g, "\\u$&"); + } + +// Adds leading zeros if shorter than four characters + function pad4(str) { + while (str.length < 4) { + str = "0" + str; + } + return str; + } + +// Converts a hexadecimal number to decimal + function dec(hex) { + return parseInt(hex, 16); + } + +// Converts a decimal number to hexadecimal + function hex(dec) { + return parseInt(dec, 10).toString(16); + } + +// Inverts a list of Unicode code points and ranges + function invert(range) { + var output = [], + lastEnd = -1, + start; + XRegExp.forEach(range, /\\u(\w{4})(?:-\\u(\w{4}))?/, function (m) { + start = dec(m[1]); + if (start > (lastEnd + 1)) { + output.push("\\u" + pad4(hex(lastEnd + 1))); + if (start > (lastEnd + 2)) { + output.push("-\\u" + pad4(hex(start - 1))); + } + } + lastEnd = dec(m[2] || m[1]); + }); + if (lastEnd < 0xFFFF) { + output.push("\\u" + pad4(hex(lastEnd + 1))); + if (lastEnd < 0xFFFE) { + output.push("-\\uFFFF"); + } + } + return output.join(""); + } + +// Generates an inverted token on first use + function cacheInversion(item) { + return unicode["^" + item] || (unicode["^" + item] = invert(unicode[item])); + } + +/*-------------------------------------- + * Core functionality + *------------------------------------*/ + + XRegExp.install("extensibility"); + +/** + * Adds to the list of Unicode properties that XRegExp regexes can match via \p{..} or \P{..}. + * @memberOf XRegExp + * @param {Object} pack Named sets of Unicode code points and ranges. + * @param {Object} [aliases] Aliases for the primary token names. + * @example + * + * XRegExp.addUnicodePackage({ + * XDigit: '0030-00390041-00460061-0066' // 0-9A-Fa-f + * }, { + * XDigit: 'Hexadecimal' + * }); + */ + XRegExp.addUnicodePackage = function (pack, aliases) { + var p; + if (!XRegExp.isInstalled("extensibility")) { + throw new Error("extensibility must be installed before adding Unicode packages"); + } + if (pack) { + for (p in pack) { + if (pack.hasOwnProperty(p)) { + unicode[slug(p)] = expand(pack[p]); + } + } + } + if (aliases) { + for (p in aliases) { + if (aliases.hasOwnProperty(p)) { + unicode[slug(aliases[p])] = unicode[slug(p)]; + } + } + } + }; + +/* Adds data for the Unicode `Letter` category. Addon packages include other categories, scripts, + * blocks, and properties. + */ + XRegExp.addUnicodePackage({ + L: "0041-005A0061-007A00AA00B500BA00C0-00D600D8-00F600F8-02C102C6-02D102E0-02E402EC02EE0370-037403760377037A-037D03860388-038A038C038E-03A103A3-03F503F7-0481048A-05270531-055605590561-058705D0-05EA05F0-05F20620-064A066E066F0671-06D306D506E506E606EE06EF06FA-06FC06FF07100712-072F074D-07A507B107CA-07EA07F407F507FA0800-0815081A082408280840-085808A008A2-08AC0904-0939093D09500958-09610971-09770979-097F0985-098C098F09900993-09A809AA-09B009B209B6-09B909BD09CE09DC09DD09DF-09E109F009F10A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A59-0A5C0A5E0A72-0A740A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABD0AD00AE00AE10B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3D0B5C0B5D0B5F-0B610B710B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BD00C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D0C580C590C600C610C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBD0CDE0CE00CE10CF10CF20D05-0D0C0D0E-0D100D12-0D3A0D3D0D4E0D600D610D7A-0D7F0D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60E01-0E300E320E330E40-0E460E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB00EB20EB30EBD0EC0-0EC40EC60EDC-0EDF0F000F40-0F470F49-0F6C0F88-0F8C1000-102A103F1050-1055105A-105D106110651066106E-10701075-1081108E10A0-10C510C710CD10D0-10FA10FC-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A1380-138F13A0-13F41401-166C166F-167F1681-169A16A0-16EA1700-170C170E-17111720-17311740-17511760-176C176E-17701780-17B317D717DC1820-18771880-18A818AA18B0-18F51900-191C1950-196D1970-19741980-19AB19C1-19C71A00-1A161A20-1A541AA71B05-1B331B45-1B4B1B83-1BA01BAE1BAF1BBA-1BE51C00-1C231C4D-1C4F1C5A-1C7D1CE9-1CEC1CEE-1CF11CF51CF61D00-1DBF1E00-1F151F18-1F1D1F20-1F451F48-1F4D1F50-1F571F591F5B1F5D1F5F-1F7D1F80-1FB41FB6-1FBC1FBE1FC2-1FC41FC6-1FCC1FD0-1FD31FD6-1FDB1FE0-1FEC1FF2-1FF41FF6-1FFC2071207F2090-209C21022107210A-211321152119-211D212421262128212A-212D212F-2139213C-213F2145-2149214E218321842C00-2C2E2C30-2C5E2C60-2CE42CEB-2CEE2CF22CF32D00-2D252D272D2D2D30-2D672D6F2D80-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDE2E2F300530063031-3035303B303C3041-3096309D-309F30A1-30FA30FC-30FF3105-312D3131-318E31A0-31BA31F0-31FF3400-4DB54E00-9FCCA000-A48CA4D0-A4FDA500-A60CA610-A61FA62AA62BA640-A66EA67F-A697A6A0-A6E5A717-A71FA722-A788A78B-A78EA790-A793A7A0-A7AAA7F8-A801A803-A805A807-A80AA80C-A822A840-A873A882-A8B3A8F2-A8F7A8FBA90A-A925A930-A946A960-A97CA984-A9B2A9CFAA00-AA28AA40-AA42AA44-AA4BAA60-AA76AA7AAA80-AAAFAAB1AAB5AAB6AAB9-AABDAAC0AAC2AADB-AADDAAE0-AAEAAAF2-AAF4AB01-AB06AB09-AB0EAB11-AB16AB20-AB26AB28-AB2EABC0-ABE2AC00-D7A3D7B0-D7C6D7CB-D7FBF900-FA6DFA70-FAD9FB00-FB06FB13-FB17FB1DFB1F-FB28FB2A-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FBB1FBD3-FD3DFD50-FD8FFD92-FDC7FDF0-FDFBFE70-FE74FE76-FEFCFF21-FF3AFF41-FF5AFF66-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDC" + }, { + L: "Letter" + }); + +/* Adds Unicode property syntax to XRegExp: \p{..}, \P{..}, \p{^..} + */ + XRegExp.addToken( + /\\([pP]){(\^?)([^}]*)}/, + function (match, scope) { + var inv = (match[1] === "P" || match[2]) ? "^" : "", + item = slug(match[3]); + // The double negative \P{^..} is invalid + if (match[1] === "P" && match[2]) { + throw new SyntaxError("invalid double negation \\P{^"); + } + if (!unicode.hasOwnProperty(item)) { + throw new SyntaxError("invalid or unknown Unicode property " + match[0]); + } + return scope === "class" ? + (inv ? cacheInversion(item) : unicode[item]) : + "[" + inv + unicode[item] + "]"; + }, + {scope: "all"} + ); + +}(XRegExp)); + + +/***** unicode-categories.js *****/ + +/*! + * XRegExp Unicode Categories v1.2.0 + * (c) 2010-2012 Steven Levithan + * MIT License + * Uses Unicode 6.1 + */ + +/** + * Adds support for all Unicode categories (aka properties) E.g., `\p{Lu}` or + * `\p{Uppercase Letter}`. Token names are case insensitive, and any spaces, hyphens, and + * underscores are ignored. + * @requires XRegExp, XRegExp Unicode Base + */ +(function (XRegExp) { + "use strict"; + + if (!XRegExp.addUnicodePackage) { + throw new ReferenceError("Unicode Base must be loaded before Unicode Categories"); + } + + XRegExp.install("extensibility"); + + XRegExp.addUnicodePackage({ + //L: "", // Included in the Unicode Base addon + Ll: "0061-007A00B500DF-00F600F8-00FF01010103010501070109010B010D010F01110113011501170119011B011D011F01210123012501270129012B012D012F01310133013501370138013A013C013E014001420144014601480149014B014D014F01510153015501570159015B015D015F01610163016501670169016B016D016F0171017301750177017A017C017E-0180018301850188018C018D019201950199-019B019E01A101A301A501A801AA01AB01AD01B001B401B601B901BA01BD-01BF01C601C901CC01CE01D001D201D401D601D801DA01DC01DD01DF01E101E301E501E701E901EB01ED01EF01F001F301F501F901FB01FD01FF02010203020502070209020B020D020F02110213021502170219021B021D021F02210223022502270229022B022D022F02310233-0239023C023F0240024202470249024B024D024F-02930295-02AF037103730377037B-037D039003AC-03CE03D003D103D5-03D703D903DB03DD03DF03E103E303E503E703E903EB03ED03EF-03F303F503F803FB03FC0430-045F04610463046504670469046B046D046F04710473047504770479047B047D047F0481048B048D048F04910493049504970499049B049D049F04A104A304A504A704A904AB04AD04AF04B104B304B504B704B904BB04BD04BF04C204C404C604C804CA04CC04CE04CF04D104D304D504D704D904DB04DD04DF04E104E304E504E704E904EB04ED04EF04F104F304F504F704F904FB04FD04FF05010503050505070509050B050D050F05110513051505170519051B051D051F05210523052505270561-05871D00-1D2B1D6B-1D771D79-1D9A1E011E031E051E071E091E0B1E0D1E0F1E111E131E151E171E191E1B1E1D1E1F1E211E231E251E271E291E2B1E2D1E2F1E311E331E351E371E391E3B1E3D1E3F1E411E431E451E471E491E4B1E4D1E4F1E511E531E551E571E591E5B1E5D1E5F1E611E631E651E671E691E6B1E6D1E6F1E711E731E751E771E791E7B1E7D1E7F1E811E831E851E871E891E8B1E8D1E8F1E911E931E95-1E9D1E9F1EA11EA31EA51EA71EA91EAB1EAD1EAF1EB11EB31EB51EB71EB91EBB1EBD1EBF1EC11EC31EC51EC71EC91ECB1ECD1ECF1ED11ED31ED51ED71ED91EDB1EDD1EDF1EE11EE31EE51EE71EE91EEB1EED1EEF1EF11EF31EF51EF71EF91EFB1EFD1EFF-1F071F10-1F151F20-1F271F30-1F371F40-1F451F50-1F571F60-1F671F70-1F7D1F80-1F871F90-1F971FA0-1FA71FB0-1FB41FB61FB71FBE1FC2-1FC41FC61FC71FD0-1FD31FD61FD71FE0-1FE71FF2-1FF41FF61FF7210A210E210F2113212F21342139213C213D2146-2149214E21842C30-2C5E2C612C652C662C682C6A2C6C2C712C732C742C76-2C7B2C812C832C852C872C892C8B2C8D2C8F2C912C932C952C972C992C9B2C9D2C9F2CA12CA32CA52CA72CA92CAB2CAD2CAF2CB12CB32CB52CB72CB92CBB2CBD2CBF2CC12CC32CC52CC72CC92CCB2CCD2CCF2CD12CD32CD52CD72CD92CDB2CDD2CDF2CE12CE32CE42CEC2CEE2CF32D00-2D252D272D2DA641A643A645A647A649A64BA64DA64FA651A653A655A657A659A65BA65DA65FA661A663A665A667A669A66BA66DA681A683A685A687A689A68BA68DA68FA691A693A695A697A723A725A727A729A72BA72DA72F-A731A733A735A737A739A73BA73DA73FA741A743A745A747A749A74BA74DA74FA751A753A755A757A759A75BA75DA75FA761A763A765A767A769A76BA76DA76FA771-A778A77AA77CA77FA781A783A785A787A78CA78EA791A793A7A1A7A3A7A5A7A7A7A9A7FAFB00-FB06FB13-FB17FF41-FF5A", + Lu: "0041-005A00C0-00D600D8-00DE01000102010401060108010A010C010E01100112011401160118011A011C011E01200122012401260128012A012C012E01300132013401360139013B013D013F0141014301450147014A014C014E01500152015401560158015A015C015E01600162016401660168016A016C016E017001720174017601780179017B017D018101820184018601870189-018B018E-0191019301940196-0198019C019D019F01A001A201A401A601A701A901AC01AE01AF01B1-01B301B501B701B801BC01C401C701CA01CD01CF01D101D301D501D701D901DB01DE01E001E201E401E601E801EA01EC01EE01F101F401F6-01F801FA01FC01FE02000202020402060208020A020C020E02100212021402160218021A021C021E02200222022402260228022A022C022E02300232023A023B023D023E02410243-02460248024A024C024E03700372037603860388-038A038C038E038F0391-03A103A3-03AB03CF03D2-03D403D803DA03DC03DE03E003E203E403E603E803EA03EC03EE03F403F703F903FA03FD-042F04600462046404660468046A046C046E04700472047404760478047A047C047E0480048A048C048E04900492049404960498049A049C049E04A004A204A404A604A804AA04AC04AE04B004B204B404B604B804BA04BC04BE04C004C104C304C504C704C904CB04CD04D004D204D404D604D804DA04DC04DE04E004E204E404E604E804EA04EC04EE04F004F204F404F604F804FA04FC04FE05000502050405060508050A050C050E05100512051405160518051A051C051E05200522052405260531-055610A0-10C510C710CD1E001E021E041E061E081E0A1E0C1E0E1E101E121E141E161E181E1A1E1C1E1E1E201E221E241E261E281E2A1E2C1E2E1E301E321E341E361E381E3A1E3C1E3E1E401E421E441E461E481E4A1E4C1E4E1E501E521E541E561E581E5A1E5C1E5E1E601E621E641E661E681E6A1E6C1E6E1E701E721E741E761E781E7A1E7C1E7E1E801E821E841E861E881E8A1E8C1E8E1E901E921E941E9E1EA01EA21EA41EA61EA81EAA1EAC1EAE1EB01EB21EB41EB61EB81EBA1EBC1EBE1EC01EC21EC41EC61EC81ECA1ECC1ECE1ED01ED21ED41ED61ED81EDA1EDC1EDE1EE01EE21EE41EE61EE81EEA1EEC1EEE1EF01EF21EF41EF61EF81EFA1EFC1EFE1F08-1F0F1F18-1F1D1F28-1F2F1F38-1F3F1F48-1F4D1F591F5B1F5D1F5F1F68-1F6F1FB8-1FBB1FC8-1FCB1FD8-1FDB1FE8-1FEC1FF8-1FFB21022107210B-210D2110-211221152119-211D212421262128212A-212D2130-2133213E213F214521832C00-2C2E2C602C62-2C642C672C692C6B2C6D-2C702C722C752C7E-2C802C822C842C862C882C8A2C8C2C8E2C902C922C942C962C982C9A2C9C2C9E2CA02CA22CA42CA62CA82CAA2CAC2CAE2CB02CB22CB42CB62CB82CBA2CBC2CBE2CC02CC22CC42CC62CC82CCA2CCC2CCE2CD02CD22CD42CD62CD82CDA2CDC2CDE2CE02CE22CEB2CED2CF2A640A642A644A646A648A64AA64CA64EA650A652A654A656A658A65AA65CA65EA660A662A664A666A668A66AA66CA680A682A684A686A688A68AA68CA68EA690A692A694A696A722A724A726A728A72AA72CA72EA732A734A736A738A73AA73CA73EA740A742A744A746A748A74AA74CA74EA750A752A754A756A758A75AA75CA75EA760A762A764A766A768A76AA76CA76EA779A77BA77DA77EA780A782A784A786A78BA78DA790A792A7A0A7A2A7A4A7A6A7A8A7AAFF21-FF3A", + Lt: "01C501C801CB01F21F88-1F8F1F98-1F9F1FA8-1FAF1FBC1FCC1FFC", + Lm: "02B0-02C102C6-02D102E0-02E402EC02EE0374037A0559064006E506E607F407F507FA081A0824082809710E460EC610FC17D718431AA71C78-1C7D1D2C-1D6A1D781D9B-1DBF2071207F2090-209C2C7C2C7D2D6F2E2F30053031-3035303B309D309E30FC-30FEA015A4F8-A4FDA60CA67FA717-A71FA770A788A7F8A7F9A9CFAA70AADDAAF3AAF4FF70FF9EFF9F", + Lo: "00AA00BA01BB01C0-01C3029405D0-05EA05F0-05F20620-063F0641-064A066E066F0671-06D306D506EE06EF06FA-06FC06FF07100712-072F074D-07A507B107CA-07EA0800-08150840-085808A008A2-08AC0904-0939093D09500958-09610972-09770979-097F0985-098C098F09900993-09A809AA-09B009B209B6-09B909BD09CE09DC09DD09DF-09E109F009F10A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A59-0A5C0A5E0A72-0A740A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABD0AD00AE00AE10B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3D0B5C0B5D0B5F-0B610B710B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BD00C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D0C580C590C600C610C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBD0CDE0CE00CE10CF10CF20D05-0D0C0D0E-0D100D12-0D3A0D3D0D4E0D600D610D7A-0D7F0D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60E01-0E300E320E330E40-0E450E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB00EB20EB30EBD0EC0-0EC40EDC-0EDF0F000F40-0F470F49-0F6C0F88-0F8C1000-102A103F1050-1055105A-105D106110651066106E-10701075-1081108E10D0-10FA10FD-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A1380-138F13A0-13F41401-166C166F-167F1681-169A16A0-16EA1700-170C170E-17111720-17311740-17511760-176C176E-17701780-17B317DC1820-18421844-18771880-18A818AA18B0-18F51900-191C1950-196D1970-19741980-19AB19C1-19C71A00-1A161A20-1A541B05-1B331B45-1B4B1B83-1BA01BAE1BAF1BBA-1BE51C00-1C231C4D-1C4F1C5A-1C771CE9-1CEC1CEE-1CF11CF51CF62135-21382D30-2D672D80-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDE3006303C3041-3096309F30A1-30FA30FF3105-312D3131-318E31A0-31BA31F0-31FF3400-4DB54E00-9FCCA000-A014A016-A48CA4D0-A4F7A500-A60BA610-A61FA62AA62BA66EA6A0-A6E5A7FB-A801A803-A805A807-A80AA80C-A822A840-A873A882-A8B3A8F2-A8F7A8FBA90A-A925A930-A946A960-A97CA984-A9B2AA00-AA28AA40-AA42AA44-AA4BAA60-AA6FAA71-AA76AA7AAA80-AAAFAAB1AAB5AAB6AAB9-AABDAAC0AAC2AADBAADCAAE0-AAEAAAF2AB01-AB06AB09-AB0EAB11-AB16AB20-AB26AB28-AB2EABC0-ABE2AC00-D7A3D7B0-D7C6D7CB-D7FBF900-FA6DFA70-FAD9FB1DFB1F-FB28FB2A-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FBB1FBD3-FD3DFD50-FD8FFD92-FDC7FDF0-FDFBFE70-FE74FE76-FEFCFF66-FF6FFF71-FF9DFFA0-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDC", + M: "0300-036F0483-04890591-05BD05BF05C105C205C405C505C70610-061A064B-065F067006D6-06DC06DF-06E406E706E806EA-06ED07110730-074A07A6-07B007EB-07F30816-0819081B-08230825-08270829-082D0859-085B08E4-08FE0900-0903093A-093C093E-094F0951-0957096209630981-098309BC09BE-09C409C709C809CB-09CD09D709E209E30A01-0A030A3C0A3E-0A420A470A480A4B-0A4D0A510A700A710A750A81-0A830ABC0ABE-0AC50AC7-0AC90ACB-0ACD0AE20AE30B01-0B030B3C0B3E-0B440B470B480B4B-0B4D0B560B570B620B630B820BBE-0BC20BC6-0BC80BCA-0BCD0BD70C01-0C030C3E-0C440C46-0C480C4A-0C4D0C550C560C620C630C820C830CBC0CBE-0CC40CC6-0CC80CCA-0CCD0CD50CD60CE20CE30D020D030D3E-0D440D46-0D480D4A-0D4D0D570D620D630D820D830DCA0DCF-0DD40DD60DD8-0DDF0DF20DF30E310E34-0E3A0E47-0E4E0EB10EB4-0EB90EBB0EBC0EC8-0ECD0F180F190F350F370F390F3E0F3F0F71-0F840F860F870F8D-0F970F99-0FBC0FC6102B-103E1056-1059105E-10601062-10641067-106D1071-10741082-108D108F109A-109D135D-135F1712-17141732-1734175217531772177317B4-17D317DD180B-180D18A91920-192B1930-193B19B0-19C019C819C91A17-1A1B1A55-1A5E1A60-1A7C1A7F1B00-1B041B34-1B441B6B-1B731B80-1B821BA1-1BAD1BE6-1BF31C24-1C371CD0-1CD21CD4-1CE81CED1CF2-1CF41DC0-1DE61DFC-1DFF20D0-20F02CEF-2CF12D7F2DE0-2DFF302A-302F3099309AA66F-A672A674-A67DA69FA6F0A6F1A802A806A80BA823-A827A880A881A8B4-A8C4A8E0-A8F1A926-A92DA947-A953A980-A983A9B3-A9C0AA29-AA36AA43AA4CAA4DAA7BAAB0AAB2-AAB4AAB7AAB8AABEAABFAAC1AAEB-AAEFAAF5AAF6ABE3-ABEAABECABEDFB1EFE00-FE0FFE20-FE26", + Mn: "0300-036F0483-04870591-05BD05BF05C105C205C405C505C70610-061A064B-065F067006D6-06DC06DF-06E406E706E806EA-06ED07110730-074A07A6-07B007EB-07F30816-0819081B-08230825-08270829-082D0859-085B08E4-08FE0900-0902093A093C0941-0948094D0951-095709620963098109BC09C1-09C409CD09E209E30A010A020A3C0A410A420A470A480A4B-0A4D0A510A700A710A750A810A820ABC0AC1-0AC50AC70AC80ACD0AE20AE30B010B3C0B3F0B41-0B440B4D0B560B620B630B820BC00BCD0C3E-0C400C46-0C480C4A-0C4D0C550C560C620C630CBC0CBF0CC60CCC0CCD0CE20CE30D41-0D440D4D0D620D630DCA0DD2-0DD40DD60E310E34-0E3A0E47-0E4E0EB10EB4-0EB90EBB0EBC0EC8-0ECD0F180F190F350F370F390F71-0F7E0F80-0F840F860F870F8D-0F970F99-0FBC0FC6102D-10301032-10371039103A103D103E10581059105E-10601071-1074108210851086108D109D135D-135F1712-17141732-1734175217531772177317B417B517B7-17BD17C617C9-17D317DD180B-180D18A91920-19221927192819321939-193B1A171A181A561A58-1A5E1A601A621A65-1A6C1A73-1A7C1A7F1B00-1B031B341B36-1B3A1B3C1B421B6B-1B731B801B811BA2-1BA51BA81BA91BAB1BE61BE81BE91BED1BEF-1BF11C2C-1C331C361C371CD0-1CD21CD4-1CE01CE2-1CE81CED1CF41DC0-1DE61DFC-1DFF20D0-20DC20E120E5-20F02CEF-2CF12D7F2DE0-2DFF302A-302D3099309AA66FA674-A67DA69FA6F0A6F1A802A806A80BA825A826A8C4A8E0-A8F1A926-A92DA947-A951A980-A982A9B3A9B6-A9B9A9BCAA29-AA2EAA31AA32AA35AA36AA43AA4CAAB0AAB2-AAB4AAB7AAB8AABEAABFAAC1AAECAAEDAAF6ABE5ABE8ABEDFB1EFE00-FE0FFE20-FE26", + Mc: "0903093B093E-09400949-094C094E094F0982098309BE-09C009C709C809CB09CC09D70A030A3E-0A400A830ABE-0AC00AC90ACB0ACC0B020B030B3E0B400B470B480B4B0B4C0B570BBE0BBF0BC10BC20BC6-0BC80BCA-0BCC0BD70C01-0C030C41-0C440C820C830CBE0CC0-0CC40CC70CC80CCA0CCB0CD50CD60D020D030D3E-0D400D46-0D480D4A-0D4C0D570D820D830DCF-0DD10DD8-0DDF0DF20DF30F3E0F3F0F7F102B102C10311038103B103C105610571062-10641067-106D108310841087-108C108F109A-109C17B617BE-17C517C717C81923-19261929-192B193019311933-193819B0-19C019C819C91A19-1A1B1A551A571A611A631A641A6D-1A721B041B351B3B1B3D-1B411B431B441B821BA11BA61BA71BAA1BAC1BAD1BE71BEA-1BEC1BEE1BF21BF31C24-1C2B1C341C351CE11CF21CF3302E302FA823A824A827A880A881A8B4-A8C3A952A953A983A9B4A9B5A9BAA9BBA9BD-A9C0AA2FAA30AA33AA34AA4DAA7BAAEBAAEEAAEFAAF5ABE3ABE4ABE6ABE7ABE9ABEAABEC", + Me: "0488048920DD-20E020E2-20E4A670-A672", + N: "0030-003900B200B300B900BC-00BE0660-066906F0-06F907C0-07C90966-096F09E6-09EF09F4-09F90A66-0A6F0AE6-0AEF0B66-0B6F0B72-0B770BE6-0BF20C66-0C6F0C78-0C7E0CE6-0CEF0D66-0D750E50-0E590ED0-0ED90F20-0F331040-10491090-10991369-137C16EE-16F017E0-17E917F0-17F91810-18191946-194F19D0-19DA1A80-1A891A90-1A991B50-1B591BB0-1BB91C40-1C491C50-1C5920702074-20792080-20892150-21822185-21892460-249B24EA-24FF2776-27932CFD30073021-30293038-303A3192-31953220-32293248-324F3251-325F3280-328932B1-32BFA620-A629A6E6-A6EFA830-A835A8D0-A8D9A900-A909A9D0-A9D9AA50-AA59ABF0-ABF9FF10-FF19", + Nd: "0030-00390660-066906F0-06F907C0-07C90966-096F09E6-09EF0A66-0A6F0AE6-0AEF0B66-0B6F0BE6-0BEF0C66-0C6F0CE6-0CEF0D66-0D6F0E50-0E590ED0-0ED90F20-0F291040-10491090-109917E0-17E91810-18191946-194F19D0-19D91A80-1A891A90-1A991B50-1B591BB0-1BB91C40-1C491C50-1C59A620-A629A8D0-A8D9A900-A909A9D0-A9D9AA50-AA59ABF0-ABF9FF10-FF19", + Nl: "16EE-16F02160-21822185-218830073021-30293038-303AA6E6-A6EF", + No: "00B200B300B900BC-00BE09F4-09F90B72-0B770BF0-0BF20C78-0C7E0D70-0D750F2A-0F331369-137C17F0-17F919DA20702074-20792080-20892150-215F21892460-249B24EA-24FF2776-27932CFD3192-31953220-32293248-324F3251-325F3280-328932B1-32BFA830-A835", + P: "0021-00230025-002A002C-002F003A003B003F0040005B-005D005F007B007D00A100A700AB00B600B700BB00BF037E0387055A-055F0589058A05BE05C005C305C605F305F40609060A060C060D061B061E061F066A-066D06D40700-070D07F7-07F90830-083E085E0964096509700AF00DF40E4F0E5A0E5B0F04-0F120F140F3A-0F3D0F850FD0-0FD40FD90FDA104A-104F10FB1360-13681400166D166E169B169C16EB-16ED1735173617D4-17D617D8-17DA1800-180A194419451A1E1A1F1AA0-1AA61AA8-1AAD1B5A-1B601BFC-1BFF1C3B-1C3F1C7E1C7F1CC0-1CC71CD32010-20272030-20432045-20512053-205E207D207E208D208E2329232A2768-277527C527C627E6-27EF2983-299829D8-29DB29FC29FD2CF9-2CFC2CFE2CFF2D702E00-2E2E2E30-2E3B3001-30033008-30113014-301F3030303D30A030FBA4FEA4FFA60D-A60FA673A67EA6F2-A6F7A874-A877A8CEA8CFA8F8-A8FAA92EA92FA95FA9C1-A9CDA9DEA9DFAA5C-AA5FAADEAADFAAF0AAF1ABEBFD3EFD3FFE10-FE19FE30-FE52FE54-FE61FE63FE68FE6AFE6BFF01-FF03FF05-FF0AFF0C-FF0FFF1AFF1BFF1FFF20FF3B-FF3DFF3FFF5BFF5DFF5F-FF65", + Pd: "002D058A05BE140018062010-20152E172E1A2E3A2E3B301C303030A0FE31FE32FE58FE63FF0D", + Ps: "0028005B007B0F3A0F3C169B201A201E2045207D208D23292768276A276C276E27702772277427C527E627E827EA27EC27EE2983298529872989298B298D298F299129932995299729D829DA29FC2E222E242E262E283008300A300C300E3010301430163018301A301DFD3EFE17FE35FE37FE39FE3BFE3DFE3FFE41FE43FE47FE59FE5BFE5DFF08FF3BFF5BFF5FFF62", + Pe: "0029005D007D0F3B0F3D169C2046207E208E232A2769276B276D276F27712773277527C627E727E927EB27ED27EF298429862988298A298C298E2990299229942996299829D929DB29FD2E232E252E272E293009300B300D300F3011301530173019301B301E301FFD3FFE18FE36FE38FE3AFE3CFE3EFE40FE42FE44FE48FE5AFE5CFE5EFF09FF3DFF5DFF60FF63", + Pi: "00AB2018201B201C201F20392E022E042E092E0C2E1C2E20", + Pf: "00BB2019201D203A2E032E052E0A2E0D2E1D2E21", + Pc: "005F203F20402054FE33FE34FE4D-FE4FFF3F", + Po: "0021-00230025-0027002A002C002E002F003A003B003F0040005C00A100A700B600B700BF037E0387055A-055F058905C005C305C605F305F40609060A060C060D061B061E061F066A-066D06D40700-070D07F7-07F90830-083E085E0964096509700AF00DF40E4F0E5A0E5B0F04-0F120F140F850FD0-0FD40FD90FDA104A-104F10FB1360-1368166D166E16EB-16ED1735173617D4-17D617D8-17DA1800-18051807-180A194419451A1E1A1F1AA0-1AA61AA8-1AAD1B5A-1B601BFC-1BFF1C3B-1C3F1C7E1C7F1CC0-1CC71CD3201620172020-20272030-2038203B-203E2041-20432047-205120532055-205E2CF9-2CFC2CFE2CFF2D702E002E012E06-2E082E0B2E0E-2E162E182E192E1B2E1E2E1F2E2A-2E2E2E30-2E393001-3003303D30FBA4FEA4FFA60D-A60FA673A67EA6F2-A6F7A874-A877A8CEA8CFA8F8-A8FAA92EA92FA95FA9C1-A9CDA9DEA9DFAA5C-AA5FAADEAADFAAF0AAF1ABEBFE10-FE16FE19FE30FE45FE46FE49-FE4CFE50-FE52FE54-FE57FE5F-FE61FE68FE6AFE6BFF01-FF03FF05-FF07FF0AFF0CFF0EFF0FFF1AFF1BFF1FFF20FF3CFF61FF64FF65", + S: "0024002B003C-003E005E0060007C007E00A2-00A600A800A900AC00AE-00B100B400B800D700F702C2-02C502D2-02DF02E5-02EB02ED02EF-02FF03750384038503F60482058F0606-0608060B060E060F06DE06E906FD06FE07F609F209F309FA09FB0AF10B700BF3-0BFA0C7F0D790E3F0F01-0F030F130F15-0F170F1A-0F1F0F340F360F380FBE-0FC50FC7-0FCC0FCE0FCF0FD5-0FD8109E109F1390-139917DB194019DE-19FF1B61-1B6A1B74-1B7C1FBD1FBF-1FC11FCD-1FCF1FDD-1FDF1FED-1FEF1FFD1FFE20442052207A-207C208A-208C20A0-20B9210021012103-21062108210921142116-2118211E-2123212521272129212E213A213B2140-2144214A-214D214F2190-2328232B-23F32400-24262440-244A249C-24E92500-26FF2701-27672794-27C427C7-27E527F0-29822999-29D729DC-29FB29FE-2B4C2B50-2B592CE5-2CEA2E80-2E992E9B-2EF32F00-2FD52FF0-2FFB300430123013302030363037303E303F309B309C319031913196-319F31C0-31E33200-321E322A-324732503260-327F328A-32B032C0-32FE3300-33FF4DC0-4DFFA490-A4C6A700-A716A720A721A789A78AA828-A82BA836-A839AA77-AA79FB29FBB2-FBC1FDFCFDFDFE62FE64-FE66FE69FF04FF0BFF1C-FF1EFF3EFF40FF5CFF5EFFE0-FFE6FFE8-FFEEFFFCFFFD", + Sm: "002B003C-003E007C007E00AC00B100D700F703F60606-060820442052207A-207C208A-208C21182140-2144214B2190-2194219A219B21A021A321A621AE21CE21CF21D221D421F4-22FF2308-230B23202321237C239B-23B323DC-23E125B725C125F8-25FF266F27C0-27C427C7-27E527F0-27FF2900-29822999-29D729DC-29FB29FE-2AFF2B30-2B442B47-2B4CFB29FE62FE64-FE66FF0BFF1C-FF1EFF5CFF5EFFE2FFE9-FFEC", + Sc: "002400A2-00A5058F060B09F209F309FB0AF10BF90E3F17DB20A0-20B9A838FDFCFE69FF04FFE0FFE1FFE5FFE6", + Sk: "005E006000A800AF00B400B802C2-02C502D2-02DF02E5-02EB02ED02EF-02FF0375038403851FBD1FBF-1FC11FCD-1FCF1FDD-1FDF1FED-1FEF1FFD1FFE309B309CA700-A716A720A721A789A78AFBB2-FBC1FF3EFF40FFE3", + So: "00A600A900AE00B00482060E060F06DE06E906FD06FE07F609FA0B700BF3-0BF80BFA0C7F0D790F01-0F030F130F15-0F170F1A-0F1F0F340F360F380FBE-0FC50FC7-0FCC0FCE0FCF0FD5-0FD8109E109F1390-1399194019DE-19FF1B61-1B6A1B74-1B7C210021012103-210621082109211421162117211E-2123212521272129212E213A213B214A214C214D214F2195-2199219C-219F21A121A221A421A521A7-21AD21AF-21CD21D021D121D321D5-21F32300-2307230C-231F2322-2328232B-237B237D-239A23B4-23DB23E2-23F32400-24262440-244A249C-24E92500-25B625B8-25C025C2-25F72600-266E2670-26FF2701-27672794-27BF2800-28FF2B00-2B2F2B452B462B50-2B592CE5-2CEA2E80-2E992E9B-2EF32F00-2FD52FF0-2FFB300430123013302030363037303E303F319031913196-319F31C0-31E33200-321E322A-324732503260-327F328A-32B032C0-32FE3300-33FF4DC0-4DFFA490-A4C6A828-A82BA836A837A839AA77-AA79FDFDFFE4FFE8FFEDFFEEFFFCFFFD", + Z: "002000A01680180E2000-200A20282029202F205F3000", + Zs: "002000A01680180E2000-200A202F205F3000", + Zl: "2028", + Zp: "2029", + C: "0000-001F007F-009F00AD03780379037F-0383038B038D03A20528-05300557055805600588058B-058E059005C8-05CF05EB-05EF05F5-0605061C061D06DD070E070F074B074C07B2-07BF07FB-07FF082E082F083F085C085D085F-089F08A108AD-08E308FF097809800984098D098E0991099209A909B109B3-09B509BA09BB09C509C609C909CA09CF-09D609D8-09DB09DE09E409E509FC-0A000A040A0B-0A0E0A110A120A290A310A340A370A3A0A3B0A3D0A43-0A460A490A4A0A4E-0A500A52-0A580A5D0A5F-0A650A76-0A800A840A8E0A920AA90AB10AB40ABA0ABB0AC60ACA0ACE0ACF0AD1-0ADF0AE40AE50AF2-0B000B040B0D0B0E0B110B120B290B310B340B3A0B3B0B450B460B490B4A0B4E-0B550B58-0B5B0B5E0B640B650B78-0B810B840B8B-0B8D0B910B96-0B980B9B0B9D0BA0-0BA20BA5-0BA70BAB-0BAD0BBA-0BBD0BC3-0BC50BC90BCE0BCF0BD1-0BD60BD8-0BE50BFB-0C000C040C0D0C110C290C340C3A-0C3C0C450C490C4E-0C540C570C5A-0C5F0C640C650C70-0C770C800C810C840C8D0C910CA90CB40CBA0CBB0CC50CC90CCE-0CD40CD7-0CDD0CDF0CE40CE50CF00CF3-0D010D040D0D0D110D3B0D3C0D450D490D4F-0D560D58-0D5F0D640D650D76-0D780D800D810D840D97-0D990DB20DBC0DBE0DBF0DC7-0DC90DCB-0DCE0DD50DD70DE0-0DF10DF5-0E000E3B-0E3E0E5C-0E800E830E850E860E890E8B0E8C0E8E-0E930E980EA00EA40EA60EA80EA90EAC0EBA0EBE0EBF0EC50EC70ECE0ECF0EDA0EDB0EE0-0EFF0F480F6D-0F700F980FBD0FCD0FDB-0FFF10C610C8-10CC10CE10CF1249124E124F12571259125E125F1289128E128F12B112B612B712BF12C112C612C712D7131113161317135B135C137D-137F139A-139F13F5-13FF169D-169F16F1-16FF170D1715-171F1737-173F1754-175F176D17711774-177F17DE17DF17EA-17EF17FA-17FF180F181A-181F1878-187F18AB-18AF18F6-18FF191D-191F192C-192F193C-193F1941-1943196E196F1975-197F19AC-19AF19CA-19CF19DB-19DD1A1C1A1D1A5F1A7D1A7E1A8A-1A8F1A9A-1A9F1AAE-1AFF1B4C-1B4F1B7D-1B7F1BF4-1BFB1C38-1C3A1C4A-1C4C1C80-1CBF1CC8-1CCF1CF7-1CFF1DE7-1DFB1F161F171F1E1F1F1F461F471F4E1F4F1F581F5A1F5C1F5E1F7E1F7F1FB51FC51FD41FD51FDC1FF01FF11FF51FFF200B-200F202A-202E2060-206F20722073208F209D-209F20BA-20CF20F1-20FF218A-218F23F4-23FF2427-243F244B-245F27002B4D-2B4F2B5A-2BFF2C2F2C5F2CF4-2CF82D262D28-2D2C2D2E2D2F2D68-2D6E2D71-2D7E2D97-2D9F2DA72DAF2DB72DBF2DC72DCF2DD72DDF2E3C-2E7F2E9A2EF4-2EFF2FD6-2FEF2FFC-2FFF3040309730983100-3104312E-3130318F31BB-31BF31E4-31EF321F32FF4DB6-4DBF9FCD-9FFFA48D-A48FA4C7-A4CFA62C-A63FA698-A69EA6F8-A6FFA78FA794-A79FA7AB-A7F7A82C-A82FA83A-A83FA878-A87FA8C5-A8CDA8DA-A8DFA8FC-A8FFA954-A95EA97D-A97FA9CEA9DA-A9DDA9E0-A9FFAA37-AA3FAA4EAA4FAA5AAA5BAA7C-AA7FAAC3-AADAAAF7-AB00AB07AB08AB0FAB10AB17-AB1FAB27AB2F-ABBFABEEABEFABFA-ABFFD7A4-D7AFD7C7-D7CAD7FC-F8FFFA6EFA6FFADA-FAFFFB07-FB12FB18-FB1CFB37FB3DFB3FFB42FB45FBC2-FBD2FD40-FD4FFD90FD91FDC8-FDEFFDFEFDFFFE1A-FE1FFE27-FE2FFE53FE67FE6C-FE6FFE75FEFD-FF00FFBF-FFC1FFC8FFC9FFD0FFD1FFD8FFD9FFDD-FFDFFFE7FFEF-FFFBFFFEFFFF", + Cc: "0000-001F007F-009F", + Cf: "00AD0600-060406DD070F200B-200F202A-202E2060-2064206A-206FFEFFFFF9-FFFB", + Co: "E000-F8FF", + Cs: "D800-DFFF", + Cn: "03780379037F-0383038B038D03A20528-05300557055805600588058B-058E059005C8-05CF05EB-05EF05F5-05FF0605061C061D070E074B074C07B2-07BF07FB-07FF082E082F083F085C085D085F-089F08A108AD-08E308FF097809800984098D098E0991099209A909B109B3-09B509BA09BB09C509C609C909CA09CF-09D609D8-09DB09DE09E409E509FC-0A000A040A0B-0A0E0A110A120A290A310A340A370A3A0A3B0A3D0A43-0A460A490A4A0A4E-0A500A52-0A580A5D0A5F-0A650A76-0A800A840A8E0A920AA90AB10AB40ABA0ABB0AC60ACA0ACE0ACF0AD1-0ADF0AE40AE50AF2-0B000B040B0D0B0E0B110B120B290B310B340B3A0B3B0B450B460B490B4A0B4E-0B550B58-0B5B0B5E0B640B650B78-0B810B840B8B-0B8D0B910B96-0B980B9B0B9D0BA0-0BA20BA5-0BA70BAB-0BAD0BBA-0BBD0BC3-0BC50BC90BCE0BCF0BD1-0BD60BD8-0BE50BFB-0C000C040C0D0C110C290C340C3A-0C3C0C450C490C4E-0C540C570C5A-0C5F0C640C650C70-0C770C800C810C840C8D0C910CA90CB40CBA0CBB0CC50CC90CCE-0CD40CD7-0CDD0CDF0CE40CE50CF00CF3-0D010D040D0D0D110D3B0D3C0D450D490D4F-0D560D58-0D5F0D640D650D76-0D780D800D810D840D97-0D990DB20DBC0DBE0DBF0DC7-0DC90DCB-0DCE0DD50DD70DE0-0DF10DF5-0E000E3B-0E3E0E5C-0E800E830E850E860E890E8B0E8C0E8E-0E930E980EA00EA40EA60EA80EA90EAC0EBA0EBE0EBF0EC50EC70ECE0ECF0EDA0EDB0EE0-0EFF0F480F6D-0F700F980FBD0FCD0FDB-0FFF10C610C8-10CC10CE10CF1249124E124F12571259125E125F1289128E128F12B112B612B712BF12C112C612C712D7131113161317135B135C137D-137F139A-139F13F5-13FF169D-169F16F1-16FF170D1715-171F1737-173F1754-175F176D17711774-177F17DE17DF17EA-17EF17FA-17FF180F181A-181F1878-187F18AB-18AF18F6-18FF191D-191F192C-192F193C-193F1941-1943196E196F1975-197F19AC-19AF19CA-19CF19DB-19DD1A1C1A1D1A5F1A7D1A7E1A8A-1A8F1A9A-1A9F1AAE-1AFF1B4C-1B4F1B7D-1B7F1BF4-1BFB1C38-1C3A1C4A-1C4C1C80-1CBF1CC8-1CCF1CF7-1CFF1DE7-1DFB1F161F171F1E1F1F1F461F471F4E1F4F1F581F5A1F5C1F5E1F7E1F7F1FB51FC51FD41FD51FDC1FF01FF11FF51FFF2065-206920722073208F209D-209F20BA-20CF20F1-20FF218A-218F23F4-23FF2427-243F244B-245F27002B4D-2B4F2B5A-2BFF2C2F2C5F2CF4-2CF82D262D28-2D2C2D2E2D2F2D68-2D6E2D71-2D7E2D97-2D9F2DA72DAF2DB72DBF2DC72DCF2DD72DDF2E3C-2E7F2E9A2EF4-2EFF2FD6-2FEF2FFC-2FFF3040309730983100-3104312E-3130318F31BB-31BF31E4-31EF321F32FF4DB6-4DBF9FCD-9FFFA48D-A48FA4C7-A4CFA62C-A63FA698-A69EA6F8-A6FFA78FA794-A79FA7AB-A7F7A82C-A82FA83A-A83FA878-A87FA8C5-A8CDA8DA-A8DFA8FC-A8FFA954-A95EA97D-A97FA9CEA9DA-A9DDA9E0-A9FFAA37-AA3FAA4EAA4FAA5AAA5BAA7C-AA7FAAC3-AADAAAF7-AB00AB07AB08AB0FAB10AB17-AB1FAB27AB2F-ABBFABEEABEFABFA-ABFFD7A4-D7AFD7C7-D7CAD7FC-D7FFFA6EFA6FFADA-FAFFFB07-FB12FB18-FB1CFB37FB3DFB3FFB42FB45FBC2-FBD2FD40-FD4FFD90FD91FDC8-FDEFFDFEFDFFFE1A-FE1FFE27-FE2FFE53FE67FE6C-FE6FFE75FEFDFEFEFF00FFBF-FFC1FFC8FFC9FFD0FFD1FFD8FFD9FFDD-FFDFFFE7FFEF-FFF8FFFEFFFF" + }, { + //L: "Letter", // Included in the Unicode Base addon + Ll: "Lowercase_Letter", + Lu: "Uppercase_Letter", + Lt: "Titlecase_Letter", + Lm: "Modifier_Letter", + Lo: "Other_Letter", + M: "Mark", + Mn: "Nonspacing_Mark", + Mc: "Spacing_Mark", + Me: "Enclosing_Mark", + N: "Number", + Nd: "Decimal_Number", + Nl: "Letter_Number", + No: "Other_Number", + P: "Punctuation", + Pd: "Dash_Punctuation", + Ps: "Open_Punctuation", + Pe: "Close_Punctuation", + Pi: "Initial_Punctuation", + Pf: "Final_Punctuation", + Pc: "Connector_Punctuation", + Po: "Other_Punctuation", + S: "Symbol", + Sm: "Math_Symbol", + Sc: "Currency_Symbol", + Sk: "Modifier_Symbol", + So: "Other_Symbol", + Z: "Separator", + Zs: "Space_Separator", + Zl: "Line_Separator", + Zp: "Paragraph_Separator", + C: "Other", + Cc: "Control", + Cf: "Format", + Co: "Private_Use", + Cs: "Surrogate", + Cn: "Unassigned" + }); + +}(XRegExp)); + + +/***** unicode-scripts.js *****/ + +/*! + * XRegExp Unicode Scripts v1.2.0 + * (c) 2010-2012 Steven Levithan + * MIT License + * Uses Unicode 6.1 + */ + +/** + * Adds support for all Unicode scripts in the Basic Multilingual Plane (U+0000-U+FFFF). + * E.g., `\p{Latin}`. Token names are case insensitive, and any spaces, hyphens, and underscores + * are ignored. + * @requires XRegExp, XRegExp Unicode Base + */ +(function (XRegExp) { + "use strict"; + + if (!XRegExp.addUnicodePackage) { + throw new ReferenceError("Unicode Base must be loaded before Unicode Scripts"); + } + + XRegExp.install("extensibility"); + + XRegExp.addUnicodePackage({ + Arabic: "0600-06040606-060B060D-061A061E0620-063F0641-064A0656-065E066A-066F0671-06DC06DE-06FF0750-077F08A008A2-08AC08E4-08FEFB50-FBC1FBD3-FD3DFD50-FD8FFD92-FDC7FDF0-FDFCFE70-FE74FE76-FEFC", + Armenian: "0531-05560559-055F0561-0587058A058FFB13-FB17", + Balinese: "1B00-1B4B1B50-1B7C", + Bamum: "A6A0-A6F7", + Batak: "1BC0-1BF31BFC-1BFF", + Bengali: "0981-09830985-098C098F09900993-09A809AA-09B009B209B6-09B909BC-09C409C709C809CB-09CE09D709DC09DD09DF-09E309E6-09FB", + Bopomofo: "02EA02EB3105-312D31A0-31BA", + Braille: "2800-28FF", + Buginese: "1A00-1A1B1A1E1A1F", + Buhid: "1740-1753", + Canadian_Aboriginal: "1400-167F18B0-18F5", + Cham: "AA00-AA36AA40-AA4DAA50-AA59AA5C-AA5F", + Cherokee: "13A0-13F4", + Common: "0000-0040005B-0060007B-00A900AB-00B900BB-00BF00D700F702B9-02DF02E5-02E902EC-02FF0374037E038503870589060C061B061F06400660-066906DD096409650E3F0FD5-0FD810FB16EB-16ED173517361802180318051CD31CE11CE9-1CEC1CEE-1CF31CF51CF62000-200B200E-2064206A-20702074-207E2080-208E20A0-20B92100-21252127-2129212C-21312133-214D214F-215F21892190-23F32400-24262440-244A2460-26FF2701-27FF2900-2B4C2B50-2B592E00-2E3B2FF0-2FFB3000-300430063008-30203030-3037303C-303F309B309C30A030FB30FC3190-319F31C0-31E33220-325F327F-32CF3358-33FF4DC0-4DFFA700-A721A788-A78AA830-A839FD3EFD3FFDFDFE10-FE19FE30-FE52FE54-FE66FE68-FE6BFEFFFF01-FF20FF3B-FF40FF5B-FF65FF70FF9EFF9FFFE0-FFE6FFE8-FFEEFFF9-FFFD", + Coptic: "03E2-03EF2C80-2CF32CF9-2CFF", + Cyrillic: "0400-04840487-05271D2B1D782DE0-2DFFA640-A697A69F", + Devanagari: "0900-09500953-09630966-09770979-097FA8E0-A8FB", + Ethiopic: "1200-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A135D-137C1380-13992D80-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDEAB01-AB06AB09-AB0EAB11-AB16AB20-AB26AB28-AB2E", + Georgian: "10A0-10C510C710CD10D0-10FA10FC-10FF2D00-2D252D272D2D", + Glagolitic: "2C00-2C2E2C30-2C5E", + Greek: "0370-03730375-0377037A-037D038403860388-038A038C038E-03A103A3-03E103F0-03FF1D26-1D2A1D5D-1D611D66-1D6A1DBF1F00-1F151F18-1F1D1F20-1F451F48-1F4D1F50-1F571F591F5B1F5D1F5F-1F7D1F80-1FB41FB6-1FC41FC6-1FD31FD6-1FDB1FDD-1FEF1FF2-1FF41FF6-1FFE2126", + Gujarati: "0A81-0A830A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABC-0AC50AC7-0AC90ACB-0ACD0AD00AE0-0AE30AE6-0AF1", + Gurmukhi: "0A01-0A030A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A3C0A3E-0A420A470A480A4B-0A4D0A510A59-0A5C0A5E0A66-0A75", + Han: "2E80-2E992E9B-2EF32F00-2FD5300530073021-30293038-303B3400-4DB54E00-9FCCF900-FA6DFA70-FAD9", + Hangul: "1100-11FF302E302F3131-318E3200-321E3260-327EA960-A97CAC00-D7A3D7B0-D7C6D7CB-D7FBFFA0-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDC", + Hanunoo: "1720-1734", + Hebrew: "0591-05C705D0-05EA05F0-05F4FB1D-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FB4F", + Hiragana: "3041-3096309D-309F", + Inherited: "0300-036F04850486064B-0655065F0670095109521CD0-1CD21CD4-1CE01CE2-1CE81CED1CF41DC0-1DE61DFC-1DFF200C200D20D0-20F0302A-302D3099309AFE00-FE0FFE20-FE26", + Javanese: "A980-A9CDA9CF-A9D9A9DEA9DF", + Kannada: "0C820C830C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBC-0CC40CC6-0CC80CCA-0CCD0CD50CD60CDE0CE0-0CE30CE6-0CEF0CF10CF2", + Katakana: "30A1-30FA30FD-30FF31F0-31FF32D0-32FE3300-3357FF66-FF6FFF71-FF9D", + Kayah_Li: "A900-A92F", + Khmer: "1780-17DD17E0-17E917F0-17F919E0-19FF", + Lao: "0E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB90EBB-0EBD0EC0-0EC40EC60EC8-0ECD0ED0-0ED90EDC-0EDF", + Latin: "0041-005A0061-007A00AA00BA00C0-00D600D8-00F600F8-02B802E0-02E41D00-1D251D2C-1D5C1D62-1D651D6B-1D771D79-1DBE1E00-1EFF2071207F2090-209C212A212B2132214E2160-21882C60-2C7FA722-A787A78B-A78EA790-A793A7A0-A7AAA7F8-A7FFFB00-FB06FF21-FF3AFF41-FF5A", + Lepcha: "1C00-1C371C3B-1C491C4D-1C4F", + Limbu: "1900-191C1920-192B1930-193B19401944-194F", + Lisu: "A4D0-A4FF", + Malayalam: "0D020D030D05-0D0C0D0E-0D100D12-0D3A0D3D-0D440D46-0D480D4A-0D4E0D570D60-0D630D66-0D750D79-0D7F", + Mandaic: "0840-085B085E", + Meetei_Mayek: "AAE0-AAF6ABC0-ABEDABF0-ABF9", + Mongolian: "1800180118041806-180E1810-18191820-18771880-18AA", + Myanmar: "1000-109FAA60-AA7B", + New_Tai_Lue: "1980-19AB19B0-19C919D0-19DA19DE19DF", + Nko: "07C0-07FA", + Ogham: "1680-169C", + Ol_Chiki: "1C50-1C7F", + Oriya: "0B01-0B030B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3C-0B440B470B480B4B-0B4D0B560B570B5C0B5D0B5F-0B630B66-0B77", + Phags_Pa: "A840-A877", + Rejang: "A930-A953A95F", + Runic: "16A0-16EA16EE-16F0", + Samaritan: "0800-082D0830-083E", + Saurashtra: "A880-A8C4A8CE-A8D9", + Sinhala: "0D820D830D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60DCA0DCF-0DD40DD60DD8-0DDF0DF2-0DF4", + Sundanese: "1B80-1BBF1CC0-1CC7", + Syloti_Nagri: "A800-A82B", + Syriac: "0700-070D070F-074A074D-074F", + Tagalog: "1700-170C170E-1714", + Tagbanwa: "1760-176C176E-177017721773", + Tai_Le: "1950-196D1970-1974", + Tai_Tham: "1A20-1A5E1A60-1A7C1A7F-1A891A90-1A991AA0-1AAD", + Tai_Viet: "AA80-AAC2AADB-AADF", + Tamil: "0B820B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BBE-0BC20BC6-0BC80BCA-0BCD0BD00BD70BE6-0BFA", + Telugu: "0C01-0C030C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D-0C440C46-0C480C4A-0C4D0C550C560C580C590C60-0C630C66-0C6F0C78-0C7F", + Thaana: "0780-07B1", + Thai: "0E01-0E3A0E40-0E5B", + Tibetan: "0F00-0F470F49-0F6C0F71-0F970F99-0FBC0FBE-0FCC0FCE-0FD40FD90FDA", + Tifinagh: "2D30-2D672D6F2D702D7F", + Vai: "A500-A62B", + Yi: "A000-A48CA490-A4C6" + }); + +}(XRegExp)); + + +/***** unicode-blocks.js *****/ + +/*! + * XRegExp Unicode Blocks v1.2.0 + * (c) 2010-2012 Steven Levithan + * MIT License + * Uses Unicode 6.1 + */ + +/** + * Adds support for all Unicode blocks in the Basic Multilingual Plane (U+0000-U+FFFF). Unicode + * blocks use the prefix "In". E.g., `\p{InBasicLatin}`. Token names are case insensitive, and any + * spaces, hyphens, and underscores are ignored. + * @requires XRegExp, XRegExp Unicode Base + */ +(function (XRegExp) { + "use strict"; + + if (!XRegExp.addUnicodePackage) { + throw new ReferenceError("Unicode Base must be loaded before Unicode Blocks"); + } + + XRegExp.install("extensibility"); + + XRegExp.addUnicodePackage({ + InBasic_Latin: "0000-007F", + InLatin_1_Supplement: "0080-00FF", + InLatin_Extended_A: "0100-017F", + InLatin_Extended_B: "0180-024F", + InIPA_Extensions: "0250-02AF", + InSpacing_Modifier_Letters: "02B0-02FF", + InCombining_Diacritical_Marks: "0300-036F", + InGreek_and_Coptic: "0370-03FF", + InCyrillic: "0400-04FF", + InCyrillic_Supplement: "0500-052F", + InArmenian: "0530-058F", + InHebrew: "0590-05FF", + InArabic: "0600-06FF", + InSyriac: "0700-074F", + InArabic_Supplement: "0750-077F", + InThaana: "0780-07BF", + InNKo: "07C0-07FF", + InSamaritan: "0800-083F", + InMandaic: "0840-085F", + InArabic_Extended_A: "08A0-08FF", + InDevanagari: "0900-097F", + InBengali: "0980-09FF", + InGurmukhi: "0A00-0A7F", + InGujarati: "0A80-0AFF", + InOriya: "0B00-0B7F", + InTamil: "0B80-0BFF", + InTelugu: "0C00-0C7F", + InKannada: "0C80-0CFF", + InMalayalam: "0D00-0D7F", + InSinhala: "0D80-0DFF", + InThai: "0E00-0E7F", + InLao: "0E80-0EFF", + InTibetan: "0F00-0FFF", + InMyanmar: "1000-109F", + InGeorgian: "10A0-10FF", + InHangul_Jamo: "1100-11FF", + InEthiopic: "1200-137F", + InEthiopic_Supplement: "1380-139F", + InCherokee: "13A0-13FF", + InUnified_Canadian_Aboriginal_Syllabics: "1400-167F", + InOgham: "1680-169F", + InRunic: "16A0-16FF", + InTagalog: "1700-171F", + InHanunoo: "1720-173F", + InBuhid: "1740-175F", + InTagbanwa: "1760-177F", + InKhmer: "1780-17FF", + InMongolian: "1800-18AF", + InUnified_Canadian_Aboriginal_Syllabics_Extended: "18B0-18FF", + InLimbu: "1900-194F", + InTai_Le: "1950-197F", + InNew_Tai_Lue: "1980-19DF", + InKhmer_Symbols: "19E0-19FF", + InBuginese: "1A00-1A1F", + InTai_Tham: "1A20-1AAF", + InBalinese: "1B00-1B7F", + InSundanese: "1B80-1BBF", + InBatak: "1BC0-1BFF", + InLepcha: "1C00-1C4F", + InOl_Chiki: "1C50-1C7F", + InSundanese_Supplement: "1CC0-1CCF", + InVedic_Extensions: "1CD0-1CFF", + InPhonetic_Extensions: "1D00-1D7F", + InPhonetic_Extensions_Supplement: "1D80-1DBF", + InCombining_Diacritical_Marks_Supplement: "1DC0-1DFF", + InLatin_Extended_Additional: "1E00-1EFF", + InGreek_Extended: "1F00-1FFF", + InGeneral_Punctuation: "2000-206F", + InSuperscripts_and_Subscripts: "2070-209F", + InCurrency_Symbols: "20A0-20CF", + InCombining_Diacritical_Marks_for_Symbols: "20D0-20FF", + InLetterlike_Symbols: "2100-214F", + InNumber_Forms: "2150-218F", + InArrows: "2190-21FF", + InMathematical_Operators: "2200-22FF", + InMiscellaneous_Technical: "2300-23FF", + InControl_Pictures: "2400-243F", + InOptical_Character_Recognition: "2440-245F", + InEnclosed_Alphanumerics: "2460-24FF", + InBox_Drawing: "2500-257F", + InBlock_Elements: "2580-259F", + InGeometric_Shapes: "25A0-25FF", + InMiscellaneous_Symbols: "2600-26FF", + InDingbats: "2700-27BF", + InMiscellaneous_Mathematical_Symbols_A: "27C0-27EF", + InSupplemental_Arrows_A: "27F0-27FF", + InBraille_Patterns: "2800-28FF", + InSupplemental_Arrows_B: "2900-297F", + InMiscellaneous_Mathematical_Symbols_B: "2980-29FF", + InSupplemental_Mathematical_Operators: "2A00-2AFF", + InMiscellaneous_Symbols_and_Arrows: "2B00-2BFF", + InGlagolitic: "2C00-2C5F", + InLatin_Extended_C: "2C60-2C7F", + InCoptic: "2C80-2CFF", + InGeorgian_Supplement: "2D00-2D2F", + InTifinagh: "2D30-2D7F", + InEthiopic_Extended: "2D80-2DDF", + InCyrillic_Extended_A: "2DE0-2DFF", + InSupplemental_Punctuation: "2E00-2E7F", + InCJK_Radicals_Supplement: "2E80-2EFF", + InKangxi_Radicals: "2F00-2FDF", + InIdeographic_Description_Characters: "2FF0-2FFF", + InCJK_Symbols_and_Punctuation: "3000-303F", + InHiragana: "3040-309F", + InKatakana: "30A0-30FF", + InBopomofo: "3100-312F", + InHangul_Compatibility_Jamo: "3130-318F", + InKanbun: "3190-319F", + InBopomofo_Extended: "31A0-31BF", + InCJK_Strokes: "31C0-31EF", + InKatakana_Phonetic_Extensions: "31F0-31FF", + InEnclosed_CJK_Letters_and_Months: "3200-32FF", + InCJK_Compatibility: "3300-33FF", + InCJK_Unified_Ideographs_Extension_A: "3400-4DBF", + InYijing_Hexagram_Symbols: "4DC0-4DFF", + InCJK_Unified_Ideographs: "4E00-9FFF", + InYi_Syllables: "A000-A48F", + InYi_Radicals: "A490-A4CF", + InLisu: "A4D0-A4FF", + InVai: "A500-A63F", + InCyrillic_Extended_B: "A640-A69F", + InBamum: "A6A0-A6FF", + InModifier_Tone_Letters: "A700-A71F", + InLatin_Extended_D: "A720-A7FF", + InSyloti_Nagri: "A800-A82F", + InCommon_Indic_Number_Forms: "A830-A83F", + InPhags_pa: "A840-A87F", + InSaurashtra: "A880-A8DF", + InDevanagari_Extended: "A8E0-A8FF", + InKayah_Li: "A900-A92F", + InRejang: "A930-A95F", + InHangul_Jamo_Extended_A: "A960-A97F", + InJavanese: "A980-A9DF", + InCham: "AA00-AA5F", + InMyanmar_Extended_A: "AA60-AA7F", + InTai_Viet: "AA80-AADF", + InMeetei_Mayek_Extensions: "AAE0-AAFF", + InEthiopic_Extended_A: "AB00-AB2F", + InMeetei_Mayek: "ABC0-ABFF", + InHangul_Syllables: "AC00-D7AF", + InHangul_Jamo_Extended_B: "D7B0-D7FF", + InHigh_Surrogates: "D800-DB7F", + InHigh_Private_Use_Surrogates: "DB80-DBFF", + InLow_Surrogates: "DC00-DFFF", + InPrivate_Use_Area: "E000-F8FF", + InCJK_Compatibility_Ideographs: "F900-FAFF", + InAlphabetic_Presentation_Forms: "FB00-FB4F", + InArabic_Presentation_Forms_A: "FB50-FDFF", + InVariation_Selectors: "FE00-FE0F", + InVertical_Forms: "FE10-FE1F", + InCombining_Half_Marks: "FE20-FE2F", + InCJK_Compatibility_Forms: "FE30-FE4F", + InSmall_Form_Variants: "FE50-FE6F", + InArabic_Presentation_Forms_B: "FE70-FEFF", + InHalfwidth_and_Fullwidth_Forms: "FF00-FFEF", + InSpecials: "FFF0-FFFF" + }); + +}(XRegExp)); + + +/***** unicode-properties.js *****/ + +/*! + * XRegExp Unicode Properties v1.0.0 + * (c) 2012 Steven Levithan + * MIT License + * Uses Unicode 6.1 + */ + +/** + * Adds Unicode properties necessary to meet Level 1 Unicode support (detailed in UTS#18 RL1.2). + * Includes code points from the Basic Multilingual Plane (U+0000-U+FFFF) only. Token names are + * case insensitive, and any spaces, hyphens, and underscores are ignored. + * @requires XRegExp, XRegExp Unicode Base + */ +(function (XRegExp) { + "use strict"; + + if (!XRegExp.addUnicodePackage) { + throw new ReferenceError("Unicode Base must be loaded before Unicode Properties"); + } + + XRegExp.install("extensibility"); + + XRegExp.addUnicodePackage({ + Alphabetic: "0041-005A0061-007A00AA00B500BA00C0-00D600D8-00F600F8-02C102C6-02D102E0-02E402EC02EE03450370-037403760377037A-037D03860388-038A038C038E-03A103A3-03F503F7-0481048A-05270531-055605590561-058705B0-05BD05BF05C105C205C405C505C705D0-05EA05F0-05F20610-061A0620-06570659-065F066E-06D306D5-06DC06E1-06E806ED-06EF06FA-06FC06FF0710-073F074D-07B107CA-07EA07F407F507FA0800-0817081A-082C0840-085808A008A2-08AC08E4-08E908F0-08FE0900-093B093D-094C094E-09500955-09630971-09770979-097F0981-09830985-098C098F09900993-09A809AA-09B009B209B6-09B909BD-09C409C709C809CB09CC09CE09D709DC09DD09DF-09E309F009F10A01-0A030A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A3E-0A420A470A480A4B0A4C0A510A59-0A5C0A5E0A70-0A750A81-0A830A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABD-0AC50AC7-0AC90ACB0ACC0AD00AE0-0AE30B01-0B030B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3D-0B440B470B480B4B0B4C0B560B570B5C0B5D0B5F-0B630B710B820B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BBE-0BC20BC6-0BC80BCA-0BCC0BD00BD70C01-0C030C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D-0C440C46-0C480C4A-0C4C0C550C560C580C590C60-0C630C820C830C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBD-0CC40CC6-0CC80CCA-0CCC0CD50CD60CDE0CE0-0CE30CF10CF20D020D030D05-0D0C0D0E-0D100D12-0D3A0D3D-0D440D46-0D480D4A-0D4C0D4E0D570D60-0D630D7A-0D7F0D820D830D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60DCF-0DD40DD60DD8-0DDF0DF20DF30E01-0E3A0E40-0E460E4D0E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB90EBB-0EBD0EC0-0EC40EC60ECD0EDC-0EDF0F000F40-0F470F49-0F6C0F71-0F810F88-0F970F99-0FBC1000-10361038103B-103F1050-10621065-1068106E-1086108E109C109D10A0-10C510C710CD10D0-10FA10FC-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A135F1380-138F13A0-13F41401-166C166F-167F1681-169A16A0-16EA16EE-16F01700-170C170E-17131720-17331740-17531760-176C176E-1770177217731780-17B317B6-17C817D717DC1820-18771880-18AA18B0-18F51900-191C1920-192B1930-19381950-196D1970-19741980-19AB19B0-19C91A00-1A1B1A20-1A5E1A61-1A741AA71B00-1B331B35-1B431B45-1B4B1B80-1BA91BAC-1BAF1BBA-1BE51BE7-1BF11C00-1C351C4D-1C4F1C5A-1C7D1CE9-1CEC1CEE-1CF31CF51CF61D00-1DBF1E00-1F151F18-1F1D1F20-1F451F48-1F4D1F50-1F571F591F5B1F5D1F5F-1F7D1F80-1FB41FB6-1FBC1FBE1FC2-1FC41FC6-1FCC1FD0-1FD31FD6-1FDB1FE0-1FEC1FF2-1FF41FF6-1FFC2071207F2090-209C21022107210A-211321152119-211D212421262128212A-212D212F-2139213C-213F2145-2149214E2160-218824B6-24E92C00-2C2E2C30-2C5E2C60-2CE42CEB-2CEE2CF22CF32D00-2D252D272D2D2D30-2D672D6F2D80-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDE2DE0-2DFF2E2F3005-30073021-30293031-30353038-303C3041-3096309D-309F30A1-30FA30FC-30FF3105-312D3131-318E31A0-31BA31F0-31FF3400-4DB54E00-9FCCA000-A48CA4D0-A4FDA500-A60CA610-A61FA62AA62BA640-A66EA674-A67BA67F-A697A69F-A6EFA717-A71FA722-A788A78B-A78EA790-A793A7A0-A7AAA7F8-A801A803-A805A807-A80AA80C-A827A840-A873A880-A8C3A8F2-A8F7A8FBA90A-A92AA930-A952A960-A97CA980-A9B2A9B4-A9BFA9CFAA00-AA36AA40-AA4DAA60-AA76AA7AAA80-AABEAAC0AAC2AADB-AADDAAE0-AAEFAAF2-AAF5AB01-AB06AB09-AB0EAB11-AB16AB20-AB26AB28-AB2EABC0-ABEAAC00-D7A3D7B0-D7C6D7CB-D7FBF900-FA6DFA70-FAD9FB00-FB06FB13-FB17FB1D-FB28FB2A-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FBB1FBD3-FD3DFD50-FD8FFD92-FDC7FDF0-FDFBFE70-FE74FE76-FEFCFF21-FF3AFF41-FF5AFF66-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDC", + Uppercase: "0041-005A00C0-00D600D8-00DE01000102010401060108010A010C010E01100112011401160118011A011C011E01200122012401260128012A012C012E01300132013401360139013B013D013F0141014301450147014A014C014E01500152015401560158015A015C015E01600162016401660168016A016C016E017001720174017601780179017B017D018101820184018601870189-018B018E-0191019301940196-0198019C019D019F01A001A201A401A601A701A901AC01AE01AF01B1-01B301B501B701B801BC01C401C701CA01CD01CF01D101D301D501D701D901DB01DE01E001E201E401E601E801EA01EC01EE01F101F401F6-01F801FA01FC01FE02000202020402060208020A020C020E02100212021402160218021A021C021E02200222022402260228022A022C022E02300232023A023B023D023E02410243-02460248024A024C024E03700372037603860388-038A038C038E038F0391-03A103A3-03AB03CF03D2-03D403D803DA03DC03DE03E003E203E403E603E803EA03EC03EE03F403F703F903FA03FD-042F04600462046404660468046A046C046E04700472047404760478047A047C047E0480048A048C048E04900492049404960498049A049C049E04A004A204A404A604A804AA04AC04AE04B004B204B404B604B804BA04BC04BE04C004C104C304C504C704C904CB04CD04D004D204D404D604D804DA04DC04DE04E004E204E404E604E804EA04EC04EE04F004F204F404F604F804FA04FC04FE05000502050405060508050A050C050E05100512051405160518051A051C051E05200522052405260531-055610A0-10C510C710CD1E001E021E041E061E081E0A1E0C1E0E1E101E121E141E161E181E1A1E1C1E1E1E201E221E241E261E281E2A1E2C1E2E1E301E321E341E361E381E3A1E3C1E3E1E401E421E441E461E481E4A1E4C1E4E1E501E521E541E561E581E5A1E5C1E5E1E601E621E641E661E681E6A1E6C1E6E1E701E721E741E761E781E7A1E7C1E7E1E801E821E841E861E881E8A1E8C1E8E1E901E921E941E9E1EA01EA21EA41EA61EA81EAA1EAC1EAE1EB01EB21EB41EB61EB81EBA1EBC1EBE1EC01EC21EC41EC61EC81ECA1ECC1ECE1ED01ED21ED41ED61ED81EDA1EDC1EDE1EE01EE21EE41EE61EE81EEA1EEC1EEE1EF01EF21EF41EF61EF81EFA1EFC1EFE1F08-1F0F1F18-1F1D1F28-1F2F1F38-1F3F1F48-1F4D1F591F5B1F5D1F5F1F68-1F6F1FB8-1FBB1FC8-1FCB1FD8-1FDB1FE8-1FEC1FF8-1FFB21022107210B-210D2110-211221152119-211D212421262128212A-212D2130-2133213E213F21452160-216F218324B6-24CF2C00-2C2E2C602C62-2C642C672C692C6B2C6D-2C702C722C752C7E-2C802C822C842C862C882C8A2C8C2C8E2C902C922C942C962C982C9A2C9C2C9E2CA02CA22CA42CA62CA82CAA2CAC2CAE2CB02CB22CB42CB62CB82CBA2CBC2CBE2CC02CC22CC42CC62CC82CCA2CCC2CCE2CD02CD22CD42CD62CD82CDA2CDC2CDE2CE02CE22CEB2CED2CF2A640A642A644A646A648A64AA64CA64EA650A652A654A656A658A65AA65CA65EA660A662A664A666A668A66AA66CA680A682A684A686A688A68AA68CA68EA690A692A694A696A722A724A726A728A72AA72CA72EA732A734A736A738A73AA73CA73EA740A742A744A746A748A74AA74CA74EA750A752A754A756A758A75AA75CA75EA760A762A764A766A768A76AA76CA76EA779A77BA77DA77EA780A782A784A786A78BA78DA790A792A7A0A7A2A7A4A7A6A7A8A7AAFF21-FF3A", + Lowercase: "0061-007A00AA00B500BA00DF-00F600F8-00FF01010103010501070109010B010D010F01110113011501170119011B011D011F01210123012501270129012B012D012F01310133013501370138013A013C013E014001420144014601480149014B014D014F01510153015501570159015B015D015F01610163016501670169016B016D016F0171017301750177017A017C017E-0180018301850188018C018D019201950199-019B019E01A101A301A501A801AA01AB01AD01B001B401B601B901BA01BD-01BF01C601C901CC01CE01D001D201D401D601D801DA01DC01DD01DF01E101E301E501E701E901EB01ED01EF01F001F301F501F901FB01FD01FF02010203020502070209020B020D020F02110213021502170219021B021D021F02210223022502270229022B022D022F02310233-0239023C023F0240024202470249024B024D024F-02930295-02B802C002C102E0-02E40345037103730377037A-037D039003AC-03CE03D003D103D5-03D703D903DB03DD03DF03E103E303E503E703E903EB03ED03EF-03F303F503F803FB03FC0430-045F04610463046504670469046B046D046F04710473047504770479047B047D047F0481048B048D048F04910493049504970499049B049D049F04A104A304A504A704A904AB04AD04AF04B104B304B504B704B904BB04BD04BF04C204C404C604C804CA04CC04CE04CF04D104D304D504D704D904DB04DD04DF04E104E304E504E704E904EB04ED04EF04F104F304F504F704F904FB04FD04FF05010503050505070509050B050D050F05110513051505170519051B051D051F05210523052505270561-05871D00-1DBF1E011E031E051E071E091E0B1E0D1E0F1E111E131E151E171E191E1B1E1D1E1F1E211E231E251E271E291E2B1E2D1E2F1E311E331E351E371E391E3B1E3D1E3F1E411E431E451E471E491E4B1E4D1E4F1E511E531E551E571E591E5B1E5D1E5F1E611E631E651E671E691E6B1E6D1E6F1E711E731E751E771E791E7B1E7D1E7F1E811E831E851E871E891E8B1E8D1E8F1E911E931E95-1E9D1E9F1EA11EA31EA51EA71EA91EAB1EAD1EAF1EB11EB31EB51EB71EB91EBB1EBD1EBF1EC11EC31EC51EC71EC91ECB1ECD1ECF1ED11ED31ED51ED71ED91EDB1EDD1EDF1EE11EE31EE51EE71EE91EEB1EED1EEF1EF11EF31EF51EF71EF91EFB1EFD1EFF-1F071F10-1F151F20-1F271F30-1F371F40-1F451F50-1F571F60-1F671F70-1F7D1F80-1F871F90-1F971FA0-1FA71FB0-1FB41FB61FB71FBE1FC2-1FC41FC61FC71FD0-1FD31FD61FD71FE0-1FE71FF2-1FF41FF61FF72071207F2090-209C210A210E210F2113212F21342139213C213D2146-2149214E2170-217F218424D0-24E92C30-2C5E2C612C652C662C682C6A2C6C2C712C732C742C76-2C7D2C812C832C852C872C892C8B2C8D2C8F2C912C932C952C972C992C9B2C9D2C9F2CA12CA32CA52CA72CA92CAB2CAD2CAF2CB12CB32CB52CB72CB92CBB2CBD2CBF2CC12CC32CC52CC72CC92CCB2CCD2CCF2CD12CD32CD52CD72CD92CDB2CDD2CDF2CE12CE32CE42CEC2CEE2CF32D00-2D252D272D2DA641A643A645A647A649A64BA64DA64FA651A653A655A657A659A65BA65DA65FA661A663A665A667A669A66BA66DA681A683A685A687A689A68BA68DA68FA691A693A695A697A723A725A727A729A72BA72DA72F-A731A733A735A737A739A73BA73DA73FA741A743A745A747A749A74BA74DA74FA751A753A755A757A759A75BA75DA75FA761A763A765A767A769A76BA76DA76F-A778A77AA77CA77FA781A783A785A787A78CA78EA791A793A7A1A7A3A7A5A7A7A7A9A7F8-A7FAFB00-FB06FB13-FB17FF41-FF5A", + White_Space: "0009-000D0020008500A01680180E2000-200A20282029202F205F3000", + Noncharacter_Code_Point: "FDD0-FDEFFFFEFFFF", + Default_Ignorable_Code_Point: "00AD034F115F116017B417B5180B-180D200B-200F202A-202E2060-206F3164FE00-FE0FFEFFFFA0FFF0-FFF8", + // \p{Any} matches a code unit. To match any code point via surrogate pairs, use (?:[\0-\uD7FF\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF]) + Any: "0000-FFFF", // \p{^Any} compiles to [^\u0000-\uFFFF]; [\p{^Any}] to [] + Ascii: "0000-007F", + // \p{Assigned} is equivalent to \p{^Cn} + //Assigned: XRegExp("[\\p{^Cn}]").source.replace(/[[\]]|\\u/g, "") // Negation inside a character class triggers inversion + Assigned: "0000-0377037A-037E0384-038A038C038E-03A103A3-05270531-05560559-055F0561-05870589058A058F0591-05C705D0-05EA05F0-05F40600-06040606-061B061E-070D070F-074A074D-07B107C0-07FA0800-082D0830-083E0840-085B085E08A008A2-08AC08E4-08FE0900-09770979-097F0981-09830985-098C098F09900993-09A809AA-09B009B209B6-09B909BC-09C409C709C809CB-09CE09D709DC09DD09DF-09E309E6-09FB0A01-0A030A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A3C0A3E-0A420A470A480A4B-0A4D0A510A59-0A5C0A5E0A66-0A750A81-0A830A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABC-0AC50AC7-0AC90ACB-0ACD0AD00AE0-0AE30AE6-0AF10B01-0B030B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3C-0B440B470B480B4B-0B4D0B560B570B5C0B5D0B5F-0B630B66-0B770B820B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BBE-0BC20BC6-0BC80BCA-0BCD0BD00BD70BE6-0BFA0C01-0C030C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D-0C440C46-0C480C4A-0C4D0C550C560C580C590C60-0C630C66-0C6F0C78-0C7F0C820C830C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBC-0CC40CC6-0CC80CCA-0CCD0CD50CD60CDE0CE0-0CE30CE6-0CEF0CF10CF20D020D030D05-0D0C0D0E-0D100D12-0D3A0D3D-0D440D46-0D480D4A-0D4E0D570D60-0D630D66-0D750D79-0D7F0D820D830D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60DCA0DCF-0DD40DD60DD8-0DDF0DF2-0DF40E01-0E3A0E3F-0E5B0E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB90EBB-0EBD0EC0-0EC40EC60EC8-0ECD0ED0-0ED90EDC-0EDF0F00-0F470F49-0F6C0F71-0F970F99-0FBC0FBE-0FCC0FCE-0FDA1000-10C510C710CD10D0-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A135D-137C1380-139913A0-13F41400-169C16A0-16F01700-170C170E-17141720-17361740-17531760-176C176E-1770177217731780-17DD17E0-17E917F0-17F91800-180E1810-18191820-18771880-18AA18B0-18F51900-191C1920-192B1930-193B19401944-196D1970-19741980-19AB19B0-19C919D0-19DA19DE-1A1B1A1E-1A5E1A60-1A7C1A7F-1A891A90-1A991AA0-1AAD1B00-1B4B1B50-1B7C1B80-1BF31BFC-1C371C3B-1C491C4D-1C7F1CC0-1CC71CD0-1CF61D00-1DE61DFC-1F151F18-1F1D1F20-1F451F48-1F4D1F50-1F571F591F5B1F5D1F5F-1F7D1F80-1FB41FB6-1FC41FC6-1FD31FD6-1FDB1FDD-1FEF1FF2-1FF41FF6-1FFE2000-2064206A-20712074-208E2090-209C20A0-20B920D0-20F02100-21892190-23F32400-24262440-244A2460-26FF2701-2B4C2B50-2B592C00-2C2E2C30-2C5E2C60-2CF32CF9-2D252D272D2D2D30-2D672D6F2D702D7F-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDE2DE0-2E3B2E80-2E992E9B-2EF32F00-2FD52FF0-2FFB3000-303F3041-30963099-30FF3105-312D3131-318E3190-31BA31C0-31E331F0-321E3220-32FE3300-4DB54DC0-9FCCA000-A48CA490-A4C6A4D0-A62BA640-A697A69F-A6F7A700-A78EA790-A793A7A0-A7AAA7F8-A82BA830-A839A840-A877A880-A8C4A8CE-A8D9A8E0-A8FBA900-A953A95F-A97CA980-A9CDA9CF-A9D9A9DEA9DFAA00-AA36AA40-AA4DAA50-AA59AA5C-AA7BAA80-AAC2AADB-AAF6AB01-AB06AB09-AB0EAB11-AB16AB20-AB26AB28-AB2EABC0-ABEDABF0-ABF9AC00-D7A3D7B0-D7C6D7CB-D7FBD800-FA6DFA70-FAD9FB00-FB06FB13-FB17FB1D-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FBC1FBD3-FD3FFD50-FD8FFD92-FDC7FDF0-FDFDFE00-FE19FE20-FE26FE30-FE52FE54-FE66FE68-FE6BFE70-FE74FE76-FEFCFEFFFF01-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDCFFE0-FFE6FFE8-FFEEFFF9-FFFD" + }); + +}(XRegExp)); + + +/***** matchrecursive.js *****/ + +/*! + * XRegExp.matchRecursive v0.2.0 + * (c) 2009-2012 Steven Levithan + * MIT License + */ + +(function (XRegExp) { + "use strict"; + +/** + * Returns a match detail object composed of the provided values. + * @private + */ + function row(value, name, start, end) { + return {value:value, name:name, start:start, end:end}; + } + +/** + * Returns an array of match strings between outermost left and right delimiters, or an array of + * objects with detailed match parts and position data. An error is thrown if delimiters are + * unbalanced within the data. + * @memberOf XRegExp + * @param {String} str String to search. + * @param {String} left Left delimiter as an XRegExp pattern. + * @param {String} right Right delimiter as an XRegExp pattern. + * @param {String} [flags] Flags for the left and right delimiters. Use any of: `gimnsxy`. + * @param {Object} [options] Lets you specify `valueNames` and `escapeChar` options. + * @returns {Array} Array of matches, or an empty array. + * @example + * + * // Basic usage + * var str = '(t((e))s)t()(ing)'; + * XRegExp.matchRecursive(str, '\\(', '\\)', 'g'); + * // -> ['t((e))s', '', 'ing'] + * + * // Extended information mode with valueNames + * str = 'Here is
    an
    example'; + * XRegExp.matchRecursive(str, '', '', 'gi', { + * valueNames: ['between', 'left', 'match', 'right'] + * }); + * // -> [ + * // {name: 'between', value: 'Here is ', start: 0, end: 8}, + * // {name: 'left', value: '
    ', start: 8, end: 13}, + * // {name: 'match', value: '
    an
    ', start: 13, end: 27}, + * // {name: 'right', value: '
    ', start: 27, end: 33}, + * // {name: 'between', value: ' example', start: 33, end: 41} + * // ] + * + * // Omitting unneeded parts with null valueNames, and using escapeChar + * str = '...{1}\\{{function(x,y){return y+x;}}'; + * XRegExp.matchRecursive(str, '{', '}', 'g', { + * valueNames: ['literal', null, 'value', null], + * escapeChar: '\\' + * }); + * // -> [ + * // {name: 'literal', value: '...', start: 0, end: 3}, + * // {name: 'value', value: '1', start: 4, end: 5}, + * // {name: 'literal', value: '\\{', start: 6, end: 8}, + * // {name: 'value', value: 'function(x,y){return y+x;}', start: 9, end: 35} + * // ] + * + * // Sticky mode via flag y + * str = '<1><<<2>>><3>4<5>'; + * XRegExp.matchRecursive(str, '<', '>', 'gy'); + * // -> ['1', '<<2>>', '3'] + */ + XRegExp.matchRecursive = function (str, left, right, flags, options) { + flags = flags || ""; + options = options || {}; + var global = flags.indexOf("g") > -1, + sticky = flags.indexOf("y") > -1, + basicFlags = flags.replace(/y/g, ""), // Flag y controlled internally + escapeChar = options.escapeChar, + vN = options.valueNames, + output = [], + openTokens = 0, + delimStart = 0, + delimEnd = 0, + lastOuterEnd = 0, + outerStart, + innerStart, + leftMatch, + rightMatch, + esc; + left = XRegExp(left, basicFlags); + right = XRegExp(right, basicFlags); + + if (escapeChar) { + if (escapeChar.length > 1) { + throw new SyntaxError("can't use more than one escape character"); + } + escapeChar = XRegExp.escape(escapeChar); + // Using XRegExp.union safely rewrites backreferences in `left` and `right` + esc = new RegExp( + "(?:" + escapeChar + "[\\S\\s]|(?:(?!" + XRegExp.union([left, right]).source + ")[^" + escapeChar + "])+)+", + flags.replace(/[^im]+/g, "") // Flags gy not needed here; flags nsx handled by XRegExp + ); + } + + while (true) { + // If using an escape character, advance to the delimiter's next starting position, + // skipping any escaped characters in between + if (escapeChar) { + delimEnd += (XRegExp.exec(str, esc, delimEnd, "sticky") || [""])[0].length; + } + leftMatch = XRegExp.exec(str, left, delimEnd); + rightMatch = XRegExp.exec(str, right, delimEnd); + // Keep the leftmost match only + if (leftMatch && rightMatch) { + if (leftMatch.index <= rightMatch.index) { + rightMatch = null; + } else { + leftMatch = null; + } + } + /* Paths (LM:leftMatch, RM:rightMatch, OT:openTokens): + LM | RM | OT | Result + 1 | 0 | 1 | loop + 1 | 0 | 0 | loop + 0 | 1 | 1 | loop + 0 | 1 | 0 | throw + 0 | 0 | 1 | throw + 0 | 0 | 0 | break + * Doesn't include the sticky mode special case + * Loop ends after the first completed match if `!global` */ + if (leftMatch || rightMatch) { + delimStart = (leftMatch || rightMatch).index; + delimEnd = delimStart + (leftMatch || rightMatch)[0].length; + } else if (!openTokens) { + break; + } + if (sticky && !openTokens && delimStart > lastOuterEnd) { + break; + } + if (leftMatch) { + if (!openTokens) { + outerStart = delimStart; + innerStart = delimEnd; + } + ++openTokens; + } else if (rightMatch && openTokens) { + if (!--openTokens) { + if (vN) { + if (vN[0] && outerStart > lastOuterEnd) { + output.push(row(vN[0], str.slice(lastOuterEnd, outerStart), lastOuterEnd, outerStart)); + } + if (vN[1]) { + output.push(row(vN[1], str.slice(outerStart, innerStart), outerStart, innerStart)); + } + if (vN[2]) { + output.push(row(vN[2], str.slice(innerStart, delimStart), innerStart, delimStart)); + } + if (vN[3]) { + output.push(row(vN[3], str.slice(delimStart, delimEnd), delimStart, delimEnd)); + } + } else { + output.push(str.slice(innerStart, delimStart)); + } + lastOuterEnd = delimEnd; + if (!global) { + break; + } + } + } else { + throw new Error("string contains unbalanced delimiters"); + } + // If the delimiter matched an empty string, avoid an infinite loop + if (delimStart === delimEnd) { + ++delimEnd; + } + } + + if (global && !sticky && vN && vN[0] && str.length > lastOuterEnd) { + output.push(row(vN[0], str.slice(lastOuterEnd), lastOuterEnd, str.length)); + } + + return output; + }; + +}(XRegExp)); + + +/***** build.js *****/ + +/*! + * XRegExp.build v0.1.0 + * (c) 2012 Steven Levithan + * MIT License + * Inspired by RegExp.create by Lea Verou + */ + +(function (XRegExp) { + "use strict"; + + var subparts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*]/g, + parts = XRegExp.union([/\({{([\w$]+)}}\)|{{([\w$]+)}}/, subparts], "g"); + +/** + * Strips a leading `^` and trailing unescaped `$`, if both are present. + * @private + * @param {String} pattern Pattern to process. + * @returns {String} Pattern with edge anchors removed. + */ + function deanchor(pattern) { + var startAnchor = /^(?:\(\?:\))?\^/, // Leading `^` or `(?:)^` (handles /x cruft) + endAnchor = /\$(?:\(\?:\))?$/; // Trailing `$` or `$(?:)` (handles /x cruft) + if (endAnchor.test(pattern.replace(/\\[\s\S]/g, ""))) { // Ensure trailing `$` isn't escaped + return pattern.replace(startAnchor, "").replace(endAnchor, ""); + } + return pattern; + } + +/** + * Converts the provided value to an XRegExp. + * @private + * @param {String|RegExp} value Value to convert. + * @returns {RegExp} XRegExp object with XRegExp syntax applied. + */ + function asXRegExp(value) { + return XRegExp.isRegExp(value) ? + (value.xregexp && !value.xregexp.isNative ? value : XRegExp(value.source)) : + XRegExp(value); + } + +/** + * Builds regexes using named subpatterns, for readability and pattern reuse. Backreferences in the + * outer pattern and provided subpatterns are automatically renumbered to work correctly. Native + * flags used by provided subpatterns are ignored in favor of the `flags` argument. + * @memberOf XRegExp + * @param {String} pattern XRegExp pattern using `{{name}}` for embedded subpatterns. Allows + * `({{name}})` as shorthand for `(?{{name}})`. Patterns cannot be embedded within + * character classes. + * @param {Object} subs Lookup object for named subpatterns. Values can be strings or regexes. A + * leading `^` and trailing unescaped `$` are stripped from subpatterns, if both are present. + * @param {String} [flags] Any combination of XRegExp flags. + * @returns {RegExp} Regex with interpolated subpatterns. + * @example + * + * var time = XRegExp.build('(?x)^ {{hours}} ({{minutes}}) $', { + * hours: XRegExp.build('{{h12}} : | {{h24}}', { + * h12: /1[0-2]|0?[1-9]/, + * h24: /2[0-3]|[01][0-9]/ + * }, 'x'), + * minutes: /^[0-5][0-9]$/ + * }); + * time.test('10:59'); // -> true + * XRegExp.exec('10:59', time).minutes; // -> '59' + */ + XRegExp.build = function (pattern, subs, flags) { + var inlineFlags = /^\(\?([\w$]+)\)/.exec(pattern), + data = {}, + numCaps = 0, // Caps is short for captures + numPriorCaps, + numOuterCaps = 0, + outerCapsMap = [0], + outerCapNames, + sub, + p; + + // Add flags within a leading mode modifier to the overall pattern's flags + if (inlineFlags) { + flags = flags || ""; + inlineFlags[1].replace(/./g, function (flag) { + flags += (flags.indexOf(flag) > -1 ? "" : flag); // Don't add duplicates + }); + } + + for (p in subs) { + if (subs.hasOwnProperty(p)) { + // Passing to XRegExp enables entended syntax for subpatterns provided as strings + // and ensures independent validity, lest an unescaped `(`, `)`, `[`, or trailing + // `\` breaks the `(?:)` wrapper. For subpatterns provided as regexes, it dies on + // octals and adds the `xregexp` property, for simplicity + sub = asXRegExp(subs[p]); + // Deanchoring allows embedding independently useful anchored regexes. If you + // really need to keep your anchors, double them (i.e., `^^...$$`) + data[p] = {pattern: deanchor(sub.source), names: sub.xregexp.captureNames || []}; + } + } + + // Passing to XRegExp dies on octals and ensures the outer pattern is independently valid; + // helps keep this simple. Named captures will be put back + pattern = asXRegExp(pattern); + outerCapNames = pattern.xregexp.captureNames || []; + pattern = pattern.source.replace(parts, function ($0, $1, $2, $3, $4) { + var subName = $1 || $2, capName, intro; + if (subName) { // Named subpattern + if (!data.hasOwnProperty(subName)) { + throw new ReferenceError("undefined property " + $0); + } + if ($1) { // Named subpattern was wrapped in a capturing group + capName = outerCapNames[numOuterCaps]; + outerCapsMap[++numOuterCaps] = ++numCaps; + // If it's a named group, preserve the name. Otherwise, use the subpattern name + // as the capture name + intro = "(?<" + (capName || subName) + ">"; + } else { + intro = "(?:"; + } + numPriorCaps = numCaps; + return intro + data[subName].pattern.replace(subparts, function (match, paren, backref) { + if (paren) { // Capturing group + capName = data[subName].names[numCaps - numPriorCaps]; + ++numCaps; + if (capName) { // If the current capture has a name, preserve the name + return "(?<" + capName + ">"; + } + } else if (backref) { // Backreference + return "\\" + (+backref + numPriorCaps); // Rewrite the backreference + } + return match; + }) + ")"; + } + if ($3) { // Capturing group + capName = outerCapNames[numOuterCaps]; + outerCapsMap[++numOuterCaps] = ++numCaps; + if (capName) { // If the current capture has a name, preserve the name + return "(?<" + capName + ">"; + } + } else if ($4) { // Backreference + return "\\" + outerCapsMap[+$4]; // Rewrite the backreference + } + return $0; + }); + + return XRegExp(pattern, flags); + }; + +}(XRegExp)); + + +/***** prototypes.js *****/ + +/*! + * XRegExp Prototype Methods v1.0.0 + * (c) 2012 Steven Levithan + * MIT License + */ + +/** + * Adds a collection of methods to `XRegExp.prototype`. RegExp objects copied by XRegExp are also + * augmented with any `XRegExp.prototype` methods. Hence, the following work equivalently: + * + * XRegExp('[a-z]', 'ig').xexec('abc'); + * XRegExp(/[a-z]/ig).xexec('abc'); + * XRegExp.globalize(/[a-z]/i).xexec('abc'); + */ +(function (XRegExp) { + "use strict"; + +/** + * Copy properties of `b` to `a`. + * @private + * @param {Object} a Object that will receive new properties. + * @param {Object} b Object whose properties will be copied. + */ + function extend(a, b) { + for (var p in b) { + if (b.hasOwnProperty(p)) { + a[p] = b[p]; + } + } + //return a; + } + + extend(XRegExp.prototype, { + +/** + * Implicitly calls the regex's `test` method with the first value in the provided arguments array. + * @memberOf XRegExp.prototype + * @param {*} context Ignored. Accepted only for congruity with `Function.prototype.apply`. + * @param {Array} args Array with the string to search as its first value. + * @returns {Boolean} Whether the regex matched the provided value. + * @example + * + * XRegExp('[a-z]').apply(null, ['abc']); // -> true + */ + apply: function (context, args) { + return this.test(args[0]); + }, + +/** + * Implicitly calls the regex's `test` method with the provided string. + * @memberOf XRegExp.prototype + * @param {*} context Ignored. Accepted only for congruity with `Function.prototype.call`. + * @param {String} str String to search. + * @returns {Boolean} Whether the regex matched the provided value. + * @example + * + * XRegExp('[a-z]').call(null, 'abc'); // -> true + */ + call: function (context, str) { + return this.test(str); + }, + +/** + * Implicitly calls {@link #XRegExp.forEach}. + * @memberOf XRegExp.prototype + * @example + * + * XRegExp('\\d').forEach('1a2345', function (match, i) { + * if (i % 2) this.push(+match[0]); + * }, []); + * // -> [2, 4] + */ + forEach: function (str, callback, context) { + return XRegExp.forEach(str, this, callback, context); + }, + +/** + * Implicitly calls {@link #XRegExp.globalize}. + * @memberOf XRegExp.prototype + * @example + * + * var globalCopy = XRegExp('regex').globalize(); + * globalCopy.global; // -> true + */ + globalize: function () { + return XRegExp.globalize(this); + }, + +/** + * Implicitly calls {@link #XRegExp.exec}. + * @memberOf XRegExp.prototype + * @example + * + * var match = XRegExp('U\\+(?[0-9A-F]{4})').xexec('U+2620'); + * match.hex; // -> '2620' + */ + xexec: function (str, pos, sticky) { + return XRegExp.exec(str, this, pos, sticky); + }, + +/** + * Implicitly calls {@link #XRegExp.test}. + * @memberOf XRegExp.prototype + * @example + * + * XRegExp('c').xtest('abc'); // -> true + */ + xtest: function (str, pos, sticky) { + return XRegExp.test(str, this, pos, sticky); + } + + }); + +}(XRegExp)); + From f78bf50adcec216fc8071835bd9e4e249e15483d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 6 Jun 2016 10:49:44 -0400 Subject: [PATCH 645/756] [1.9.x] Documented known Python 3.5+ test failures in contributing tutorial. Backport of 6a316423df349e2cbc0a4188c55d0639aa2e15c5 from master --- docs/intro/contributing.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index ac06c1cae3c9..4a4a71a3640b 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -279,7 +279,9 @@ Once the tests complete, you should be greeted with a message informing you whether the test suite passed or failed. Since you haven't yet made any changes to Django's code, the entire test suite **should** pass. If you get failures or errors make sure you've followed all of the previous steps properly. See -:ref:`running-unit-tests` for more information. +:ref:`running-unit-tests` for more information. If you're using Python 3.5+, +there will be a couple failures related to deprecation warnings that you can +ignore. These failures have since been fixed in Django. Note that the latest Django trunk may not always be stable. When developing against trunk, you can check `Django's continuous integration builds`__ to From 73d64a020cedebb6d78f8b3e2355a10b7a663635 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 6 Jun 2016 11:37:20 +0100 Subject: [PATCH 646/756] [1.9.x] Fixed #26713 -- Documented resolving deprecation warnings before upgrade. Backport of 316395d82874beea6af08a873e91b1880d0b982a from master --- docs/howto/upgrade-version.txt | 39 ++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/docs/howto/upgrade-version.txt b/docs/howto/upgrade-version.txt index 28c745ef4bfa..a107f404cd3a 100644 --- a/docs/howto/upgrade-version.txt +++ b/docs/howto/upgrade-version.txt @@ -42,6 +42,38 @@ released or if some of your dependencies are not well-maintained, some of your dependencies may not yet support the new Django version. In these cases you may have to wait until new versions of your dependencies are released. +Resolving deprecation warnings +============================== + +Before upgrading, it's a good idea to resolve any deprecation warnings raised +by your project while using your current version of Django. Fixing these +warnings before upgrading ensures that you're informed about areas of the code +that need altering. + +In Python, deprecation warnings are silenced by default. You must turn them on +using the ``-Wall`` Python command line option or the :envvar:`PYTHONWARNINGS` +environment variable. For example, to show warnings while running tests: + +.. code-block:: console + + $ python -Wall manage.py test + +If you're not using the Django test runner, you may need to also ensure that +any console output is not captured which would hide deprecation warnings. For +example, if you use `py.test`: + +.. code-block:: console + + $ PYTHONWARNINGS=all py.test tests --capture=no + +Resolve any deprecation warnings with your current version of Django before +continuing the upgrade process. + +Third party applications might use deprecated APIs in order to support multiple +versions of Django, so deprecation warnings in packages you've installed don't +necessarily indicate a problem. If a package doesn't support the latest version +of Django, consider raising an issue or sending a pull request for it. + Installation ============ @@ -69,10 +101,9 @@ Testing ======= When the new environment is set up, :doc:`run the full test suite -` for your application. In Python 2.7+, deprecation -warnings are silenced by default. It is useful to turn the warnings on so they -are shown in the test output (you can also use the flag if you test your app -manually using ``manage.py runserver``): +` for your application. Again, it's useful to turn +on deprecation warnings on so they're shown in the test output (you can also +use the flag if you test your app manually using ``manage.py runserver``): .. code-block:: console From df4382341a0a3201db25d2d82ab86b1cd72ffab1 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 5 Jun 2016 07:54:24 -0700 Subject: [PATCH 647/756] [1.9.x] Added some links to the TypedChoiceField docs. Backport of 31a9a965a36c5ce923692e5dac430ea6a1cbf2a0 from master --- docs/ref/forms/fields.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 9e848a0a1f8e..5cdc3d86f098 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -425,11 +425,12 @@ For each field, we describe the default widget used if you don't specify .. class:: TypedChoiceField(**kwargs) Just like a :class:`ChoiceField`, except :class:`TypedChoiceField` takes two - extra arguments, ``coerce`` and ``empty_value``. + extra arguments, :attr:`coerce` and :attr:`empty_value`. * Default widget: :class:`Select` - * Empty value: Whatever you've given as ``empty_value`` - * Normalizes to: A value of the type provided by the ``coerce`` argument. + * Empty value: Whatever you've given as :attr:`empty_value`. + * Normalizes to: A value of the type provided by the :attr:`coerce` + argument. * Validates that the given value exists in the list of choices and can be coerced. * Error message keys: ``required``, ``invalid_choice`` From d22bac99b072abd6b04e4393d355ee6fb614300c Mon Sep 17 00:00:00 2001 From: Tommy Beadle Date: Fri, 3 Jun 2016 14:33:19 -0400 Subject: [PATCH 648/756] [1.9.x] Fixed #26704 -- Documented DjangoJSONEncoder. Backport of 729b9452b19b031d3817821128a115d5b2d5caed from master --- docs/ref/request-response.txt | 2 +- docs/topics/serialization.txt | 28 +++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 29caf0617eb4..564a36cf135d 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -936,7 +936,7 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in JSON-serializable object. The ``encoder``, which defaults to - ``django.core.serializers.json.DjangoJSONEncoder``, will be used to + :class:`django.core.serializers.json.DjangoJSONEncoder`, will be used to serialize the data. See :ref:`JSON serialization ` for more details about this serializer. diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index 75a1eaa9cdd1..d8b47b0482a6 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -253,9 +253,6 @@ Foreign keys just have the PK of the linked object as property value. ManyToMany-relations are serialized for the model that defines them and are represented as a list of PKs. -Date and datetime related types are treated in a special way by the JSON -serializer to make the format compatible with `ECMA-262`_. - Be aware that not all Django output can be passed unmodified to :mod:`json`. In particular, :ref:`lazy translation objects ` need a custom :mod:`json` encoder. Something like this will work:: @@ -273,6 +270,31 @@ custom :mod:`json` encoder. Something like this will work:: Also note that GeoDjango provides a :doc:`customized GeoJSON serializer `. +``DjangoJSONEncoder`` +~~~~~~~~~~~~~~~~~~~~~ + +.. class:: django.core.serializers.json.DjangoJSONEncoder + +The JSON serializer uses ``DjangoJSONEncoder`` for encoding. A subclass of +:class:`~json.JSONEncoder`, it handles these additional types: + +:class:`~datetime.datetime` + A string of the form ``YYYY-MM-DDTHH:mm:ss.sssZ`` or + ``YYYY-MM-DDTHH:mm:ss.sss+HH:MM`` as defined in `ECMA-262`_. + +:class:`~datetime.date` + A string of the form ``YYYY-MM-DD`` as defined in `ECMA-262`_. + +:class:`~datetime.time` + A string of the form ``HH:MM:ss.sss`` as defined in `ECMA-262`_. + +:class:`~decimal.Decimal`, :class:`~uuid.UUID` + A string representation of the object. + +.. versionchanged:: 1.8.4 + + Support for :class:`~uuid.UUID` was added. + .. _ecma-262: http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 YAML From 4765769b76c07f074a915655503d29028d50c122 Mon Sep 17 00:00:00 2001 From: Baylee Feore Date: Thu, 2 Jun 2016 17:50:11 -0700 Subject: [PATCH 649/756] [1.9.x] Fixed #26702 -- Documented how to change the base class of a custom field. Backport of 7767978beec6098baea75d50a191a3b8224e729f from master --- docs/howto/custom-model-fields.txt | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 3485c8a22eef..1c2484d78676 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -311,6 +311,34 @@ and reconstructing the field:: new_instance = MyField(*args, **kwargs) self.assertEqual(my_field_instance.some_attribute, new_instance.some_attribute) +Changing a custom field's base class +------------------------------------ + +You can't change the base class of a custom field because Django won't detect +the change and make a migration for it. For example, if you start with:: + + class CustomCharField(models.CharField): + ... + +and then decide that you want to use ``TextField`` instead, you can't change +the subclass like this:: + + class CustomCharField(models.TextField): + ... + +Instead, you must create a new custom field class and update your models to +reference it:: + + class CustomCharField(models.CharField): + ... + + class CustomTextField(models.TextField): + ... + +As discussed in :ref:`removing fields `, you +must retain the original ``CustomCharField`` class as long as you have +migrations that reference it. + Documenting your custom field ----------------------------- From b0e0a62cbf1978ccfe59b317f6f9303233aa96c4 Mon Sep 17 00:00:00 2001 From: John Karahalis Date: Thu, 9 Jun 2016 11:15:39 -0400 Subject: [PATCH 650/756] [1.9.x] Fixed typo in docs/intro/tutorial07.txt Backport of 37b36af6c6e169eae2f0b503f4550f798c428a97 from master --- docs/intro/tutorial07.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/tutorial07.txt b/docs/intro/tutorial07.txt index 59237ebc7efe..7f48b85a59b7 100644 --- a/docs/intro/tutorial07.txt +++ b/docs/intro/tutorial07.txt @@ -3,7 +3,7 @@ Writing your first Django app, part 7 ===================================== This tutorial begins where :doc:`Tutorial 6 ` left off. We're -continuing the Web-poll application and will focus on customizing the Django's +continuing the Web-poll application and will focus on customizing Django's automatically-generated admin site that we first explored in :doc:`Tutorial 2 `. From 324eaf42f249d07c5cd236798fda50e08aa5c501 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sat, 4 Jun 2016 16:58:23 -0700 Subject: [PATCH 651/756] [1.9.x] Fixed #21588 -- Corrected handler initialization in "modifying upload handlers" example. Backport of 8f50ff5b15a742f345dade0848a3fbbf2aff629d from master --- docs/topics/http/file-uploads.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt index 8a7191ac1d08..75ac1de45292 100644 --- a/docs/topics/http/file-uploads.txt +++ b/docs/topics/http/file-uploads.txt @@ -229,7 +229,7 @@ For instance, suppose you've written a ``ProgressBarUploadHandler`` that provides feedback on upload progress to some sort of AJAX widget. You'd add this handler to your upload handlers like this:: - request.upload_handlers.insert(0, ProgressBarUploadHandler()) + request.upload_handlers.insert(0, ProgressBarUploadHandler(request)) You'd probably want to use ``list.insert()`` in this case (instead of ``append()``) because a progress bar handler would need to run *before* any @@ -238,7 +238,7 @@ other handlers. Remember, the upload handlers are processed in order. If you want to replace the upload handlers completely, you can just assign a new list:: - request.upload_handlers = [ProgressBarUploadHandler()] + request.upload_handlers = [ProgressBarUploadHandler(request)] .. note:: @@ -266,7 +266,7 @@ list:: @csrf_exempt def upload_file_view(request): - request.upload_handlers.insert(0, ProgressBarUploadHandler()) + request.upload_handlers.insert(0, ProgressBarUploadHandler(request)) return _upload_file_view(request) @csrf_protect From b789d81c957b437f43a3329bfef45312232bf80c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 14 Jun 2016 12:57:17 +0300 Subject: [PATCH 652/756] [1.9.x] Fixed #26754 -- Documented django.template.context_processors.tz Backport of 7003174fec188bd5a21e3d62bd5e13e80f6f329c from master --- docs/ref/templates/api.txt | 8 ++++++++ docs/topics/i18n/timezones.txt | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index f6283f3bd262..0efb10b9dda7 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -757,6 +757,14 @@ tag for protection against :doc:`Cross Site Request Forgeries If this processor is enabled, every ``RequestContext`` will contain a variable ``request``, which is the current :class:`~django.http.HttpRequest`. +``django.template.context_processors.tz`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. function:: tz + +If this processor is enabled, every ``RequestContext`` will contain a variable +``TIME_ZONE``, providing the name of the currently active time zone. + ``django.contrib.messages.context_processors.messages`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/topics/i18n/timezones.txt b/docs/topics/i18n/timezones.txt index b8cb3041edb0..3461e143cdc0 100644 --- a/docs/topics/i18n/timezones.txt +++ b/docs/topics/i18n/timezones.txt @@ -314,9 +314,9 @@ You can get the name of the current time zone using the {% get_current_timezone as TIME_ZONE %} -If you enable the ``django.template.context_processors.tz`` context processor, -each :class:`~django.template.RequestContext` will contain a ``TIME_ZONE`` -variable with the value of ``get_current_timezone()``. +Alternatively, you can activate the +:func:`~django.template.context_processors.tz` context processor and +use the ``TIME_ZONE`` context variable. Template filters ---------------- From ab7a0b5d17fc8d0410b86d42835be9b767636e06 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 15 Jun 2016 08:31:11 -0400 Subject: [PATCH 653/756] [1.9.x] Removed nonexistent attribute in docs/ref/contrib/admin/index.txt Backport of abec15ad810d04b59ca745cacf91802c7147c8e6 from master --- docs/ref/contrib/admin/index.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 637dc010cbfa..58024a543069 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -248,7 +248,6 @@ subclass:: def view_birth_date(self, obj): return obj.birth_date - view_birth_date.short_name = 'birth_date' view_birth_date.empty_value_display = '???' .. attribute:: ModelAdmin.exclude From 99e51bf168a628f67749fcfbd31951c499c403fc Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Wed, 15 Jun 2016 18:09:59 +0500 Subject: [PATCH 654/756] [1.9.x] Refs #14030 -- Updated docs per "Improved expression support for python values." Complements e2d6e14662d780383e18066a3182155fb5b7747b. Backport of db613f4f1250971942f766dcf97c22234a3aa14e from master --- docs/ref/models/conditional-expressions.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/ref/models/conditional-expressions.txt b/docs/ref/models/conditional-expressions.txt index a4daea94b31b..52a9d8cdc6e8 100644 --- a/docs/ref/models/conditional-expressions.txt +++ b/docs/ref/models/conditional-expressions.txt @@ -72,7 +72,6 @@ Keep in mind that each of these values can be an expression. :class:`~django.db.models.Model` has a field named ``then``. This can be resolved in two ways:: - >>> from django.db.models import Value >>> When(then__exact=0, then=1) >>> When(Q(then=0), then=1) @@ -118,8 +117,8 @@ A simple example:: ``Case()`` accepts any number of ``When()`` objects as individual arguments. Other options are provided using keyword arguments. If none of the conditions evaluate to ``TRUE``, then the expression given with the ``default`` keyword -argument is returned. If no ``default`` argument is provided, ``Value(None)`` -is used. +argument is returned. If a ``default`` argument isn't provided, ``None`` is +used. If we wanted to change our previous query to get the discount based on how long the ``Client`` has been with us, we could do so using lookups:: From 1cfcc02fc3a2352868fb102118aff22b6e05c610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 16 Jun 2016 04:20:23 +0300 Subject: [PATCH 655/756] [1.9.x] Fixed broken links in docs and comments. Backport of 96f97691ad5e1483263cea3bb4e4021b4c8dcc41 from master --- django/conf/global_settings.py | 4 ++-- django/contrib/gis/gdal/datasource.py | 2 +- django/contrib/gis/gdal/envelope.py | 2 +- django/contrib/gis/gdal/feature.py | 2 +- django/contrib/gis/gdal/field.py | 2 +- django/contrib/gis/gdal/geometries.py | 4 ++-- django/contrib/gis/gdal/layer.py | 2 +- django/contrib/gis/maps/google/__init__.py | 2 +- django/contrib/gis/measure.py | 2 +- django/core/cache/backends/memcached.py | 2 +- django/core/files/temp.py | 2 +- django/db/backends/oracle/operations.py | 2 +- django/http/response.py | 2 +- django/test/utils.py | 2 +- docs/howto/deployment/wsgi/apache-auth.txt | 2 +- docs/intro/reusable-apps.txt | 2 +- docs/ref/contrib/gis/install/index.txt | 2 +- docs/ref/django-admin.txt | 2 +- docs/topics/auth/passwords.txt | 2 +- docs/topics/i18n/translation.txt | 4 ++-- docs/topics/install.txt | 2 +- docs/topics/testing/tools.txt | 2 +- 22 files changed, 25 insertions(+), 25 deletions(-) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index d85e69d26d78..d01ab1cf557f 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -318,12 +318,12 @@ FILE_UPLOAD_TEMP_DIR = None # The numeric mode to set newly-uploaded files to. The value should be a mode -# you'd pass directly to os.chmod; see http://docs.python.org/lib/os-file-dir.html. +# you'd pass directly to os.chmod; see https://docs.python.org/3/library/os.html#files-and-directories. FILE_UPLOAD_PERMISSIONS = None # The numeric mode to assign to newly-created directories, when uploading files. # The value should be a mode as you'd pass to os.chmod; -# see http://docs.python.org/lib/os-file-dir.html. +# see https://docs.python.org/3/library/os.html#files-and-directories. FILE_UPLOAD_DIRECTORY_PERMISSIONS = None # Python module path where user will place custom format definition. diff --git a/django/contrib/gis/gdal/datasource.py b/django/contrib/gis/gdal/datasource.py index d73950f8604b..b63919e157cc 100644 --- a/django/contrib/gis/gdal/datasource.py +++ b/django/contrib/gis/gdal/datasource.py @@ -46,7 +46,7 @@ # For more information, see the OGR C API source code: -# http://www.gdal.org/ogr/ogr__api_8h.html +# http://www.gdal.org/ogr__api_8h.html # # The OGR_DS_* routines are relevant here. class DataSource(GDALBase): diff --git a/django/contrib/gis/gdal/envelope.py b/django/contrib/gis/gdal/envelope.py index ae9ba434e7ce..64cac5baa072 100644 --- a/django/contrib/gis/gdal/envelope.py +++ b/django/contrib/gis/gdal/envelope.py @@ -17,7 +17,7 @@ # The OGR definition of an Envelope is a C structure containing four doubles. # See the 'ogr_core.h' source file for more information: -# http://www.gdal.org/ogr/ogr__core_8h-source.html +# http://www.gdal.org/ogr__core_8h_source.html class OGREnvelope(Structure): "Represents the OGREnvelope C Structure." _fields_ = [("MinX", c_double), diff --git a/django/contrib/gis/gdal/feature.py b/django/contrib/gis/gdal/feature.py index 7e9d5a6986f7..1b370078e5b8 100644 --- a/django/contrib/gis/gdal/feature.py +++ b/django/contrib/gis/gdal/feature.py @@ -9,7 +9,7 @@ # For more information, see the OGR C API source code: -# http://www.gdal.org/ogr/ogr__api_8h.html +# http://www.gdal.org/ogr__api_8h.html # # The OGR_F_* routines are relevant here. class Feature(GDALBase): diff --git a/django/contrib/gis/gdal/field.py b/django/contrib/gis/gdal/field.py index 38a72b6e1938..c2f16e90b245 100644 --- a/django/contrib/gis/gdal/field.py +++ b/django/contrib/gis/gdal/field.py @@ -8,7 +8,7 @@ # For more information, see the OGR C API source code: -# http://www.gdal.org/ogr/ogr__api_8h.html +# http://www.gdal.org/ogr__api_8h.html # # The OGR_Fld_* routines are relevant here. class Field(GDALBase): diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index 3889cc8c7324..6254c1c00bda 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -1,6 +1,6 @@ """ The OGRGeometry is a wrapper for using the OGR Geometry class - (see http://www.gdal.org/ogr/classOGRGeometry.html). OGRGeometry + (see http://www.gdal.org/classOGRGeometry.html). OGRGeometry may be instantiated when reading geometries from OGR Data Sources (e.g. SHP files), or when given OGC WKT (a string). @@ -56,7 +56,7 @@ # For more information, see the OGR C API source code: -# http://www.gdal.org/ogr/ogr__api_8h.html +# http://www.gdal.org/ogr__api_8h.html # # The OGR_G_* routines are relevant here. class OGRGeometry(GDALBase): diff --git a/django/contrib/gis/gdal/layer.py b/django/contrib/gis/gdal/layer.py index 82b0d8dd5d89..e7e13f9468d3 100644 --- a/django/contrib/gis/gdal/layer.py +++ b/django/contrib/gis/gdal/layer.py @@ -19,7 +19,7 @@ # For more information, see the OGR C API source code: -# http://www.gdal.org/ogr/ogr__api_8h.html +# http://www.gdal.org/ogr__api_8h.html # # The OGR_L_* routines are relevant here. class Layer(GDALBase): diff --git a/django/contrib/gis/maps/google/__init__.py b/django/contrib/gis/maps/google/__init__.py index 682e88d5cec6..b80e6d30cd63 100644 --- a/django/contrib/gis/maps/google/__init__.py +++ b/django/contrib/gis/maps/google/__init__.py @@ -52,7 +52,7 @@ The following attributes may be set or customized in your local settings: * GOOGLE_MAPS_API_KEY: String of your Google Maps API key. These are tied - to a domain. May be obtained from http://www.google.com/apis/maps/ + to a domain. May be obtained from https://developers.google.com/maps/ * GOOGLE_MAPS_API_VERSION (optional): Defaults to using "2.x" * GOOGLE_MAPS_URL (optional): Must have a substitution ('%s') for the API version. diff --git a/django/contrib/gis/measure.py b/django/contrib/gis/measure.py index 52fdf874ca84..9b3ba47499be 100644 --- a/django/contrib/gis/measure.py +++ b/django/contrib/gis/measure.py @@ -32,7 +32,7 @@ Authors: Robert Coup, Justin Bronn, Riccardo Di Virgilio -Inspired by GeoPy (http://exogen.case.edu/projects/geopy/) +Inspired by GeoPy (https://github.com/geopy/geopy) and Geoff Biggs' PhD work on dimensioned units for robotics. """ __all__ = ['A', 'Area', 'D', 'Distance'] diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py index 40f13183d474..a2b647027ba5 100644 --- a/django/core/cache/backends/memcached.py +++ b/django/core/cache/backends/memcached.py @@ -53,7 +53,7 @@ def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT): timeout = -1 if timeout > 2592000: # 60*60*24*30, 30 days - # See http://code.google.com/p/memcached/wiki/NewProgramming#Expiration + # See https://github.com/memcached/memcached/wiki/Programming#expiration # "Expiration times can be set from 0, meaning "never expire", to # 30 days. Any time higher than 30 days is interpreted as a Unix # timestamp date. If you want to expire an object on January 1st of diff --git a/django/core/files/temp.py b/django/core/files/temp.py index 0791b8536e48..0f8d2e513a98 100644 --- a/django/core/files/temp.py +++ b/django/core/files/temp.py @@ -12,7 +12,7 @@ Also note that the custom version of NamedTemporaryFile does not support the full range of keyword arguments available in Python 2.6+ and 3.0+. -1: https://mail.python.org/pipermail/python-list/2005-December/336958.html +1: https://mail.python.org/pipermail/python-list/2005-December/336957.html 2: http://bugs.python.org/issue14243 """ diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index 0651105d9ff4..7a9b892e7761 100644 --- a/django/db/backends/oracle/operations.py +++ b/django/db/backends/oracle/operations.py @@ -242,7 +242,7 @@ def field_cast_sql(self, db_type, internal_type): return "%s" def last_executed_query(self, cursor, sql, params): - # http://cx-oracle.sourceforge.net/html/cursor.html#Cursor.statement + # https://cx-oracle.readthedocs.io/en/latest/cursor.html#Cursor.statement # The DB API definition does not define this attribute. statement = cursor.statement if statement and six.PY2 and not isinstance(statement, unicode): # NOQA: unicode undefined on PY3 diff --git a/django/http/response.py b/django/http/response.py index 3365cdbdca8c..ddd9d521474f 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -238,7 +238,7 @@ def make_bytes(self, value): return force_bytes(value, self.charset) # These methods partially implement the file-like object interface. - # See http://docs.python.org/lib/bltin-file-objects.html + # See https://docs.python.org/3/library/io.html#io.IOBase # The WSGI server must call this method upon completion of the request. # See http://blog.dscpl.com.au/2012/10/obligations-for-calling-close-on.html diff --git a/django/test/utils.py b/django/test/utils.py index 95757dbf1b6b..48633a03297c 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -295,7 +295,7 @@ def compare_xml(want, got): ordering should not be important. Comment nodes are not considered in the comparison. - Based on http://codespeak.net/svn/lxml/trunk/src/lxml/doctestcompare.py + Based on https://github.com/lxml/lxml/blob/master/src/lxml/doctestcompare.py """ _norm_whitespace_re = re.compile(r'[ \t\n][ \t\n]+') diff --git a/docs/howto/deployment/wsgi/apache-auth.txt b/docs/howto/deployment/wsgi/apache-auth.txt index ff84a3f43330..3d4b35fd04a3 100644 --- a/docs/howto/deployment/wsgi/apache-auth.txt +++ b/docs/howto/deployment/wsgi/apache-auth.txt @@ -22,7 +22,7 @@ version >= 2.2 and mod_wsgi >= 2.0. For example, you could: a 'name' field. You can also specify your own custom mod_wsgi auth handler if your custom cannot conform to these requirements. -.. _Subversion: http://subversion.tigris.org/ +.. _Subversion: http://subversion.apache.org/ .. _mod_dav: https://httpd.apache.org/docs/2.2/mod/mod_dav.html Authentication with ``mod_wsgi`` diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index c0e5e086f68c..32752bbfbda3 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -185,7 +185,7 @@ this. For a small app like polls, this process isn't too difficult. 5. Next we'll create a ``setup.py`` file which provides details about how to build and install the app. A full explanation of this file is beyond the scope of this tutorial, but the `setuptools docs - `_ have a good + `_ have a good explanation. Create a file ``django-polls/setup.py`` with the following contents: diff --git a/docs/ref/contrib/gis/install/index.txt b/docs/ref/contrib/gis/install/index.txt index b7ba8e594da7..51fba618854f 100644 --- a/docs/ref/contrib/gis/install/index.txt +++ b/docs/ref/contrib/gis/install/index.txt @@ -327,7 +327,7 @@ Fink of the `Fink`__ package system. `Different packages are available`__ (starting with "django-gis"), depending on which version of Python you want to use. -__ http://schwehr.org/blog/ +__ https://schwehr.blogspot.com/ __ http://www.finkproject.org/ __ http://pdb.finkproject.org/pdb/browse.php?summary=django-gis diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 1afaf1503808..d0e88fd457b3 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -967,7 +967,7 @@ interpreter, use the ``--plain`` option, like so:: django-admin shell --plain -.. _IPython: http://ipython.scipy.org/ +.. _IPython: https://ipython.org/ .. _bpython: http://bpython-interpreter.org/ .. django-admin-option:: --nostartup diff --git a/docs/topics/auth/passwords.txt b/docs/topics/auth/passwords.txt index 2da25ecf6ccc..5b88ce33dd16 100644 --- a/docs/topics/auth/passwords.txt +++ b/docs/topics/auth/passwords.txt @@ -292,7 +292,7 @@ Include any other hashers that your site uses in this list. .. _sha1: https://en.wikipedia.org/wiki/SHA1 .. _pbkdf2: https://en.wikipedia.org/wiki/PBKDF2 -.. _nist: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf +.. _nist: https://dx.doi.org/10.6028/NIST.SP.800-132 .. _bcrypt: https://en.wikipedia.org/wiki/Bcrypt .. _`bcrypt library`: https://pypi.python.org/pypi/bcrypt/ diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 5eea38e1daf2..8719f7de7f33 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1569,9 +1569,9 @@ multiple times:: in general, and doesn't depend on ``gettext``. For more information, read its documentation about `working with message catalogs`_. - .. _Message extracting: http://babel.pocoo.org/docs/messages/#message-extraction + .. _Message extracting: http://babel.pocoo.org/en/latest/messages.html#message-extraction .. _Babel: http://babel.pocoo.org/ - .. _working with message catalogs: http://babel.pocoo.org/docs/messages/ + .. _working with message catalogs: http://babel.pocoo.org/en/latest/messages.html .. admonition:: No gettext? diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 2b5a5d045f9c..e42ff7c6da20 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -179,7 +179,7 @@ This is the recommended way to install Django. .. _pip: https://pip.pypa.io/ .. _virtualenv: http://www.virtualenv.org/ .. _virtualenvwrapper: https://virtualenvwrapper.readthedocs.io/en/latest/ -.. _standalone pip installer: https://pip.pypa.io/en/latest/installing.html#install-pip +.. _standalone pip installer: https://pip.pypa.io/en/latest/installing/#installing-with-get-pip-py Installing a distribution-specific package ------------------------------------------ diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 8890afcf08f6..510ebacf5dc9 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -971,7 +971,7 @@ out the `full reference`_ for more details. use cases. Please refer to the `Selenium FAQ`_ and `Selenium documentation`_ for more information. - .. _Selenium FAQ: https://code.google.com/p/selenium/wiki/FrequentlyAskedQuestions#Q:_WebDriver_fails_to_find_elements_/_Does_not_block_on_page_loa + .. _Selenium FAQ: https://web.archive.org/web/20160129132110/http://code.google.com/p/selenium/wiki/FrequentlyAskedQuestions#Q:_WebDriver_fails_to_find_elements_/_Does_not_block_on_page_loa .. _Selenium documentation: http://seleniumhq.org/docs/04_webdriver_advanced.html#explicit-waits Test cases features From 13d16697a0de87b37a046d5418426a02b810a5b0 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 16 Jun 2016 09:07:51 -0400 Subject: [PATCH 656/756] [1.9.x] Fixed flake8 2.6 warnings. Backport of ea34426ae789d31b036f58c8fd59ce299649e91e from master --- django/contrib/gis/db/backends/base/models.py | 8 ++++---- django/contrib/gis/db/models/sql/aggregates.py | 10 ---------- .../migrations/0002_create_test_models.py | 5 ++++- tests/servers/tests.py | 14 ++++---------- 4 files changed, 12 insertions(+), 25 deletions(-) delete mode 100644 django/contrib/gis/db/models/sql/aggregates.py diff --git a/django/contrib/gis/db/backends/base/models.py b/django/contrib/gis/db/backends/base/models.py index a84b23dfe8f8..7367a9f6a0fa 100644 --- a/django/contrib/gis/db/backends/base/models.py +++ b/django/contrib/gis/db/backends/base/models.py @@ -45,14 +45,14 @@ def srs(self): try: self._srs = gdal.SpatialReference(self.wkt) return self.srs - except Exception as msg: - pass + except Exception as e: + msg = e try: self._srs = gdal.SpatialReference(self.proj4text) return self.srs - except Exception as msg: - pass + except Exception as e: + msg = e raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg)) else: diff --git a/django/contrib/gis/db/models/sql/aggregates.py b/django/contrib/gis/db/models/sql/aggregates.py deleted file mode 100644 index e3fb049cf547..000000000000 --- a/django/contrib/gis/db/models/sql/aggregates.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.db.models.sql import aggregates -from django.db.models.sql.aggregates import * # NOQA - -__all__ = ['Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union'] + aggregates.__all__ - - -warnings.warn( - "django.contrib.gis.db.models.sql.aggregates is deprecated. Use " - "django.contrib.gis.db.models.aggregates instead.", - RemovedInDjango110Warning, stacklevel=2) diff --git a/tests/postgres_tests/migrations/0002_create_test_models.py b/tests/postgres_tests/migrations/0002_create_test_models.py index 4be219f722ee..0152f88a9fa4 100644 --- a/tests/postgres_tests/migrations/0002_create_test_models.py +++ b/tests/postgres_tests/migrations/0002_create_test_models.py @@ -3,7 +3,10 @@ from django.db import migrations, models -from ..fields import * # NOQA +from ..fields import ( + ArrayField, BigIntegerRangeField, DateRangeField, DateTimeRangeField, + FloatRangeField, HStoreField, IntegerRangeField, JSONField, +) class Migration(migrations.Migration): diff --git a/tests/servers/tests.py b/tests/servers/tests.py index dbb298e00362..fa5e5d469036 100644 --- a/tests/servers/tests.py +++ b/tests/servers/tests.py @@ -103,12 +103,9 @@ def test_404(self): Ensure that the LiveServerTestCase serves 404s. Refs #2879. """ - try: + with self.assertRaises(HTTPError) as err: self.urlopen('/') - except HTTPError as err: - self.assertEqual(err.code, 404, 'Expected 404 response') - else: - self.fail('Expected 404 response') + self.assertEqual(err.exception.code, 404, 'Expected 404 response') def test_view(self): """ @@ -132,12 +129,9 @@ def test_no_collectstatic_emulation(self): tries to access a static file that isn't explicitly put under STATIC_ROOT. """ - try: + with self.assertRaises(HTTPError) as err: self.urlopen('/static/another_app/another_app_static_file.txt') - except HTTPError as err: - self.assertEqual(err.code, 404, 'Expected 404 response') - else: - self.fail('Expected 404 response (got %d)' % err.code) + self.assertEqual(err.exception.code, 404, 'Expected 404 response') def test_media_files(self): """ From e7087ac5039eedbca132d7a293ece08d84f929e6 Mon Sep 17 00:00:00 2001 From: Krzysztof Jurewicz Date: Fri, 17 Jun 2016 14:01:13 +0200 Subject: [PATCH 657/756] [1.9.x] Fixed #26774 -- Corrected value of default_zoom in GeoModelAdmin doc Backport of 6dd4d2709bfe427c05842c0269860ef95a0b4f00 from master --- docs/ref/contrib/gis/admin.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/gis/admin.txt b/docs/ref/contrib/gis/admin.txt index 62f7a78b0fbb..5d056911c72a 100644 --- a/docs/ref/contrib/gis/admin.txt +++ b/docs/ref/contrib/gis/admin.txt @@ -20,7 +20,7 @@ GeoDjango's admin site .. attribute:: default_zoom - The default zoom level to use. Defaults to 18. + The default zoom level to use. Defaults to 4. .. attribute:: extra_js From d1ada8c93cf29ef8fc25bf2a6654ebe070156e1c Mon Sep 17 00:00:00 2001 From: Trey Hunner Date: Fri, 17 Jun 2016 18:01:43 -0700 Subject: [PATCH 658/756] [1.9.x] Added urlpatterns variable in docs/topics/http/urls.txt. Backport of 91e9be45ed5ad3b0c3de8890a4fcdbb68836856f from master --- docs/topics/http/urls.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 0cfcf14049b6..d675b36b5ffa 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -830,7 +830,9 @@ For example:: url(r'^(?P\d+)/$', views.DetailView.as_view(), name='detail'), ], 'polls') - url(r'^polls/', include(polls_patterns)), + urlpatterns = [ + url(r'^polls/', include(polls_patterns)), + ] This will include the nominated URL patterns into the given application namespace. From 2ff72962a633001a1ee44838a82d68dfcf04dd32 Mon Sep 17 00:00:00 2001 From: Paul J Stevens Date: Fri, 17 Jun 2016 10:30:40 +0200 Subject: [PATCH 659/756] Refs #26772 -- Added a test for FileField reopening closed files. Thanks Simon Charette for review. --- tests/file_storage/tests.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py index 9aea27420db1..1fe89c738cd4 100644 --- a/tests/file_storage/tests.py +++ b/tests/file_storage/tests.py @@ -500,6 +500,14 @@ def test_filefield_read(self): self.assertEqual(list(obj.normal.chunks(chunk_size=2)), [b"co", b"nt", b"en", b"t"]) obj.normal.close() + def test_filefield_reopen(self): + obj = Storage.objects.create(normal=SimpleUploadedFile('reopen.txt', b'content')) + with obj.normal as normal: + normal.open() + obj.normal.open() + obj.normal.file.seek(0) + obj.normal.close() + def test_duplicate_filename(self): # Multiple files with the same name get _(7 random chars) appended to them. objs = [Storage() for i in range(2)] From 040d6055a27815350c69817bc5c15df96e70c4bb Mon Sep 17 00:00:00 2001 From: Samir Shah Date: Tue, 21 Jun 2016 15:12:07 +0300 Subject: [PATCH 660/756] [1.9.x] Corrected firstof template tag's docstring. Since Django 1.8, firstof escapes its output. Backport of fff5dbe59ca629c295480693f045f03537858eee from master --- django/template/defaulttags.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index e2d28827018b..fe0542fd06e8 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -756,7 +756,7 @@ def do_filter(parser, token): @register.tag def firstof(parser, token): """ - Outputs the first variable passed that is not False, without escaping. + Outputs the first variable passed that is not False. Outputs nothing if all the passed variables are False. @@ -767,11 +767,11 @@ def firstof(parser, token): This is equivalent to:: {% if var1 %} - {{ var1|safe }} + {{ var1 }} {% elif var2 %} - {{ var2|safe }} + {{ var2 }} {% elif var3 %} - {{ var3|safe }} + {{ var3 }} {% endif %} but obviously much cleaner! From 41ac92ef52b02731e6ecc129567166595cf6c178 Mon Sep 17 00:00:00 2001 From: John-Scott Atlakson Date: Wed, 22 Jun 2016 03:38:34 -0700 Subject: [PATCH 661/756] [1.9.x] Fixed typo in docs/topics/logging.txt Backport of 9a54face2572d67c7a31dc967bfb66af6e98165c from master --- docs/topics/logging.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index c58e5c13d6dd..06d95387326c 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -575,7 +575,7 @@ Python logging module. message it receives. If the log record contains a ``request`` attribute, the full details - of the request will be included in the email. The email subject will be + of the request will be included in the email. The email subject will include the phrase "internal IP" if the client's IP address is in the :setting:`INTERNAL_IPS` setting; if not, it will include "EXTERNAL IP". From 4be49ff38a0ab24d202659f851e39aa1070d47ee Mon Sep 17 00:00:00 2001 From: Anderson Resende Date: Sun, 26 Jun 2016 21:07:42 -0300 Subject: [PATCH 662/756] [1.9.x] Fixed #26806 -- Triple quoted docstrings in docs/ref/forms/validation.txt. Backport of 2032bcf18280a875a59a39cf85c226da0a310d11 from master --- docs/ref/forms/validation.txt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index bb9a928c1abb..b57f44cb6c72 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -263,19 +263,16 @@ containing comma-separated email addresses. The full class looks like this:: class MultiEmailField(forms.Field): def to_python(self, value): - "Normalize data to a list of strings." - + """Normalize data to a list of strings.""" # Return an empty list if no input was given. if not value: return [] return value.split(',') def validate(self, value): - "Check if value consists only of valid emails." - + """Check if value consists only of valid emails.""" # Use the parent's handling of required fields, etc. super(MultiEmailField, self).validate(value) - for email in value: validate_email(email) From f40c91cdef74b81f731ad1582c26d0b60044dbc2 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sun, 26 Jun 2016 21:15:29 -0300 Subject: [PATCH 663/756] [1.9.x] Added missing trailing '$' to url() patterns in docs. Backport of c962b9104a22505a7d6c57bbec9eda163f486c7d from master --- docs/topics/auth/default.txt | 8 ++++---- docs/topics/class-based-views/index.txt | 4 ++-- docs/topics/class-based-views/intro.txt | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index 8ba82fbec4bf..2165c0704e73 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -928,7 +928,7 @@ easiest way is to include the provided URLconf in ``django.contrib.auth.urls`` in your own URLconf, for example:: urlpatterns = [ - url('^', include('django.contrib.auth.urls')) + url('^', include('django.contrib.auth.urls')), ] This will include the following URL patterns:: @@ -951,7 +951,7 @@ your URLconf:: from django.contrib.auth import views as auth_views urlpatterns = [ - url('^change-password/', auth_views.password_change) + url('^change-password/$', auth_views.password_change), ] The views have optional arguments you can use to alter the behavior of the @@ -961,10 +961,10 @@ arguments in the URLconf, these will be passed on to the view. For example:: urlpatterns = [ url( - '^change-password/', + '^change-password/$', auth_views.password_change, {'template_name': 'change-password.html'} - ) + ), ] All views return a :class:`~django.template.response.TemplateResponse` diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt index dcc0784ada90..91dd6ff944af 100644 --- a/docs/topics/class-based-views/index.txt +++ b/docs/topics/class-based-views/index.txt @@ -42,7 +42,7 @@ you can simply pass them into the from django.views.generic import TemplateView urlpatterns = [ - url(r'^about/', TemplateView.as_view(template_name="about.html")), + url(r'^about/$', TemplateView.as_view(template_name="about.html")), ] Any arguments passed to :meth:`~django.views.generic.base.View.as_view` will @@ -79,7 +79,7 @@ views:: from some_app.views import AboutView urlpatterns = [ - url(r'^about/', AboutView.as_view()), + url(r'^about/$', AboutView.as_view()), ] diff --git a/docs/topics/class-based-views/intro.txt b/docs/topics/class-based-views/intro.txt index 2fbbb4b63b2b..5cd38d57dbd3 100644 --- a/docs/topics/class-based-views/intro.txt +++ b/docs/topics/class-based-views/intro.txt @@ -93,7 +93,7 @@ request to a matching method if one is defined, or raises from myapp.views import MyView urlpatterns = [ - url(r'^about/', MyView.as_view()), + url(r'^about/$', MyView.as_view()), ] @@ -130,7 +130,7 @@ Another option is to configure class attributes as keyword arguments to the :meth:`~django.views.generic.base.View.as_view` call in the URLconf:: urlpatterns = [ - url(r'^about/', GreetingView.as_view(greeting="G'day")), + url(r'^about/$', GreetingView.as_view(greeting="G'day")), ] .. note:: @@ -245,8 +245,8 @@ The easiest place to do this is in the URLconf where you deploy your view:: from .views import VoteView urlpatterns = [ - url(r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))), - url(r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())), + url(r'^about/$', login_required(TemplateView.as_view(template_name="secret.html"))), + url(r'^vote/$', permission_required('polls.can_vote')(VoteView.as_view())), ] This approach applies the decorator on a per-instance basis. If you From 54c0dea22ee11da9822fc217f108e878647f299e Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 27 Jun 2016 09:13:15 -0700 Subject: [PATCH 664/756] [1.9.x] Refs #26034 -- Corrected a schema test to work with the correct field state. Backport of d47f6d75ef67dbd18abf2bb927753f739a9c117d from master --- tests/schema/tests.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index e200f3416422..957d9cb9b18b 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -1778,15 +1778,14 @@ def test_alter_field_add_unique_to_charfield_with_db_index(self): self.get_constraints_for_column(BookWithoutAuthor, 'title'), ['schema_book_d5d3db17', 'schema_book_title_2dfb2dff_like', 'schema_book_title_2dfb2dff_uniq'] ) - # Alter to remove unique=True (should drop unique index) # XXX: bug! - old_field = BookWithoutAuthor._meta.get_field('title') - new_field = CharField(max_length=100, db_index=True) - new_field.set_attributes_from_name('title') + # Alter to remove unique=True (should drop unique index) + new_field2 = CharField(max_length=100, db_index=True) + new_field2.set_attributes_from_name('title') with connection.schema_editor() as editor: - editor.alter_field(BookWithoutAuthor, old_field, new_field, strict=True) + editor.alter_field(BookWithoutAuthor, new_field, new_field2, strict=True) self.assertEqual( self.get_constraints_for_column(BookWithoutAuthor, 'title'), - ['schema_book_d5d3db17', 'schema_book_title_2dfb2dff_like', 'schema_book_title_2dfb2dff_uniq'] + ['schema_book_d5d3db17', 'schema_book_title_2dfb2dff_like'] ) @unittest.skipUnless(connection.vendor == 'postgresql', "PostgreSQL specific") @@ -1809,11 +1808,10 @@ def test_alter_field_add_db_index_to_charfield_with_unique(self): ['schema_tag_slug_2c418ba3_like', 'schema_tag_slug_key'] ) # Alter to remove db_index=True - old_field = Tag._meta.get_field('slug') - new_field = SlugField(unique=True) - new_field.set_attributes_from_name('slug') + new_field2 = SlugField(unique=True) + new_field2.set_attributes_from_name('slug') with connection.schema_editor() as editor: - editor.alter_field(Tag, old_field, new_field, strict=True) + editor.alter_field(Tag, new_field, new_field2, strict=True) self.assertEqual( self.get_constraints_for_column(Tag, 'slug'), ['schema_tag_slug_2c418ba3_like', 'schema_tag_slug_key'] From e1d83c2f3f617e3c5cf871ecd8942286122ec2b7 Mon Sep 17 00:00:00 2001 From: Camilo Nova Date: Tue, 28 Jun 2016 11:51:51 -0500 Subject: [PATCH 665/756] [1.9.x] Used strict comparison in docs/ref/csrf.txt's JavaScript. Backport of 222e1334bf29605925eefa45ff107ca1155e93c0 from master --- docs/ref/csrf.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/csrf.txt b/docs/ref/csrf.txt index df17d138f5c0..23332760a606 100644 --- a/docs/ref/csrf.txt +++ b/docs/ref/csrf.txt @@ -83,12 +83,12 @@ Acquiring the token is straightforward: // using jQuery function getCookie(name) { var cookieValue = null; - if (document.cookie && document.cookie != '') { + if (document.cookie && document.cookie !== '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? - if (cookie.substring(0, name.length + 1) == (name + '=')) { + if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } From 67c60cce7073762106bca186669af0fe1f588d15 Mon Sep 17 00:00:00 2001 From: Tsering Date: Sat, 23 Apr 2016 16:38:57 -0400 Subject: [PATCH 666/756] [1.9.x] Refs #23386 -- Documented that F() expressions are applied on each model.save() Backport of fc4b4fd5850989458d6e54de12a29b2e40e94ce8 from master --- docs/ref/models/expressions.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/ref/models/expressions.txt b/docs/ref/models/expressions.txt index 7a1945a948b2..4eb16e361f88 100644 --- a/docs/ref/models/expressions.txt +++ b/docs/ref/models/expressions.txt @@ -174,6 +174,22 @@ robust: it will only ever update the field based on the value of the field in the database when the :meth:`~Model.save()` or ``update()`` is executed, rather than based on its value when the instance was retrieved. +``F()`` assignments persist after ``Model.save()`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``F()`` objects assigned to model fields persist after saving the model +instance and will be applied on each :meth:`~Model.save()`. For example:: + + reporter = Reporters.objects.get(name='Tintin') + reporter.stories_filed = F('stories_filed') + 1 + reporter.save() + + reporter.name = 'Tintin Jr.' + reporter.save() + +``stories_filed`` will be updated twice in this case. If it's initially ``1``, +the final value will be ``3``. + Using ``F()`` in filters ~~~~~~~~~~~~~~~~~~~~~~~~ From fe663b3d38fe59a8d922792b11ca05eba1afa575 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 30 Jun 2016 09:00:10 -0400 Subject: [PATCH 667/756] [1.9.x] Fixed #26002 -- Explained ModelAdmin.get_search_results() example. Backport of c9d0a0f7f47c8496a9d8b0cfda94e2ef118d9ab3 from master --- docs/ref/contrib/admin/index.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 58024a543069..1a0ac5ad003c 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1389,7 +1389,7 @@ templates used by the :class:`ModelAdmin` views: implemented by your search method may introduce duplicates into the results, and return ``True`` in the second element of the return value. - For example, to enable search by integer field, you could use:: + For example, to search by ``name`` and ``age``, you could use:: class PersonAdmin(admin.ModelAdmin): list_display = ('name', 'age') @@ -1405,6 +1405,11 @@ templates used by the :class:`ModelAdmin` views: queryset |= self.model.objects.filter(age=search_term_as_int) return queryset, use_distinct + This implementation is more efficient than ``search_fields = + ('name', '=age')`` which results in a string comparison for the numeric + field, for example ``... OR UPPER("polls_choice"."votes"::text) = UPPER('4')`` + on PostgreSQL. + .. method:: ModelAdmin.save_related(request, form, formsets, change) The ``save_related`` method is given the ``HttpRequest``, the parent From 8af6923e9da066aba207d4dc5b2047939503e41c Mon Sep 17 00:00:00 2001 From: Harry Moreno Date: Thu, 30 Jun 2016 18:10:03 -0400 Subject: [PATCH 668/756] [1.9.x] Added parallel test running to "Speeding up the tests" docs. Backport of 30c65ee8187c7a87d2fc83c5b8966b1ea5e5316b from master --- docs/topics/testing/overview.txt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt index f8c8f2486ebc..9eb343b59d47 100644 --- a/docs/topics/testing/overview.txt +++ b/docs/topics/testing/overview.txt @@ -334,10 +334,18 @@ need to test for success or failure at that level. Speeding up the tests --------------------- -In recent versions of Django, the default password hasher is rather slow by -design. If during your tests you are authenticating many users, you may want -to use a custom settings file and set the :setting:`PASSWORD_HASHERS` setting -to a faster hashing algorithm:: +Running tests in parallel +~~~~~~~~~~~~~~~~~~~~~~~~~ + +As long as your tests are properly isolated, you can run them in parallel to +gain a speed up on multi-core hardware. See :option:`test --parallel`. + +Password hashing +~~~~~~~~~~~~~~~~ + +The default password hasher is rather slow by design. If you're authenticating +many users in your tests, you may want to use a custom settings file and set +the :setting:`PASSWORD_HASHERS` setting to a faster hashing algorithm:: PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.MD5PasswordHasher', From bcce93b2c54a05c29bd49667db958dce6265d31a Mon Sep 17 00:00:00 2001 From: "Md. Sadaf Noor" Date: Sat, 2 Jul 2016 16:52:36 +0600 Subject: [PATCH 669/756] [1.9.x] Fixed #26829 -- Simplified version detection command in tutorial Backport of ed1c15d8fbc17e60621ca56f67340987531e647d from master --- docs/intro/tutorial01.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index ba50451d9591..9710be7480db 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -17,7 +17,7 @@ tell Django is installed and which version by running the following command: .. code-block:: console - $ python -c "import django; print(django.get_version())" + $ python -m django --version If Django is installed, you should see the version of your installation. If it isn't, you'll get an error telling "No module named django". From 04beab3399fed99ccb21d0e684df52de6f99cc2c Mon Sep 17 00:00:00 2001 From: Leila20 Date: Sat, 2 Jul 2016 18:10:09 +0200 Subject: [PATCH 670/756] [1.9.x] Fixed #26832 -- Added translated language name on the get_language_info documentation Backport of de4265e082bcce45ef6fb0929a44205463ba552e from master --- docs/topics/i18n/translation.txt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 8719f7de7f33..353ae1d99477 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -525,18 +525,24 @@ Localized names of languages The ``get_language_info()`` function provides detailed information about languages:: - >>> from django.utils.translation import get_language_info + >>> from django.utils.translation import activate, get_language_info + >>> activate('fr') >>> li = get_language_info('de') - >>> print(li['name'], li['name_local'], li['bidi']) - German Deutsch False + >>> print(li['name'], li['name_local'], li['name_translated'], li['bidi']) + German Deutsch Allemand False -The ``name`` and ``name_local`` attributes of the dictionary contain the name of -the language in English and in the language itself, respectively. The ``bidi`` +The ``name``, ``name_local``, and ``name_translated`` attributes of the +dictionary contain the name of the language in English, in the language +itself, and in your current active language respectively. The ``bidi`` attribute is True only for bi-directional languages. The source of the language information is the ``django.conf.locale`` module. Similar access to this information is available for template code. See below. +.. versionchanged:: 1.9 + + The ``'name_translated'`` attribute was added. + .. _specifying-translation-strings-in-template-code: Internationalization: in template code From 0b57016ec7f7faf53b791c33c2ec6d04e92d23a3 Mon Sep 17 00:00:00 2001 From: Jiang Haiyun Date: Mon, 4 Jul 2016 23:02:11 +0800 Subject: [PATCH 671/756] [1.9.x] Fixed a typo in auth docs. Backport of 6d61ec0e1a4eb5768be5add9e1c44c89dacbfa7e from master --- docs/topics/auth/passwords.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/auth/passwords.txt b/docs/topics/auth/passwords.txt index 5b88ce33dd16..2856e7bdb00e 100644 --- a/docs/topics/auth/passwords.txt +++ b/docs/topics/auth/passwords.txt @@ -113,7 +113,7 @@ algorithm. that ``bcrypt(password_with_100_chars) == bcrypt(password_with_100_chars[:72])``. The original ``BCryptPasswordHasher`` does not have any special handling and thus is also subject to this hidden password length limit. - ``BCryptSHA256PasswordHasher`` fixes this by first first hashing the + ``BCryptSHA256PasswordHasher`` fixes this by first hashing the password using sha256. This prevents the password truncation and so should be preferred over the ``BCryptPasswordHasher``. The practical ramification of this truncation is pretty marginal as the average user does not have a From 3cd3a1af6d90c279b0e7032e2b852c777467e1a6 Mon Sep 17 00:00:00 2001 From: Taylor Edmiston Date: Wed, 6 Jul 2016 08:48:43 -0400 Subject: [PATCH 672/756] [1.9.x] Fixed typo in docs/topics/class-based-views/generic-display.txt Backport of 43d0345fe11624d6b4fd960139ff653451b4f147 from master --- docs/topics/class-based-views/generic-display.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/class-based-views/generic-display.txt b/docs/topics/class-based-views/generic-display.txt index 0c3ac0b74250..076afba6788c 100644 --- a/docs/topics/class-based-views/generic-display.txt +++ b/docs/topics/class-based-views/generic-display.txt @@ -290,7 +290,7 @@ technique:: class AcmeBookList(ListView): context_object_name = 'book_list' - queryset = Book.objects.filter(publisher__name='Acme Publishing') + queryset = Book.objects.filter(publisher__name='ACME Publishing') template_name = 'books/acme_list.html' Notice that along with a filtered ``queryset``, we're also using a custom From ca7b926c8558f6e9bbd33a748bc53235b909e29a Mon Sep 17 00:00:00 2001 From: Romain Garrigues Date: Wed, 6 Jul 2016 14:28:37 +0100 Subject: [PATCH 673/756] [1.9.x] Fixed #25461 -- Corrected meta API code examples to account for MTI. In the case of multiple-table inheritance models, get_all_related_objects() and get_all_related_objects_with_model() don't return the auto-created OneToOneField, but the new examples didn't account for this. Backport of 8be84e2ac42b2556fd6fa07794b3708b143ef341 from master --- docs/ref/models/meta.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/ref/models/meta.txt b/docs/ref/models/meta.txt index 9e4d9435a8ca..982db0fc4ffd 100644 --- a/docs/ref/models/meta.txt +++ b/docs/ref/models/meta.txt @@ -238,7 +238,8 @@ can be made to convert your code to the new API: [ f for f in MyModel._meta.get_fields() - if (f.one_to_many or f.one_to_one) and f.auto_created + if (f.one_to_many or f.one_to_one) + and f.auto_created and not f.concrete ] * ``MyModel._meta.get_all_related_objects_with_model()`` becomes:: @@ -246,7 +247,8 @@ can be made to convert your code to the new API: [ (f, f.model if f.model != MyModel else None) for f in MyModel._meta.get_fields() - if (f.one_to_many or f.one_to_one) and f.auto_created + if (f.one_to_many or f.one_to_one) + and f.auto_created and not f.concrete ] * ``MyModel._meta.get_all_related_many_to_many_objects()`` becomes:: From 61da9bb2999171433a01b781478fdc58410f0bb0 Mon Sep 17 00:00:00 2001 From: Daniel Rice Date: Mon, 4 Jul 2016 14:43:29 +0100 Subject: [PATCH 674/756] [1.9.x] Improved grammar in tutorial01. Backport of 1f9deba1b312a12c47668724daf70b8fe2d56b17 from master --- docs/intro/tutorial01.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 9710be7480db..9f9e6b1d721d 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -383,9 +383,10 @@ aren't going to use this feature of Django in the tutorial. :func:`~django.conf.urls.url` argument: name --------------------------------------------- -Naming your URL lets you refer to it unambiguously from elsewhere in Django -especially templates. This powerful feature allows you to make global changes -to the url patterns of your project while only touching a single file. +Naming your URL lets you refer to it unambiguously from elsewhere in Django, +especially from within templates. This powerful feature allows you to make +global changes to the url patterns of your project while only touching a single +file. When you're comfortable with the basic request and response flow, read :doc:`part 2 of this tutorial ` to start working with the From 76ba451d2f61cb85287c3297d8e427d92d35691c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 6 Jul 2016 15:31:12 -0400 Subject: [PATCH 675/756] [1.9.x] Fixed capitalization of "URL pattern". Backport of 415ae960bb9f1bdae798023fdce3247d2c938eec from master --- docs/intro/tutorial01.txt | 2 +- docs/ref/urlresolvers.txt | 2 +- docs/releases/1.6.txt | 2 +- docs/topics/i18n/translation.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 9f9e6b1d721d..10738fa91f13 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -385,7 +385,7 @@ aren't going to use this feature of Django in the tutorial. Naming your URL lets you refer to it unambiguously from elsewhere in Django, especially from within templates. This powerful feature allows you to make -global changes to the url patterns of your project while only touching a single +global changes to the URL patterns of your project while only touching a single file. When you're comfortable with the basic request and response flow, read diff --git a/docs/ref/urlresolvers.txt b/docs/ref/urlresolvers.txt index b4c05d607582..e048d00c0dae 100644 --- a/docs/ref/urlresolvers.txt +++ b/docs/ref/urlresolvers.txt @@ -60,7 +60,7 @@ This ``current_app`` argument is used as a hint to resolve application namespaces into URLs on specific application instances, according to the :ref:`namespaced URL resolution strategy `. -The ``urlconf`` argument is the URLconf module containing the url patterns to +The ``urlconf`` argument is the URLconf module containing the URL patterns to use for reversing. By default, the root URLconf for the current thread is used. .. deprecated:: 1.8 diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt index c17e7dfa3a5d..c8bd3f86f459 100644 --- a/docs/releases/1.6.txt +++ b/docs/releases/1.6.txt @@ -788,7 +788,7 @@ the ``name`` argument so it doesn't conflict with the new url:: url(r'^reset/(?P[0-9A-Za-z]+)-(?P.+)/$', 'django.contrib.auth.views.password_reset_confirm_uidb36'), -You can remove this url pattern after your app has been deployed with Django +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 diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 353ae1d99477..b69cae344c91 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1347,7 +1347,7 @@ Language prefix in URL patterns :func:`django.conf.urls.url` instances instead. This function can be used in your root URLconf and Django will automatically -prepend the current active language code to all url patterns defined within +prepend the current active language code to all URL patterns defined within :func:`~django.conf.urls.i18n.i18n_patterns`. Example URL patterns:: from django.conf.urls import include, url From d5f1736dbd904096608cb35caf37decce9215915 Mon Sep 17 00:00:00 2001 From: Sean Marlow Date: Wed, 6 Jul 2016 12:11:43 -0400 Subject: [PATCH 676/756] [1.9.x] Fixed #26837 -- Documented ModelMultipleChoiceField.to_field_name Backport of 8b9e16ec858c23cb65ea5de95f65a71b31521841 from master --- docs/ref/forms/fields.txt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 5cdc3d86f098..960674b38470 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -1236,13 +1236,19 @@ method:: Allows the selection of one or more model objects, suitable for representing a many-to-many relation. As with :class:`ModelChoiceField`, you can use ``label_from_instance`` to customize the object - representations, and ``queryset`` is a required parameter: + representations. + + A single argument is required: .. attribute:: queryset - A ``QuerySet`` of model objects from which the choices for the - field will be derived, and which will be used to validate the - user's selection. + Same as :class:`ModelChoiceField.queryset`. + + Takes one optional argument: + + .. attribute:: to_field_name + + Same as :class:`ModelChoiceField.to_field_name`. Creating custom fields ====================== From b87d3180b20b785acc3b4099ad4df078474936d0 Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Sat, 9 Jul 2016 10:22:13 -0400 Subject: [PATCH 677/756] [1.9.x] Fixed numpy deprecation warning silencing in template_tests. Backport of 418658f453bed7fe7949dda26651aab370003e6a from master --- tests/template_tests/syntax_tests/test_numpy.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/template_tests/syntax_tests/test_numpy.py b/tests/template_tests/syntax_tests/test_numpy.py index 822b10fd7adb..16b4d7433416 100644 --- a/tests/template_tests/syntax_tests/test_numpy.py +++ b/tests/template_tests/syntax_tests/test_numpy.py @@ -7,6 +7,7 @@ try: import numpy + VisibleDeprecationWarning = numpy.VisibleDeprecationWarning except ImportError: numpy = False @@ -14,12 +15,13 @@ @skipIf(numpy is False, "Numpy must be installed to run these tests.") class NumpyTests(SimpleTestCase): # Ignore numpy deprecation warnings (#23890) - warnings.filterwarnings( - "ignore", - "Using a non-integer number instead of an " - "integer will result in an error in the future", - DeprecationWarning - ) + if numpy: + warnings.filterwarnings( + "ignore", + "Using a non-integer number instead of an " + "integer will result in an error in the future", + numpy.VisibleDeprecationWarning + ) @setup({'numpy-array-index01': '{{ var.1 }}'}) def test_numpy_array_index01(self): From 065d05e7981b4541205230695a5c02fb9c2df85e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 11 Jul 2016 15:22:32 -0400 Subject: [PATCH 678/756] [1.9.x] Linked "Features removed" release notes to corresponding deprecation notes. Backport of ee2f0f311a1e84754f30d2586c0bd96f4fd9e8b6 from master --- docs/releases/1.7.txt | 8 +++----- docs/releases/1.8.txt | 6 +++--- docs/releases/1.9.txt | 6 +++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index e51257f3e906..4ba08d935e25 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -1814,11 +1814,9 @@ Private APIs ``django.db.models.sql.where.WhereNode.make_atom()`` and Features removed in 1.7 ======================= -These features have reached the end of their -:ref:`deprecation cycle ` and so have been -removed in Django 1.7 (please see the -:ref:`deprecation timeline ` for more details): - +These features have reached the end of their deprecation cycle and are removed +in Django 1.7. See :ref:`deprecated-features-1.5` for details, including how to +remove usage of these features. * ``django.utils.simplejson`` is removed. diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 261de4c40d17..7393c858f6f4 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -1731,9 +1731,9 @@ After switching to the new signature the router will also be called by the Features removed in 1.8 ======================= -These features have reached the end of their deprecation cycle and so have been -removed in Django 1.8 (please see the :ref:`deprecation timeline -` for more details): +These features have reached the end of their deprecation cycle and are removed +in Django 1.8. See :ref:`deprecated-features-1.6` for details, including how to +remove usage of these features. * ``django.contrib.comments`` is removed. diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index f825b435b3b2..19f7795fc4e2 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -1390,9 +1390,9 @@ Miscellaneous Features removed in 1.9 ======================= -These features have reached the end of their deprecation cycle and so have been -removed in Django 1.9 (please see the :ref:`deprecation timeline -` for more details): +These features have reached the end of their deprecation cycle and are removed +in Django 1.9. See :ref:`deprecated-features-1.7` for details, including how to +remove usage of these features. * ``django.utils.dictconfig`` is removed. From e6db7271547087034b1ee2db11676601706e67a3 Mon Sep 17 00:00:00 2001 From: "Md. Sadaf Noor" Date: Sun, 3 Jul 2016 12:59:47 +0600 Subject: [PATCH 679/756] [1.9.x] Fixed #26831 -- Documented session data must be JSON encodable for JSONSerializer. Backport of 1f82b857ceb75f2d7a68e79c6a00c30bfe7f1318 from master --- docs/topics/http/sessions.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index aa0ac548c935..69b291ded606 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -354,6 +354,9 @@ Bundled serializers >>> request.session['0'] 'bar' + Similarly, data that can't be encoded in JSON, such as non-UTF8 bytes like + ``'\xd9'`` (which raises :exc:`UnicodeDecodeError`), can't be stored. + See the :ref:`custom-serializers` section for more details on limitations of JSON serialization. From b6af8b6f2e15f4302205975d8519a8c0f5650e57 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 12 Jul 2016 17:50:17 -0700 Subject: [PATCH 680/756] [1.9.x] Fixed #26889 -- Fixed missing PostgreSQL index in SchemaEditor.add_field(). Backport of 2e4cfcd2b9a0984ad6c4087a5deebbf33413835c from master --- django/db/backends/postgresql/schema.py | 6 ++++++ docs/releases/1.8.14.txt | 14 +++++++++++++ docs/releases/1.9.8.txt | 4 +++- docs/releases/index.txt | 1 + tests/schema/tests.py | 26 +++++++++++++++++++++++++ 5 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 docs/releases/1.8.14.txt diff --git a/django/db/backends/postgresql/schema.py b/django/db/backends/postgresql/schema.py index 8d4dc871cd91..f23b147dabbc 100644 --- a/django/db/backends/postgresql/schema.py +++ b/django/db/backends/postgresql/schema.py @@ -17,6 +17,12 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): def quote_value(self, value): return psycopg2.extensions.adapt(value) + def add_field(self, model, field): + super(DatabaseSchemaEditor, self).add_field(model, field) + like_index_statement = self._create_like_index_sql(model, field) + if like_index_statement is not None: + self.deferred_sql.append(like_index_statement) + def _model_indexes_sql(self, model): output = super(DatabaseSchemaEditor, self)._model_indexes_sql(model) if not model._meta.managed or model._meta.proxy or model._meta.swapped: diff --git a/docs/releases/1.8.14.txt b/docs/releases/1.8.14.txt new file mode 100644 index 000000000000..6311172abcf9 --- /dev/null +++ b/docs/releases/1.8.14.txt @@ -0,0 +1,14 @@ +=========================== +Django 1.8.14 release notes +=========================== + +*Under development* + +Django 1.8.14 fixes several bugs in 1.8.13. + +Bugfixes +======== + +* Fixed missing ``varchar/text_pattern_ops`` index on ``CharField`` and + ``TextField`` respectively when using ``AddField`` on PostgreSQL + (:ticket:`26889`). diff --git a/docs/releases/1.9.8.txt b/docs/releases/1.9.8.txt index 7a3b55bae5a2..7265e166b8a9 100644 --- a/docs/releases/1.9.8.txt +++ b/docs/releases/1.9.8.txt @@ -9,4 +9,6 @@ Django 1.9.8 fixes several bugs in 1.9.7. Bugfixes ======== -* ... +* Fixed missing ``varchar/text_pattern_ops`` index on ``CharField`` and + ``TextField`` respectively when using ``AddField`` on PostgreSQL + (:ticket:`26889`). diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 81b11e86613e..8819bc35dfd6 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -40,6 +40,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.8.14 1.8.13 1.8.12 1.8.11 diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 957d9cb9b18b..2032743db136 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -1717,6 +1717,32 @@ def test_add_textfield_unhashable_default(self): with connection.schema_editor() as editor: editor.add_field(Author, new_field) + @unittest.skipUnless(connection.vendor == 'postgresql', "PostgreSQL specific") + def test_add_indexed_charfield(self): + field = CharField(max_length=255, db_index=True) + field.set_attributes_from_name('nom_de_plume') + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.add_field(Author, field) + # Should create two indexes; one for like operator. + self.assertEqual( + self.get_constraints_for_column(Author, 'nom_de_plume'), + ['schema_author_95aa9e9b', 'schema_author_nom_de_plume_7570a851_like'], + ) + + @unittest.skipUnless(connection.vendor == 'postgresql', "PostgreSQL specific") + def test_add_unique_charfield(self): + field = CharField(max_length=255, unique=True) + field.set_attributes_from_name('nom_de_plume') + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.add_field(Author, field) + # Should create two indexes; one for like operator. + self.assertEqual( + self.get_constraints_for_column(Author, 'nom_de_plume'), + ['schema_author_nom_de_plume_7570a851_like', 'schema_author_nom_de_plume_key'] + ) + @unittest.skipUnless(connection.vendor == 'postgresql', "PostgreSQL specific") def test_alter_field_add_index_to_charfield(self): # Create the table and verify no initial indexes. From 678d9f524184e9c58c51603d7bc8704bfbb8f5a4 Mon Sep 17 00:00:00 2001 From: Daniel Rice Date: Wed, 13 Jul 2016 18:16:29 +0100 Subject: [PATCH 681/756] [1.9.x] Reworded a sentence in tutorial 7. Backport of 08c723fbae9d7625599fb84f602b425e82943e15 from master --- docs/intro/tutorial07.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/tutorial07.txt b/docs/intro/tutorial07.txt index 7f48b85a59b7..7dd0f42e22c8 100644 --- a/docs/intro/tutorial07.txt +++ b/docs/intro/tutorial07.txt @@ -327,7 +327,7 @@ when loading Django templates; it's a search path. Just like the static files, we *could* have all our templates together, in one big templates directory, and it would work perfectly well. However, - templates that belongs to a particular application, we should put in the + templates that belong to a particular application should be placed in that application’s template directory (e.g. ``polls/templates``) rather than the project’s (``templates``). We'll discuss in more detail in the :doc:`reusable apps tutorial ` *why* we do this. From 5316ae7d4f03ceed268422cc9adcedbba0d6d7f4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 14 Jul 2016 08:02:11 -0400 Subject: [PATCH 682/756] [1.9.x] Fixed #26894 -- Fixed a typo in docs/faq/admin.txt Backport of a11719047711d8031dcea6a71cc5972b9ec1d48f from master --- docs/faq/admin.txt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/faq/admin.txt b/docs/faq/admin.txt index a1d42099f9b1..26ad08374fa0 100644 --- a/docs/faq/admin.txt +++ b/docs/faq/admin.txt @@ -6,13 +6,10 @@ I can't log in. When I enter a valid username and password, it just brings up th =========================================================================================================================== The login cookie isn't being set correctly, because the domain of the cookie -sent out by Django doesn't match the domain in your browser. Try these two -things: - -* Set the :setting:`SESSION_COOKIE_DOMAIN` setting in your admin config - file to match your domain. For example, if you're going to - "https://www.example.com/admin/" in your browser, in "myproject.settings" you - should set :setting:`SESSION_COOKIE_DOMAIN` = 'www.example.com'. +sent out by Django doesn't match the domain in your browser. Try setting the +:setting:`SESSION_COOKIE_DOMAIN` setting to match your domain. For example, if +you're going to "https://www.example.com/admin/" in your browser, set +``SESSION_COOKIE_DOMAIN = 'www.example.com'``. I can't log in. When I enter a valid username and password, it brings up the login page again, with a "Please enter a correct username and password" error. =========================================================================================================================================================== From a74adb4c35a90f41e56bb97166a784e56835a56a Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 14 Jul 2016 18:24:40 +0200 Subject: [PATCH 683/756] [1.9.x] Fixed #26897 -- Fixed makemessages crash on Python 2 with non-ASCII file names Thanks Tim Graham for the review. Backport of 3e71f6544feca490211e88db4f449dfdb7acce39 from master. --- django/core/management/commands/makemessages.py | 2 +- docs/releases/1.9.8.txt | 3 +++ tests/i18n/test_extraction.py | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py index 0df47d306e5c..0e75c7e99ce8 100644 --- a/django/core/management/commands/makemessages.py +++ b/django/core/management/commands/makemessages.py @@ -511,7 +511,7 @@ def process_locale_dir(self, locale_dir, files): input_files = [bf.work_path for bf in build_files] with NamedTemporaryFile(mode='w+') as input_files_list: - input_files_list.write('\n'.join(input_files)) + input_files_list.write(force_str('\n'.join(input_files), encoding=DEFAULT_LOCALE_ENCODING)) input_files_list.flush() args.extend(['--files-from', input_files_list.name]) args.extend(self.xgettext_options) diff --git a/docs/releases/1.9.8.txt b/docs/releases/1.9.8.txt index 7265e166b8a9..8db5c3d01f6e 100644 --- a/docs/releases/1.9.8.txt +++ b/docs/releases/1.9.8.txt @@ -12,3 +12,6 @@ Bugfixes * Fixed missing ``varchar/text_pattern_ops`` index on ``CharField`` and ``TextField`` respectively when using ``AddField`` on PostgreSQL (:ticket:`26889`). + +* Fixed ``makemessages`` crash on Python 2 with non-ASCII file names + (:ticket:`26897`). diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py index 8ca9bdd779d8..98e8e074cc06 100644 --- a/tests/i18n/test_extraction.py +++ b/tests/i18n/test_extraction.py @@ -242,6 +242,10 @@ def test_unicode_decode_error(self): self.assertIn("UnicodeDecodeError: skipped file not_utf8.txt in .", force_text(out.getvalue())) + def test_unicode_file_name(self): + open(os.path.join(self.test_dir, 'vidéo.txt'), 'a').close() + management.call_command('makemessages', locale=[LOCALE], verbosity=0) + def test_extraction_warning(self): """test xgettext warning about multiple bare interpolation placeholders""" os.chdir(self.test_dir) From e1a60f2ecbc9311744a42e5a7247fd4e8e2a6f91 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 14 Jul 2016 21:43:52 +0200 Subject: [PATCH 684/756] [1.9.x] Refs #26897 -- Missing directory containment in i18n test --- tests/i18n/test_extraction.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py index 98e8e074cc06..cc4d7a81adb9 100644 --- a/tests/i18n/test_extraction.py +++ b/tests/i18n/test_extraction.py @@ -243,6 +243,7 @@ def test_unicode_decode_error(self): force_text(out.getvalue())) def test_unicode_file_name(self): + os.chdir(self.test_dir) open(os.path.join(self.test_dir, 'vidéo.txt'), 'a').close() management.call_command('makemessages', locale=[LOCALE], verbosity=0) From ab2f5f764a2f6db97e23cccd5c4f5abbb43d1caf Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 14 Jul 2016 22:39:28 +0200 Subject: [PATCH 685/756] [1.9.x] Refs #26897 -- Added cleanup to i18n test --- tests/i18n/test_extraction.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py index cc4d7a81adb9..f013b3077c99 100644 --- a/tests/i18n/test_extraction.py +++ b/tests/i18n/test_extraction.py @@ -244,7 +244,9 @@ def test_unicode_decode_error(self): def test_unicode_file_name(self): os.chdir(self.test_dir) - open(os.path.join(self.test_dir, 'vidéo.txt'), 'a').close() + unicode_path = os.path.join(self.test_dir, 'vidéo.txt') + open(unicode_path, 'a').close() + self.addCleanup(self.rmfile, unicode_path) management.call_command('makemessages', locale=[LOCALE], verbosity=0) def test_extraction_warning(self): From d03bf6fe4e9bf5b07de62c1a271c4b41a7d3d158 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 6 Jul 2016 15:41:06 -0400 Subject: [PATCH 686/756] [1.9.x] Fixed XSS in admin's add/change related popup. This is a security fix. --- .../static/admin/js/admin/RelatedObjectLookups.js | 2 +- django/views/debug.py | 4 ++-- docs/releases/1.8.14.txt | 15 +++++++++++++-- docs/releases/1.9.8.txt | 15 +++++++++++++-- tests/admin_views/models.py | 4 ++++ tests/admin_views/tests.py | 9 ++++++++- 6 files changed, 41 insertions(+), 8 deletions(-) diff --git a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js index 4ac9baa8ee78..76cc9522393c 100644 --- a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js +++ b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js @@ -120,7 +120,7 @@ var selects = django.jQuery(selectsSelector); selects.find('option').each(function() { if (this.value === objId) { - this.innerHTML = newRepr; + this.textContent = newRepr; this.value = newId; } }); diff --git a/django/views/debug.py b/django/views/debug.py index 7cff3eface36..2629d345eb61 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -631,13 +631,13 @@ def default_urlconf(request): var s = link.getElementsByTagName('span')[0]; var uarr = String.fromCharCode(0x25b6); var darr = String.fromCharCode(0x25bc); - s.innerHTML = s.innerHTML == uarr ? darr : uarr; + s.textContent = s.textContent == uarr ? darr : uarr; return false; } function switchPastebinFriendly(link) { s1 = "Switch to copy-and-paste view"; s2 = "Switch back to interactive view"; - link.innerHTML = link.innerHTML.trim() == s1 ? s2: s1; + link.textContent = link.textContent.trim() == s1 ? s2: s1; toggle('browserTraceback', 'pastebinTraceback'); return false; } diff --git a/docs/releases/1.8.14.txt b/docs/releases/1.8.14.txt index 6311172abcf9..31a304f7c060 100644 --- a/docs/releases/1.8.14.txt +++ b/docs/releases/1.8.14.txt @@ -2,9 +2,20 @@ Django 1.8.14 release notes =========================== -*Under development* +*July 18, 2016* -Django 1.8.14 fixes several bugs in 1.8.13. +Django 1.8.14 fixes a security issue and a bug in 1.8.13. + +XSS in admin's add/change related popup +======================================= + +Unsafe usage of JavaScript's ``Element.innerHTML`` could result in XSS in the +admin's add/change related popup. ``Element.textContent`` is now used to +prevent execution of the data. + +The debug view also used ``innerHTML``. Although a security issue wasn't +identified there, out of an abundance of caution it's also updated to use +``textContent``. Bugfixes ======== diff --git a/docs/releases/1.9.8.txt b/docs/releases/1.9.8.txt index 8db5c3d01f6e..08ba5ae08f8a 100644 --- a/docs/releases/1.9.8.txt +++ b/docs/releases/1.9.8.txt @@ -2,9 +2,20 @@ Django 1.9.8 release notes ========================== -*Under development* +*July 18, 2016* -Django 1.9.8 fixes several bugs in 1.9.7. +Django 1.9.8 fixes a security issue and several bugs in 1.9.7. + +XSS in admin's add/change related popup +======================================= + +Unsafe usage of JavaScript's ``Element.innerHTML`` could result in XSS in the +admin's add/change related popup. ``Element.textContent`` is now used to +prevent execution of the data. + +The debug view also used ``innerHTML``. Although a security issue wasn't +identified there, out of an abundance of caution it's also updated to use +``textContent``. Bugfixes ======== diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index aa91eef57ee4..f1c77e28725f 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -17,6 +17,7 @@ from django.utils.encoding import python_2_unicode_compatible +@python_2_unicode_compatible class Section(models.Model): """ A simple section that links to articles, to test linking to related items @@ -24,6 +25,9 @@ class Section(models.Model): """ name = models.CharField(max_length=100) + def __str__(self): + return self.name + @property def name_property(self): """ diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 31e4326ff706..bf419678a086 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -4625,8 +4625,10 @@ def test_list_editable_popups(self): """ list_editable foreign keys have add/change popups. """ + from selenium.webdriver.support.ui import Select s1 = Section.objects.create(name='Test section') Article.objects.create( + title='foo', content='

    Middle content

    ', date=datetime.datetime(2008, 3, 18, 11, 54, 58), section=s1, @@ -4638,8 +4640,13 @@ def test_list_editable_popups(self): self.wait_for_popup() self.selenium.switch_to.window(self.selenium.window_handles[-1]) self.wait_for_text('#content h1', 'Change section') - self.selenium.close() + name_input = self.selenium.find_element_by_id('id_name') + name_input.clear() + name_input.send_keys('edited section') + self.selenium.find_element_by_xpath('//input[@value="Save"]').click() self.selenium.switch_to.window(self.selenium.window_handles[0]) + select = Select(self.selenium.find_element_by_id('id_form-0-section')) + self.assertEqual(select.first_selected_option.text, 'edited section') # Add popup self.selenium.find_element_by_id('add_id_form-0-section').click() From 2234d1f08d079a3e4be4f1a89847dc294a4a5c1a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 18 Jul 2016 11:34:44 -0400 Subject: [PATCH 687/756] [1.9.x] Bumped version for 1.9.8 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 8c92de66d99a..9583467b214a 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 8, 'alpha', 0) +VERSION = (1, 9, 8, 'final', 0) __version__ = get_version(VERSION) From 384fb90aaf0a23baa07e311b417e4becb96d217f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 18 Jul 2016 14:59:03 -0400 Subject: [PATCH 688/756] [1.9.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 9583467b214a..90e16d9fe271 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 8, 'final', 0) +VERSION = (1, 9, 9, 'alpha', 0) __version__ = get_version(VERSION) From 57c4db8c6903e3e95f53f8f0ca448231b385071f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 18 Jul 2016 15:19:35 -0400 Subject: [PATCH 689/756] [1.9.x] Added CVE-2016-6186 to the security release archive. Backport of bc53af13cbf09b0cbac945426c2d51d0ca52fff3 from master --- docs/releases/security.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 58e16748f644..509ef7f244db 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -728,3 +728,16 @@ Versions affected * Django 1.9 `(patch) `__ * Django 1.8 `(patch) `__ + +July 18, 2016 - CVE-2016-6186 +----------------------------- + +`CVE-2016-6186 `_: +XSS in admin's add/change related popup. +`Full description `__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 1.9 `(patch) `__ +* Django 1.8 `(patch) `__ From 77ec7c50d69aac0be18086041d3e7007279e22fc Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 19 Jul 2016 07:56:15 -0400 Subject: [PATCH 690/756] [1.9.x] Fixed a GeoIP test failure with the latest data. Backport of 081fdaf110386db940d834ba51d93e23aa293fcd from master --- tests/gis_tests/test_geoip.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/gis_tests/test_geoip.py b/tests/gis_tests/test_geoip.py index 2c44db492a00..8dbda2c5d903 100644 --- a/tests/gis_tests/test_geoip.py +++ b/tests/gis_tests/test_geoip.py @@ -128,8 +128,8 @@ def test04_city(self): lat_lon = g.lat_lon(query) lat_lon = (lat_lon[1], lat_lon[0]) for tup in (geom.tuple, g.coords(query), g.lon_lat(query), lat_lon): - self.assertAlmostEqual(lon, tup[0], 4) - self.assertAlmostEqual(lat, tup[1], 4) + self.assertAlmostEqual(lon, tup[0], 0) + self.assertAlmostEqual(lat, tup[1], 0) def test05_unicode_response(self): "Testing that GeoIP strings are properly encoded, see #16553." From bc151b692a2e0f35a15b72a5bafcb42dea263461 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 21 Jul 2016 10:06:41 -0400 Subject: [PATCH 691/756] [1.9.x] Fixed #26918 -- Clarified source of pre/post_save update_fields argument. Backport of a05d86a69a7f11f00032b8d7846932b139c670e0 from master --- docs/ref/signals.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index e28bab2c12c9..874116db03f3 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -125,8 +125,8 @@ Arguments sent with this signal: The database alias being used. ``update_fields`` - The set of fields to update explicitly specified in the ``save()`` method. - ``None`` if this argument was not used in the ``save()`` call. + The set of fields to update as passed to :meth:`.Model.save`, or ``None`` + if ``update_fields`` wasn't passed to ``save()``. ``post_save`` ------------- @@ -158,8 +158,8 @@ Arguments sent with this signal: The database alias being used. ``update_fields`` - The set of fields to update explicitly specified in the ``save()`` method. - ``None`` if this argument was not used in the ``save()`` call. + The set of fields to update as passed to :meth:`.Model.save`, or ``None`` + if ``update_fields`` wasn't passed to ``save()``. ``pre_delete`` -------------- From 8f7008c48769b2b4340581b6b0f6c41d99c2c1df Mon Sep 17 00:00:00 2001 From: petedmarsh Date: Thu, 21 Jul 2016 15:28:31 +0100 Subject: [PATCH 692/756] [1.9.x] Fixed #26899 -- Documented why RawSQL params is a required parameter. Backport of 7bf3ba0d0c700670d13d7683eec7bd3eb3d4dd1f from master --- docs/ref/models/expressions.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/ref/models/expressions.txt b/docs/ref/models/expressions.txt index 4eb16e361f88..051d92d54384 100644 --- a/docs/ref/models/expressions.txt +++ b/docs/ref/models/expressions.txt @@ -447,7 +447,9 @@ should avoid them if possible. You should be very careful to escape any parameters that the user can control by using ``params`` in order to protect against :ref:`SQL injection - attacks `. + attacks `. ``params`` is a required argument to + force you to acknowledge that you're not interpolating your SQL with user + provided data. .. currentmodule:: django.db.models From 638ec38c8e9c468773726a64df53ad810b5610d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Hertzog?= Date: Thu, 21 Jul 2016 18:59:55 +0200 Subject: [PATCH 693/756] [1.9.x] Fixed #26923 -- Fixed template_tests with numpy < 1.9.0. Backport of 8e5cbc884f77c85b0edbc6206810643abaf589c9 from master --- tests/template_tests/syntax_tests/test_numpy.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/template_tests/syntax_tests/test_numpy.py b/tests/template_tests/syntax_tests/test_numpy.py index 16b4d7433416..7a6caf6528e5 100644 --- a/tests/template_tests/syntax_tests/test_numpy.py +++ b/tests/template_tests/syntax_tests/test_numpy.py @@ -10,6 +10,8 @@ VisibleDeprecationWarning = numpy.VisibleDeprecationWarning except ImportError: numpy = False +except AttributeError: # numpy < 1.9.0, e.g. 1.8.2 in Debian 8 + VisibleDeprecationWarning = DeprecationWarning @skipIf(numpy is False, "Numpy must be installed to run these tests.") @@ -20,7 +22,7 @@ class NumpyTests(SimpleTestCase): "ignore", "Using a non-integer number instead of an " "integer will result in an error in the future", - numpy.VisibleDeprecationWarning + VisibleDeprecationWarning ) @setup({'numpy-array-index01': '{{ var.1 }}'}) From b0b0a02af53a36776435133480334dc52df0301f Mon Sep 17 00:00:00 2001 From: Preetham Nosum Date: Thu, 21 Jul 2016 13:39:13 -0400 Subject: [PATCH 694/756] [1.9.x] Fixed #18348 -- Documented SesssionStore.create() Backport of 32cf01c1c1dcd5fa5d700d0e5117778caf947b74 from master --- docs/topics/http/sessions.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 69b291ded606..a042d6222985 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -493,14 +493,20 @@ An API is available to manipulate session data outside of a view:: >>> s = SessionStore() >>> # stored as seconds since epoch since datetimes are not serializable in JSON. >>> s['last_login'] = 1376587691 - >>> s.save() + >>> s.create() >>> s.session_key '2b1189a188b44ad18c35e113ac6ceead' - >>> s = SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead') >>> s['last_login'] 1376587691 +``SessionStore.create()`` is designed to create a new session (i.e. one not +loaded from the session store and with ``session_key=None``). ``save()`` is +designed to save an existing session (i.e. one loaded from the session store). +Calling ``save()`` on a new session may also work but has a small chance of +generating a ``session_key`` that collides with an existing one. ``create()`` +calls ``save()`` and loops until an unused ``session_key`` is generated. + If you're using the ``django.contrib.sessions.backends.db`` backend, each session is just a normal Django model. The ``Session`` model is defined in ``django/contrib/sessions/models.py``. Because it's a normal model, you can From 1152c9b7954ede3b60dc6cbad6565ba21c4d7e96 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 23 Jul 2016 06:14:47 -0700 Subject: [PATCH 695/756] [1.9.x] Added stub release notes for 1.9.9. Backport of b2e54aec58439adbd64fec2427d87f11945ab33c from master --- docs/releases/1.9.9.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.9.9.txt diff --git a/docs/releases/1.9.9.txt b/docs/releases/1.9.9.txt new file mode 100644 index 000000000000..c458f9611a0f --- /dev/null +++ b/docs/releases/1.9.9.txt @@ -0,0 +1,12 @@ +========================== +Django 1.9.9 release notes +========================== + +*Under development* + +Django 1.9.9 fixes several bugs in 1.9.8. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 8819bc35dfd6..f0d3c3c8527c 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.9.9 1.9.8 1.9.7 1.9.6 From caec27f87c56442d4512cb6dd9e6c26fd8d4414a Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 22 Jul 2016 17:38:35 -0700 Subject: [PATCH 696/756] [1.9.x] Fixed #26938 -- Fixed invalid HTML in template postmortem on the debug page. Backport of 348cfccd9072f0e08ffe4cfb3946d1dc6a629e86 from master --- django/views/debug.py | 1 - docs/releases/1.9.9.txt | 3 ++- tests/view_tests/tests/test_debug.py | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/django/views/debug.py b/django/views/debug.py index 2629d345eb61..a34962dce8fe 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -721,7 +721,6 @@ def default_urlconf(request): {% for attempt in entry.tried %}
  • {{ attempt.0.loader_name }}: {{ attempt.0.name }} ({{ attempt.1 }})
  • {% endfor %} - {% else %}
  • This engine did not provide a list of tried templates.
  • {% endif %} diff --git a/docs/releases/1.9.9.txt b/docs/releases/1.9.9.txt index c458f9611a0f..8e90f6c8ddd3 100644 --- a/docs/releases/1.9.9.txt +++ b/docs/releases/1.9.9.txt @@ -9,4 +9,5 @@ Django 1.9.9 fixes several bugs in 1.9.8. Bugfixes ======== -* ... +* Fixed invalid HTML in template postmortem on the debug page + (:ticket:`26938`). diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py index 03554f00eb70..714652f8b1b1 100644 --- a/tests/view_tests/tests/test_debug.py +++ b/tests/view_tests/tests/test_debug.py @@ -159,6 +159,14 @@ def test_template_loader_postmortem(self): }]): response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name})) self.assertContains(response, "%s (Source does not exist)" % template_path, status_code=500, count=2) + # Assert as HTML. + self.assertContains( + response, + '
  • django.template.loaders.filesystem.Loader: ' + '%s (Source does not exist)
  • ' % os.path.join(tempdir, 'notfound.html'), + status_code=500, + html=True, + ) def test_no_template_source_loaders(self): """ From bdeec1d2aeb74d52cae8e1f9a334eefb5199f07e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 25 Jul 2016 06:21:39 -0400 Subject: [PATCH 697/756] [1.9.x] Fixed #26925 -- Linked aggregation ordering topic from Meta.ordering docs. Backport of 3aaf6cf0f3288986c4ce56defea26420f8a48534 from master --- docs/ref/models/options.txt | 3 +++ docs/topics/db/aggregation.txt | 2 ++ 2 files changed, 5 insertions(+) diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 8b77c7101dbc..3650d20b0a91 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -258,6 +258,9 @@ Django quotes column and table names behind the scenes. ordering = ['-pub_date', 'author'] + Default ordering also affects :ref:`aggregation queries + `. + .. warning:: Ordering is not a free operation. Each field you add to the ordering diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt index ec11fcf4eecd..1287f1ee5bfd 100644 --- a/docs/topics/db/aggregation.txt +++ b/docs/topics/db/aggregation.txt @@ -479,6 +479,8 @@ will be automatically added to the result set. However, if the ``values()`` clause is applied after the ``annotate()`` clause, you need to explicitly include the aggregate column. +.. _aggregation-ordering-interaction: + Interaction with default ordering or ``order_by()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 3ee1751a9907eeb1680e89a1e390717b28308231 Mon Sep 17 00:00:00 2001 From: Naved Khan Date: Mon, 25 Jul 2016 16:31:04 +0530 Subject: [PATCH 698/756] =?UTF-8?q?[1.9.x]=20Fixed=20#26941=20--=20Correct?= =?UTF-8?q?ed=20uwsgi=20"env=20=3D=20LANG=3D=E2=80=A6"=20configuration=20i?= =?UTF-8?q?n=20docs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport of 9a5a789da2b53a9c19ea47130507ce26839eb008 from master --- docs/howto/deployment/wsgi/uwsgi.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/deployment/wsgi/uwsgi.txt b/docs/howto/deployment/wsgi/uwsgi.txt index bf869a77b8ac..dfb04eaf2a70 100644 --- a/docs/howto/deployment/wsgi/uwsgi.txt +++ b/docs/howto/deployment/wsgi/uwsgi.txt @@ -114,7 +114,7 @@ Example ini configuration file usage:: that contain non-ASCII characters, make sure uWSGI is configured to accept non-ASCII file names by adding this to your ``uwsgi.ini``:: - env = LANG='en_US.UTF-8' + env = LANG=en_US.UTF-8 See the :ref:`unicode-files` section of the Unicode reference guide for details. From 2255b5f8a7dfc9fbcbeeb60e76bb94fd1f8a8d87 Mon Sep 17 00:00:00 2001 From: Kenneth Schnall Date: Wed, 27 Jul 2016 13:50:31 -0400 Subject: [PATCH 699/756] [1.9.x] Removed extra periods in docs/howto/static-files/index.txt headers. Backport of cd2e4293cbf3416af6c478c5aaab711cae88892c from master --- docs/howto/static-files/index.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/howto/static-files/index.txt b/docs/howto/static-files/index.txt index e9a7c7c0a1a9..f01e7c05a54c 100644 --- a/docs/howto/static-files/index.txt +++ b/docs/howto/static-files/index.txt @@ -77,8 +77,8 @@ details on how ``staticfiles`` finds your files. .. _serving-static-files-in-development: -Serving static files during development. -======================================== +Serving static files during development +======================================= If you use :mod:`django.contrib.staticfiles` as explained above, :djadmin:`runserver` will do this automatically when :setting:`DEBUG` is set @@ -111,8 +111,8 @@ this by adding the following snippet to your urls.py:: .. _serving-uploaded-files-in-development: -Serving files uploaded by a user during development. -==================================================== +Serving files uploaded by a user during development +=================================================== During development, you can serve user-uploaded media files from :setting:`MEDIA_ROOT` using the :func:`django.contrib.staticfiles.views.serve` From 3771019de9681260a25532498fca710f8c6c335c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 28 Jul 2016 18:03:42 -0400 Subject: [PATCH 700/756] [1.9.x] Fixed #26657 -- Made GeomValue omit SRID for MySQL. This fixes some test failures on MySQL 5.7+. Backport of 9031a4c13bdb1ee4d0f1253fcfcad73d26c25740 from master --- django/contrib/gis/db/models/functions.py | 2 +- docs/releases/1.9.9.txt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py index 7fd8cf112914..8ae4f6c26002 100644 --- a/django/contrib/gis/db/models/functions.py +++ b/django/contrib/gis/db/models/functions.py @@ -79,7 +79,7 @@ def as_sql(self, compiler, connection): return super(GeomValue, self).as_sql(compiler, connection) def as_mysql(self, compiler, connection): - return 'GeomFromText(%%s, %s)' % self.srid, [connection.ops.Adapter(self.value)] + return 'GeomFromText(%s)', [connection.ops.Adapter(self.value)] def as_sqlite(self, compiler, connection): return 'GeomFromText(%%s, %s)' % self.srid, [connection.ops.Adapter(self.value)] diff --git a/docs/releases/1.9.9.txt b/docs/releases/1.9.9.txt index 8e90f6c8ddd3..a777b1702137 100644 --- a/docs/releases/1.9.9.txt +++ b/docs/releases/1.9.9.txt @@ -11,3 +11,5 @@ Bugfixes * Fixed invalid HTML in template postmortem on the debug page (:ticket:`26938`). + +* Fixed some GIS database function crashes on MySQL 5.7 (:ticket:`26657`). From 5d6526614bc5d480baedbe8d96ed2c66ec53bfcc Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 28 Jul 2016 22:00:48 -0400 Subject: [PATCH 701/756] [1.9.x] Fixed a typo in tests/middleware/test_security.py Backport of 0850236a8c3647bc3c239bd34afae0488abe5c60 from master --- tests/middleware/test_security.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/middleware/test_security.py b/tests/middleware/test_security.py index 3189c215b8b5..efb4b152a9b2 100644 --- a/tests/middleware/test_security.py +++ b/tests/middleware/test_security.py @@ -110,7 +110,7 @@ def test_content_type_on(self): """ self.assertEqual(self.process_response()["x-content-type-options"], "nosniff") - @override_settings(SECURE_CONTENT_TYPE_NO_SNIFF=True) + @override_settings(SECURE_CONTENT_TYPE_NOSNIFF=True) def test_content_type_already_present(self): """ The middleware will not override an "x-content-type-options" header From f65d432181b332d5f34bf46636b6b492a576c404 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 1 Aug 2016 13:55:08 -0400 Subject: [PATCH 702/756] [1.9.x] Added release date for 1.9.9 Backport of 95b47c009b7d3af4aeceb24dd49e6a442faede4c from master --- docs/releases/1.9.9.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.9.9.txt b/docs/releases/1.9.9.txt index a777b1702137..ae1ee5e63dcc 100644 --- a/docs/releases/1.9.9.txt +++ b/docs/releases/1.9.9.txt @@ -2,7 +2,7 @@ Django 1.9.9 release notes ========================== -*Under development* +*August 1, 2016* Django 1.9.9 fixes several bugs in 1.9.8. From e8bb7464c562388da48bca04c5996fe16a0c3619 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 1 Aug 2016 13:59:38 -0400 Subject: [PATCH 703/756] [1.9.x] Bumped version for 1.9.9 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 90e16d9fe271..256ef847be87 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 9, 'alpha', 0) +VERSION = (1, 9, 9, 'final', 0) __version__ = get_version(VERSION) From 9c97744a605073f4ec0d1f8bd45a536c558dc44a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 1 Aug 2016 14:14:33 -0400 Subject: [PATCH 704/756] [1.9.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 256ef847be87..227c6fab7619 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 9, 'final', 0) +VERSION = (1, 9, 10, 'alpha', 0) __version__ = get_version(VERSION) From b2fa5312bb1c11db80ad55e3e03b2b834a4a14e6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 3 Aug 2016 11:08:30 -0400 Subject: [PATCH 705/756] [1.9.x] Fixed a GeoIP test failure with the latest data. Backport of 4a696bbe13383b14b2762cc5accd45849e9dcfba from master --- tests/gis_tests/test_geoip.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/gis_tests/test_geoip.py b/tests/gis_tests/test_geoip.py index 8dbda2c5d903..e95fef44964e 100644 --- a/tests/gis_tests/test_geoip.py +++ b/tests/gis_tests/test_geoip.py @@ -134,10 +134,10 @@ def test04_city(self): def test05_unicode_response(self): "Testing that GeoIP strings are properly encoded, see #16553." g = GeoIP() - fqdn = "duesseldorf.de" + fqdn = "hs-duesseldorf.de" if self._is_dns_available(fqdn): d = g.city(fqdn) - self.assertEqual('Ratingen', d['city']) + self.assertEqual('Düsseldorf', d['city']) d = g.country('200.26.205.1') # Some databases have only unaccented countries self.assertIn(d['country_name'], ('Curaçao', 'Curacao')) From f0bf535f2b6f1691d32107c78cacb6b0fa63fcf2 Mon Sep 17 00:00:00 2001 From: Moritz Sichert Date: Thu, 11 Aug 2016 15:16:46 +0200 Subject: [PATCH 706/756] [1.9.x] Refs #23960 -- Documented how to restore absolute redirect URLs. Backport of 08b8c4697112e8dae90e72afc7d85bd31ead0410 from master --- docs/releases/1.9.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 19f7795fc4e2..12d3c67d326a 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -792,6 +792,16 @@ no longer include the scheme and domain part of the URLs. For example, replaced by ``self.assertRedirects(response, '/some-url/')`` (unless the redirection specifically contained an absolute URL, of course). +In the rare case that you need the old behavior (discovered with an ancient +version of Apache with ``mod_scgi`` that interprets a relative redirect as an +"internal redirect", you can restore it by writing a custom middleware:: + + class LocationHeaderFix(object): + def process_response(self, request, response): + if 'Location' in response: + response['Location'] = request.build_absolute_uri(response['Location']) + return response + Dropped support for PostgreSQL 9.0 ---------------------------------- From 26ccd2ef165d28738f51c41203a8c83ef49a7584 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 12 Aug 2016 07:29:29 -0400 Subject: [PATCH 707/756] [1.9.x] Fixed typo in docs/releases/1.9.txt Backport of 5eab1f6f8348497e87c19112786efb970e41f36e from master --- docs/releases/1.9.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 12d3c67d326a..38915b8a61b0 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -794,7 +794,7 @@ redirection specifically contained an absolute URL, of course). In the rare case that you need the old behavior (discovered with an ancient version of Apache with ``mod_scgi`` that interprets a relative redirect as an -"internal redirect", you can restore it by writing a custom middleware:: +"internal redirect"), you can restore it by writing a custom middleware:: class LocationHeaderFix(object): def process_response(self, request, response): From 690effbcc3a388d3c42d07e55973eec836097d6b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 24 Aug 2016 08:58:21 -0400 Subject: [PATCH 708/756] [1.9.x] Fixed #27115 -- Corrected a screenshot in the tutorial. Backport of b40d24960c43091f792dd711e6a5ca7a708b12ad from master --- docs/intro/_images/admin13t.png | Bin 17052 -> 16997 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/intro/_images/admin13t.png b/docs/intro/_images/admin13t.png index 382f89b9584feea3bdb766de340c21c28385d9d1..377172301f2ad9d28877c02b748bfb553e74ccf1 100644 GIT binary patch delta 14868 zcmZX)byOTp^FO*sa0sp;xVyXW;ClW>N7o6)z#gV6$-Z#0vAsM5C2?82Zw-yg9jl603>J{xDXoOK^C8ZZdH&c zvYEibKvI9>TDC==XdjUzs$fbweA(_;TgmuCV(SmT(Qo?ms8RD~5}+IbpmR<%okF6a z{mH^S^Be3Cm+naTKQ>WZ#mmIz-WeH(kwgp^6oN;e$A6BMrF82|B^{6V8T&{a>5c0P zXC#R4MgHHzA4dUJ*80Tg=HBaT#1~Jj`>AuETK7UpxjmzVYEnYdzFI__+D{sV zl^7AaCaYZ(Q#9FHmA5mEEYo6bR3?rA!Nq0RQ$hPP7 zIM1Sfs;yo=P|FlK!u0$=H!6E?b^9KK~rPwR9N|E6$Fk4@Xbt^TO)i+8Joc+PmA=hZC$o3W~L7P<@YpOqzzV(}NA| zlAS~D6oP(+j1t5`+mVjJe;doYpKSr|ZRL+2q%ssI>!aHx`}oy+M>ood*Zyv!U^R58 z3M80v1q9HAE+ZSUE~&yWqe{?2rsaZSV-0ZdwSUmaCA3Yfro_?gHP(T+<(n!l3R&KX zP}E>z3_Ra3oPe}dJ0Y!ba@)R@2LgLr<@Y)D{4P!q;@N4vfrD?|ytdsLs+bNE2{o|uIKFB)$ge?t9Ub727N zGs%So#7M<`=W6w=8^zfeFuv1XTsIWB8bfJ6{HtXk=v>r=ccuI(9dw&KdzZs2B&*ar zeY8KK0@LFUsf#WGEz+m3Z`rnWU?)A^|J0<%vuUn)j5!hb(eAb7!yX|en+#k7@T)U%TmWt**Jkdjx6R0e7qu3`dGn3(WP)tnKu_>IobN54 z+upQaV{$W!ja5atz!QStgi?Vv8GlfB!+=#IAm_&69cW?MyQDE<%z^2*ha1@y9RAI`M`fr6JmczHabvy@G0;*_ zi;hwQn)@1>x8ml*lE)5;xgS8DJ~L+mnJW?{gkVr-rkGOB(`_{`HiFb5z6Cx$W|l{X zEI2>n1a#}d6@w1N^-BX4dZT7|>9y((kZyoQE6mKyzQt_`8yvF1ZK!K#OkMHF+Dscj;bQ2-s(8wXIrhw zq;513pOL*ePDGH{s)fTr4gM^&v0GT^$n98yju|SpT4i{?&IK#I;DbP?ypyvvZ3FTu z|3$HX#|by_WVyW5CyS9zW-Xg{>ibXM&-SR@$WDbg%$%#f#Q35XW?m*d8z!5#S}(?C z3p8((C5BeSg_oC%TA5q<3=<3YK#|5;CYaiyrbq|6dGdrq zrSeTe+D$?~3keBhL1HrRuNl12j!lxj%n5TmvuPoTt1$z zd0bsrjBWS@g7q+uSh9F_E5bHZh1Cq?ZXIzgn|6q|>7`BrOvW4jme=r>ZpB|!`eK1) zHH0R45zqcmd+MW3$fcL6;)1Eun-JN_N!7|>`o{;3l*TnD?E$ZGEeW2(nbOb_l`p44 zHmT9XRbhHZ+e4-3Ss$D_JBEJD^cPOn^D4TF1tjMWfy+PqOalbPIV|);psnmVZ-{Z9 zpj08snIA5c=+)Ej1`;fTK;{L&hP#0pTIP!)QE7xHJxRikbf3w0yIL!(@5K4*oWY_; zZbfWIzLWBnwt!f}GNwP_$ou_U@@oFO<(J&lU2&g9Rt->PRaYJ!_kF>0l162Ev7Md! zjsXz$!j4l6JxCiXdhJPhODAIP2w|WNuv|y&+88qHbD#H_L=SHIp!J3%4<*wl$ncNW z6vyzt>?PiXWN&(z=)|H-o=@ZhE8PDFe6B2!N?I4s*C%AQoZ_3gnE27Cy`o>%@0ax? zg9HJCIu)Ot5iib+Tgs-5Z(aO}1{ahNHwu+pLEQ%e!A}MqUaK#AYXDstCu?U+UMR@r zrd@y|?GQe@GjVC4m9d8O16wZ(Xg;{7e#(^kUB=fzOonzZ-9L-C3r-M&4dRYQpGNtJ zDJ|UUa}mY=CJJDP8Xqff{gXJg>Fi4E(9XJ+wRJAQ6ng)`_2=J&7dqnb`Uc1WdCUdb zk9uw&A$roLzvc_8L{Mx84-s}dsFp`oS8hE<6Gfyzmf9BZ`_d zWwuCs0GYg`LEXfZktG|yp0r}OHY$^0S64`gP)Ymqr{!kBu%kFU#p|QToqHFHsIFND z&!4%sv!mhpYpPp<;*c%ZX#Z7#8ZZWXbHf*tzjwA*f|1l+t{Q*$sr4a%|NnL5I-l5x zCCJj|=QQX~Lk*~eaRr%@?d1CKi^Ysfg9xfhf>3cW01UJ;6M5HRVpDNR`Oy=vj3D9Cl zbY3MB&SAY9n-lYdMxOm#xui(^R5x(1%#~5yZFZ(Ssr<(jZ`GmJP25Y%UPnV@HU(8i zNc!Moo!{BYtzQh*!Tr8aF8j~3T3HsRVyFgV%csU|<@7pUkz9VXu7qnf71w>$KZE#n4r>FbjZlaVKLQg!_*TAH+|jAh zRq=(J%7|&Zl73a@Ssp9p;h3+deS0n+dLH7QV<#UT%)gE9C*^&pf)0$-B4AYbW zT_GAz6RW0+&RuL{hVg=pP4#pjsiC$5+^Q&|js?vcV7Jk!;kcNw!hQEzA~p+gvK1ty zvlU#cm)11neO1sruM(K<{b~}FnYu<7YL|i4WrtxP`5~LROc|q(N!DT`Rmz!&pl|B} zlgR2AmQ!S3{#T;H6E%byaM9GM2`YhAR`Xq!M3tw)B8GU-z%!*rRfQ0IHq$CuG(c(& zVj{t^?&McGtOgp6U?C7B1d93L931la-;U2A(dqut5`BO=qJ+6GpB8Ju+yj3 z2?8O+)6RZ&9MB3A$ZO6?2-;dEe%_FpmzwXMI<9e@Zt@{clAeVll+?xM%b#VAF4wSe zIa79FG>wgQ4X>OrmI-8^IJmvuYe_`TsszDG)lM_IzWV#>KDjhc zRzGa>T5#dT%>4EKT#*GWbY*3$H9o)8&SO!1Dq^qT)THJ`;Wn;YqrQuG=JO{Wd_Qwb z1lRTckPSdyV>2oW+wv&@JhK$1D?={%>HX#2b_O3pL}esvMTevI8K42qnL2>Vf+wE>xG)rU zfZhADtN6rp89G{x3hc@F)v*vbK<<+|a(HKJ8^dwkq67PPOFSNYM~r3U2`@tI*LmZ! z1b^Zy_Rc$aY^0!6Sk521Rk}x)ldoq9cAAsFSXOsv&mra$U+h|LJ((|#vGA)!S`e&) znf2Z1y^4o?u{rP4Ex>w-oEtxBgaXZGfr;w7l=g~>LoPlBEc)MeHZREmJFn;7<6#OU zCkrOJn2$A{9b|y8TmN-2Aw-10LlS%fZSXcp*aj#?O3U6JRCaO<#!A5G>5FQwwe!8& z9o?#+g2;fs)8-eRH;JXcS84+vYcU4@i0RJwk& zjkA2_-uEmDJbU6@7MNmn`*-)p+m{cSU!f;b#;-LB3FM{3mSb z#YF7P)0m*S`cX~t@ZtJt-3b(`Mi5QclJd-^@XI_L>DjN zeFr|HZvFi`iX#se1Gg-)dF+RVVjpZ^+MMEQHnmX%)d1~vZ`6#<`(x{a zBi+8U7yRj}6L3F;Cq4?SE57EaB57mfxE1esIlvo6a(R3_tg4Y{f7#%_KM9{-gLEix zGW;f=rnWzsBINV|wEoIAL`Bds z^y@EYs~si!Vuh!6S*81EuzksM2)5+e#|AVqYmJ$RgAY0aX9yu2@F8XZ+YPvj68gsZh`Cknt|VicT?KoAK#EbE>(Z^#pz)qAgwW_i$uU zgMtei+Pv3b(}4!`I7UEyaVe~{TomE;_@yWN^&@1>6ER?kq7$>CN-;v^wIP@U}=?Ud4L(8WcdFr4mFj&2bv0wUE9%DS}Fo=%;`xk#PO9QOm z&g(y}CfIa2%Zk;emSFdvUL8NQ8T?&L!}Ac?3+;w{8nYQ>#l!y6`K7)&wUKi zHT2eNsGXo`IACB}iOlpDE-ON3b~Fy-_!D7(--BY@^Q$sA)tWKcu&d!U6IQ9D4Z;~6 zilx~ZKaEV@irx7*VwO5V^uPh}v3{MNOWD1U`*qZjvcGD&MNwZyAmX+O`nW-49D;`+ zfN9^=`=uKV6{VAxjl+qp&s6%mDRL>*1P0R<;#OsHFT3U-X0bb*lsW`OpBVdaLJG|L z{4PHtCf-Wwsqq>4pU~~5sujJS3irrB)e~RV_is6SyF#})abrRPl;y;b#P@sI*2NZ& z#v^U-twL1Rz7V|Nlcx{eg!2n^@)R|o$4)?g zDEU>R#GPdKlDVJ6)bh?1ud_;B^$;`Kta?fWTTSB^lEBOpI18d*aBw3{@Fvfm8{VlE zT-^iE^5bbOlcMyQoizs_0J;G5kH0DNdJCJ0BkGq)9#}KetAZHt__Yn<J)~j zSY5~xNq;x*@6is;qh}X&d-%J>@LAc+2%(2{h9GWcT)a;>eE(1nLSC@h&YQY$$gqYX zpWIFpv-rJ)ngqz>{rNNYvsi#krZYx!z|d!HCB$eehB6cDHcJ7c?)FkPh$V-41Oeu| zEWAN|T8tssO^UeByW~X?IKtrh`8fd=mb%sIXHr85qlTmX9&FC;Ue&K?_3Sj6)H=K6UqLlo^|4dSHs>0q%qU+}=0Qzow^tmy(x8X>;lOE6_b zcO@uuuiTq!3gF5kGjGKk!u8 z$kvo&z%o6?pRRz<3Z6EOJYgRLB~hn;94+hN<5#Gt39y(#Xk*tVd;dDC(bnZk`22Vz z<0!=5IWmcJi5SZGRNxu!Bv~x;O~~@tW2}2 zDz@8^WNhutsqNnNk{+WBaF{w$)Ngf1BaBjbp!=2vMr-odVFSl8IP-6&!>7)nOAM4z zBEfzRwYK;L$Swwqq$8C(LOS;*%Whn6EfSGni}^u_(qV$AJZu@TNpco~qsj34@wP=f z8N^He7$N>o;4*^Hh%DTrqu4rLI@x1J*B`#3G@d90tj`p@eWsB0k%O9(5j&uh8O3;XExvT_BOvz>nBh2}EY9tw2J#>RXS(lb5 zErF*oh{|8OSv0AcHB_-I3xkGGK4N~6DMB#Lx4W5=c2#mK;7k;4>IAi-Bs!Dwc~o%< zJWikdJL>#ISyaX+TvP=FE=d+R1)J))Va|MDVQ(fy^uWze_`$m<^p<4jt*`oBeacJs z`(SC@Dh=j8@%K)vdW_rVN-U^%@#mRJccik9X zdggG{t$JOeXT!hO#_T2?H!y*13ZXGZ60#bVkg=QaM2{H9#5Je zNc!=`5!~S!XseAs|B>vnciZfM%Iv<-9InUO&#pBaU*j7aGlkr^(}8h$d4fgWmE+WWP*l|=6winR*SguS-RZxBN7+ZP!Ivdh_O zRpQ-h#c2xy6;#>H?j*=8JN_rIq2sg)a=^Zs{J}XoHi7(yf4yh$KVDWK=NwWuz2ycN zqvY8le;wnpFIaP11vWf(XOlfg66qdykaM^eH_31JT3CMgo3>v09xq20lOJ8EoqoK3 zQ7zdG-)U8g@CuDBesLdk(y#1)bEf3V8PaTtYulhkFB5){qnw(dj#56WlZddHz^%71 z;NkbIUw+%I@=pDMI-feEHngsCVRf}_I*2YHoXU-YWJ-|hvTas_*{By2XUqQxvLYP) z(59fa={dvS5C!gZz<-D;u)(i2PF6n`<7yr|_QsVvoA>|84YA^Q8fL4W!b8FQeY;`u zyz8I$(kp;o%s${2O3|%R>eTm86S(8=rB9_uu`Tuf8{|d=y<27eCPfy+QtdvmEi*Znk-~DMU~?dlYzBkuJgucUKR`8sD4Xz ztBgY~mCq0Z%=cAA@j4NSCuNd%Xq^-f-(->py_^U}&5Q#eHm+!S0;c~?X_m^fhufa+ zOCc}ANb2j#W$V~Gs&H-p%1^y>tISYwib4itX>8mN)s(gNO$9Te2`+Yn5HdTTO_lw# z?H7Mqv(V+2eK{3U+wY9m*k`h{Xre^!_=y1h1bgnKk43Bsa(eZHLF$nC9Y?(S; z_sVbd;bdkI73UNoJ&*G zbxQ?muZS`c9wY2-9tQrT%rNW2UCu?Ul&zu>XZ?!MJn5oxJdc3MkXFG&Zo*W4B)P{B zB>5QPx*X1D7a0u94a;)%(;RMMV*>e~jp|ndzWvsFNZ_ML2FPj%L8v&7(j;2%Kfrje zt7%0b0bvSBCy60LlnG5JWJZAzPZaK&nh0TzvcO3s3;wW*4|61d z1@2kGxs@ProY#~I3%-g(=cMDUR^cpjtKmF^7mHM>1tSh6*m79d{CBAVAn%#3fRbzl z9qdn#{oLn%j&lLOPZJ)CtX*JO#OkD@*LcMQ+|rZ`i&INVbS2{)rOYsn5Qh-@!NK}k zl=3qKz6YJfc=D_IY&q%W-fTY#QM5{1H++|#Wdhn?%=X>=ZgtEXfAE?8I83uNMAZrr zeR-+dq#ADs^=vsc#W8vrA#-{Cy(9X~YMh^D0~>SM?={<_8bxEeTEMPN*r5)qQD5!X zJ*DqlU_Q|kVRN*M#~&=ri=^zVP5rv(1%s%$@^?!6=HA($C;+7ggXiV7`yIHgbMx6r zZI9NfpOoH26dDLVUcvGR3J4~+cVK|*%F&Bilt{E%ux~K`bP7snD#q^1bU)5E|B8kU z{dD2M!xQn+r~}5iKBd#E;!m9&s`vk^{WD>veq+r3s513rAc##3*+oHt_aL_ubGzuE z&^leFk;9h^2UN2@$dE60ejb~y`*B$>`E%0P6I(m8;HfXNT4jU{{%g~{MgTWNjc6iS zotxBD&k>W=d}Jvt(bDXywx89ccR%~SZ9fdB!a%{t(L>`Xw+2p%l*+D~Eb{)`JMps! zG@71mn0}@$ZE?7R<71yG>OVUXS2VQfh1F9opDq149we-Go=E@Aqx&=aVs>@5P@GAM zA-5-$9uRSEmc}LaGzQEzZ2tiALH^)%K_K_LY$8X11T#N@dXiS93|?q5?{;3EAbE%gW*Z6%X!Ql?m3C`J~LS0_6PlG{rA4E-C82CQ+(rp^_-_>@5RD@ z@w&c*R+Xn3xsVfD7BdVy2GmrJ9sQd{r@%Nkth+QaATs@-we)aN;`ehI!fph|)@;;l z9J)FA&vrGx&pRapFj{cJJkjwF&TTaZ>qv}i^nt*r!`0}vv64S>Vso-=P}vth;@8(U zIG>`5(tu(2U@VpLPX0wo*V0v3x$9AJdBP6^NS24J@x6;fw7ae<6 z5RTp>U}BbGg!{7i37;{`Cg6Cb!UYYCJfa|dOQmR?dZ5EgH7P=OEj7);S)!S0Dhz`m z6qbhJ`q2KS=e>Z_)maRqEVWsN+EpF{fpdzK1ACgub_1qCajrC_r70gM)U1(az*_iH zAfD+Tj&U$pDI+%|56Uf)-Waa_KROtUcs~Ob#wP&9j~DMlBn)Ly{nnw}=u(>jD+b`! zCEdzX1gLe99pcYIX{9>oL99xS|uhAltHW4d(YwggWLe^^`OM3 zxs5uD1pH4MR7@Gv_;2qiJT0>c^msZK$q&IlFr$Q|PECG+jd!MRn7hDt^^qsjuCX65tnE=>wT8g!j?HNR$=^^YaEarmhkGygo42AhEe=O`+AUdboI{j6qO+CKuKQ6`}y$4_TA1_niU$+T#Ji5lHGxlv+Px zOKl7(OZOe~PB=b@(E1}uPDUF*U3ktpWKG5ag#vqlI^VJ-#x@B5?SAl$IGi5(;4A%N zzV{@vDL*dwvST-i%M$fAyUNTBcSGv5BUD^#PJ$vOaOKTi2V+Px=IY(5wp}WV?J^Ku zl`28iIUPxd4p@qxI*6BWRf?K~dCC;*lli5|KM9aXbB|(ucKmI=a0&(LK8ziWnYX^3 zDDg8L`%lb!SmoU6A^+>S`gPXzRN#y88uKK#vj&Q$NTxv8C~=+G8~#8;2!I$BVD;~R zJg&V3-wR09@!`k#WBh)69D_v6M}GUeb>Scqm<&4zHL-1jx|U%0q1q_(Z8i?Vq5}P5HCXivlbcPN5OvXM}5i%voUU8Kr-d3#Q1@J zMh#YO2o~&D1UQ(`)Svh==G5L^XZ$StID%fTohNEhW1ihKA4x#FjRa|ZJar%kjI8cv zX>5HI+n|kNsV*TzXimN_x6}nVq^(WmD2O& z`mkA%h4#v16`HBz-@~w%p?;{jyDRNGV?1RQ3;x0R5RE5i}u=E;-LIFs};3` z*IGKL6btav2<}QR)*d4F!r>1|xBr3B$h~|9gZf*mvxHaV8MZog2YBwH%ESun!ByHo z=e|H>qE%3dO%2^{vKwQUBInD=MAB>@jF+BgZ%n?*_}2oZI# z*7=`Il#FjFeoWrq$()|Ol8oKxzDjGlqV}p*L94%+yx#Pxm44^#Fga6=It-O)Adu%R zj1YzsR3HJpjU^=RRB2$j$cBEi7VW*ipoiihc_Nr)xrfb+zUC|EU&ZZ5lA1^_@h7YB zx-vw{@6~zWpVt3|3Ay=<`~Ef~dZD3i;W8jCefd;5u#a`WK|b}tfsE4b;M&A$!ASd( z(T8B17bIW-&B5Ed^_tDht87rhoIOMaxPqJ;)>dyh{uw}Klrnxn-6;@^QBlHFB0tUG z!C_y}M#5gFHF%0jr#V>OM=5-x*ozF-F+)lb2Z8k?VAcTqN6!})hVvj8y>}HC>n&yA zwov&ZGQzTppV(e7{aA)z(+Df&pSK7$kO?S6J7ys_nP&-QwqxPJ=@*b>s+{UXNI_P3~qlqb9zufI(W^87UQch*?rXTB$EF|cetM7q#(}qrx{pLy0Lkn37w<) zZd9uQ3j6}IS&Sj2jedf#OI27#jc9xs6VbD(QTtYEm;U7d?z<{r80N1_w8U+@TXk9u zTcM$4za|F1+?ma|_`xdv!LM2!7M+1Wdu6a1ZwOn42Cs-nk&Wm{dp*>kF(2Bxio2>} zPo(b>^J<99$?_9}2$4wpdNhj26IRMCRL-c$c=G0`y^NcXo{a~w69f7ZeQPWSZ;@Ds z&DlplkRrH1X)$u9aUa%>EcYrw%=ow^vs7-E5+-a0at?EbuiO z(jnDRc(Bq3LT(hQskWJqIG8=&m(3b1u{kEo`gL?Ia!_^x^BnM<4T9Ak&)o(?pG7&Y z>Ib{S4FWX2_Kx2G_Ak-fSx2@X35?gRBkB!q69lom(j`LRg9VwfD6GHN4*I6hKo0d6 z$AsMxLe@gy`}HWm6Ik*5E1qB6j zeW>8WS^St{hLS63g2|?RLawD}#hfVF9GZK4l~)ZbD=Af5D_c3`Vb;GU%n2cH$rbq) zBT}Na{aElnhDh^FT_m`Xl;S6O7r$IY*q))R(AAUMXEYvL+6gD>wB%MAsJ&)FIz$d_ zw^VNhU#7b}(iznwK<8FlO(pjQP$V zOlJN(98!D~T+f7Ca;$LTWHG)mja)o@5#L-U&9X-{JX~@3g(t5p@p5^1P}|PeJvI(F z@~<3eJ#Z$XV*w|>C#@$B8*M?0sPD30o2r&;T1QM1TxGArQMenI*IG`2+%W`rMBd$E z`raV#v*-H3e5r>A*Qhs$;z~_&_A^W4QxayKDu#w?Y1qiug;3JxxHtl?J8HkuM>b?? zh)sq>2mQo&B2`>{_Dgt)d^xAekte+PW_fYY+3v&$HijR%LW64&`#Z_!Aho9N{=Mh@ zr;&scK8{MPOhc|t#zILLBO;MqJZqp4zzWmD*uz6Zu7WePRY5pt69ErtnG``XYbfVf-xy9 zAU*Am@?V3r()bJClCJzh+iMKOIxD|xWvRe@JLbRpndhdxI&iVLbe3dzJEKdulAQ44 zQR;Qy${~#}-HOdAZwzbk=&-K!WuC9)uQCs|A^T=E*GCM@g(M=~MeZ=6M|T`pxSvp` zKkN|6fxQoJ=j+?Xg$$OQ0nvT~qr$s4;mM42iYRo7k%`H`$ix{?q#`_>qAMu!lA1W( zhuZkzz*|zP5E%K35p336m$xo&-ToVK6@djJLRCn_DZ91mnO@u#D2qp@cnyq%typnl z{J4=^2PVyxd1EVO@B`hscyk@k9e-#CSO75?G1pJxKh`XzAR1>D0~IdSVVd8@3&{*< z2ms&TrN3je><&@4C75{*kli}%deSIPu^_S=;n?{H_?n6wR7j60m6__Lm`wYgZs`O9 zXI#%0Pz9(w!>>BZjID-s758u<1iGOrH(}PHoaioQMGKW<3$CYS)rw}_q&@h?sF#SS zBr__ID!SJFtz;t<@mTJ}t+J?TXPHhp=w7bofw_xu8e!fnNOZJ9s;UGfmYyZ*2F^F< zLv`Cl@HI+AM0MWuq|lGhv7#mp^c^A^Xk>NmUeM7<97y)Iixeho^WCQ{O8Jm$!5tOB zOMA?(hqzWdMoqxf0abI!wfJpp3-f_jt*xz2mN2W~^%1XwS&LzuJ*1XWzjvU2LZbb8zWs%W^?**7kpUb5>7l%4+=wZTB+rYl*=|3I8lyI$=8I8?oFn!aq8%e7S=Q6tW6>S0M(tkFpH3H(yO53?kf& zw#+OGO@Y5A$%G!q0Np~V?!HSyB|unUeVsL4r1v?Vdnv0a`>${0Um(((=2O^;$;g7@ z7gZR62s9U>0P(tyoZ_YRmlKv%YCelDzQUhh4u1=TQ!og-9IY?=Hhs8(?;y8|_J#0q zM`z3hBOleCU(Vj1wL7u^6|N8M{RQWB3O+ew1;=ko98f=PGd@*tC9BoEMHkIwiTic^ z5}^K0&!YQDpFK7XUkHltKOwA@UlZ4T;H(bOzYnYTF9sYO@5$S~u6bQ*T@|q84lt-y z{aJm&4mmE}0~K8dDW@yRHvu~z*YUPH-B!O=;oj{tigy^Y=5+WT{H=L;kM95BabXlm zE$eZ;Gcf6F(-Qf-O<iCbvl^c(xV-kEWXb+{b9 zczinD+C(mL_7mTJl6s~os$m36M;^x8nDS!8Ty0$LH_@4Pudnf0SUF=Fkx35h*&}X; z4*tHFRLxtNAP6gp%+|kXlZvR0oI;Y^JHf)Px4>0ly~TqRW_P|<{|@Z$tVE#tn`5&n zqKjRXrO5k5ZPYS6_GC$ajR6{Lhj!yrsrctE|Um z=h7%_A@M>14Lw7~WfqD5R1L2CGr>K9v*^csJv1iAk}vJ<=xUMlnE602RK{ik_0|55&blN(`bI~!E9sh@Eas^Xgj7!o+=0mv0sW>Us>Ek5`ixK89S`J zW3vjxRS}ksZ2e=G?*uSB{-b3J@~xsEybM#X)-vmI_ZB^T7vz}Jhb2Jh5GrU8`E-gImk)H4WdtL(jV|eTQZRT6zfR(^ z+OO=IQiBEP%F(4G%avY6mR`3-ec(EXmfzaw$Mu>iGWAAiM8g#Yk~0W6#@KYciA-ZQ z8cUmZ$_FMv)nOXzEvkdY4X@D<+MDs(S8g0)$D9jYFV#bfqgTa$?;A|Wa33-=a|352 z%;ud2-e%Aa*h(UkI!pn`2zDH#7>5&me-7vmth-zy2qaomde7<*u;EGRAB-XosOczq z1H5m>yAjC{itkITvXEVkEbWed-M-JE$ZKEo5oHht)Z7$k4N-@w#X;VD4=!EEf29bB znh#_GOOpXkaOqkbu$Nl%a7O4=KO^;P!bFhsF#}{RWL3kysLl0|I3ld$8*s=*p3;W! z14uFA1OOR`ZJ)oS(=kUzVg?+Z+UMZSI(W4)+A`2Z;?5%H1B+JVxEGMRFf1O@C=AUJ zD1G`GXyH`FsH7q1lIwxI4ervrOyFniC0PmcHoP@Jniv`3yfsf<{h)EWXC3VvP39+^ za8`rGBr#KG@?pGE5;lWOKJ?_$RTM{ngQ^1{%c!%DACO@b*SeA#B_I}+KqghRK+@6D z$)~aZA?nTw_+MzW4A71ERj=xXkoVPEbSa*qN?TwY9r76dWqi&QjNEbpjKp062nrR5 z^{(JLqJY9q%OJ2`Zp>caU!tj{L~qf=z&}R-@0aKIO_%}hEgoMTQNu6GW8|XK^ABce zSd#a#ByA8VH^~4(AuvS*MOc$$Y>AOj;Mbz?x8NpI01#HZXTF3asXF4^A;j9i$?%?0 zn{HAFVsg4-myW3cd!R}#o$;{Ht)H)awLvib*6{-_KD_;hCE@XYO^tQQW5zJ|LXIT= zzzHCk@$FyyUj5gmecVAF-6ca;A(LF{ghKZ)?(w|H7FE>bbR?mQvi$EYprgy6B#HZ3 z^|ZFsVnG%lMGOc+*-rh*U1Sh&<16mFNJV^F zX7F2yF9k}$)S+*;{}KO>9?z&n^?w{((ErhC(X@{VvRNZFMg~_Da^U_y3GH1<{vmN8 UlwKov`v1`tK&rrMY15$p2RLYz&Hw-a delta 14924 zcmYjXRahKN(_K8cYw!SB91Z*Hf*lSmZ2v+Y=RZ{nSSVrb#f7`y$*`@HA*X6L& zgx~AOEu#CU`R3?W6GAgcl%Jnig^~&(KYU5J3$9tnn^;z1RWw9BTz2gp81p8!WuTGq z6L=8z40LsMop4AHX=#P?_Wj%8d-;j=pmHu%(@3{zYiJN#mQEXGlpevc_<>PHk_=?S zUbQ{BzrKF68M86G=GPqfW~V};%xxskdM<<%Rp|Al?B{(KWKs6rqOeYghSNcmz^2iB zlFl=up~~xQPzSh!$C_l`BAh84p z29J_|c7`VV{wr;kIreh8V6woK+{$#;+o#`U6&eXeRmZ7^ih~`TiVjxX55^3|qJJpz zuxGCsG?CatG2cTy2`f*IQt2&DzFoyN4pfPEy=X#87xk7aT_`MFcKTVuWDZQx zwHU~PM-|Pp>H&#_{EGYeNJA}|+fR2^lzgKt0TbneO4kUF)~OhO9%u;svMR8B+z`XA z`zIhR=|Q?{-X zZ)VfQ!eTzMkpm}xI(B;VYs?BedIGOCcU_hRnW;>L8So49v$~s_k_;AIvpTerL5!I-qCIxpR zxD&pcJu#kaTxR?^`nov3;&U&QnduR-$F7_18(1i}|9JJBsKkNL${ev!6oBh4Mp(bD zhbgG;tTV_#xO2sEaYyWnI68ip(u^j6@0|Tk9pTBFTh%%D8_U0qE^dIxPr2258+I@0 z36#G}Wp&)Ih&OIi7KiCB8#OF-q^!Qh#k_vBHg>J;+&L3AV}sa9M~iIczFjAfN~x=o z6CQpV7VJME{WI@L#^fXAJ_jjx*6Nhs@GVc@05L%=Y{I8i9K+9ih0&%3oYoTm^yl!ZnsmaT-%ziS#Z0CmANI#1%JE!CdCeP%SHI=8~p~33>udq z;?J67%w0$FrndPR;OCOGr)+yY!gr^D&F&oz;eWb-ylmhhf3=i|)HNK#PS_6X)}eWb zUtWnCjQ9t4QuZUegxA+_9$fBX0^Qf{GQn-kXt~Q;8#SLGXtHsJ4RJAKO(JPF=mHhJ zmV?H}uneyVnmB*aLi-@;ZasC@ti9UFu&*isNfnYieRA`%3iZpah~Y*LN1h;V69?yo zK6`jUXrj==@w`(d3Lc1NFYBCZW4XKX-L)rrdiE&yxtnHNkpcrEh*&*c2Qa>P`hz~U zDR}l=9ai^t8JH2Zn9M*3Fuz)c`eli1>=_jc+ZkiGFq6w_kaLDD-v7!+d5Alt+@T8mnd(~__fiU4+29jnF$K7fr5i705PrS z2(}d5kZi9ACr2{taq&c!(rNbonMo{!ZKt>vQa9a{n&O@|@eagkh5I34VtTDpt3S1u z17hfU-(dJjlrOWinEKX|aEgED6WVHn_84MKjxeT$27%v637} zFKYdD!F3GI164OVH8pUp-WoTk4$L|$(K`3NFW2TfeK-?@P{y?kDD|11x|?ogqWg&S z9+TWi`+1&A46L*b{xJk1d}rF>EC&8_;?f3ho3%Tm%cZuHU%h0%?E;2P+vqh4JYKGY zuZY$$j@30*gjngexrKJSi`khUEFE)Raz_=ZgprXiwe+{|bPEidv=qK|{$Uy|nv#yC zZ=P&uviS~4erCPW$N&DVbNy^OF@x1E%%nl`0fED`S&rK}2=W9wz&fF=h(TNgNxbBcl~WN)1^#?hLs;)+JC9 zL{q`j$yvbDvhwZyvKw?~A>!>p&X<+ZVWWeKyRi?YIAewTQ0{pBr9qgzEUzk&y-Ot9 z4KgbVoc`_+T+ykX2)F2#w4M>6^zl-Ri&lM9QlLuVR!{gq%j{d1W0|818L($Bn>vLv z?P?U-{&C@Uo<2tgKi}jq_{#LLo);^k7#js4K)uWa4SybSo!qeT?_lI&w1TJ8-*_te zTFCh@+e8}6T7`(C*(%l8@0{{}V)!q&k>~KNP|5A0$P7 zE`ontX41O7g9~Ru3ug+4hWr25K7WO+JZN}bv+6+hLcA|yqM&21cOj=X<}0U9i%W$R zsjhB=r~W6-e^?4viO^4RI)kRF7w}@W9hs`;tyED_m@(-!`DH!7k!@X1sLEpQSe2S9 zeaMBN$b9*=aZO99!IAyjqOGrFc-L_t;sX1%ZSr%yoZDkQ7Xu5Mx86*a`QEdT^}x>M zh`H;h?@H&3hEd<*l?V%!KUG_Fr_mZovO8PF%}sG5x{2#j4|2*?wTa}bnS zT+4}`3wdLa1?Ma`E`z_-Q2-DDC7LY=?_lVVz4WcUr%z=EEI@Gc<^&eTlbm1_lR@`i zJ7dKa0rY0HG9cQL2y9_t-#rbet-c9#lE3-VL6I2NF<36*ufH`L*to}GQ&AK?AsTHN z{D;)V49U~9P}rpuT>NLs=o59F8IV>?U0paVJT=eA;*+wSI`Jx&!cJ1x-;@Mh0*D_Z z`6u#6&We|zGm@&R3El+^5u}mYx9K<=*{zm3(GG@b)o`Zosr;s+4M{nT)_Nu8VE+~F zh)zhMm#*)6+yW(`6hwKHO(w{NR(}&DPadG(7Zv#?N|=Rg%*U<9CAYyHwV7DzM8Uph zAdSC^lx%9MmFmZ`9oV%#!XrzlzQ6|Qe7ZZg2|opRt)cDBY@XCStyLQCRm&YlojN-! z)Dx)JZP?3#C3XnqzzQ>Pno)*Mq4_iPNnv?>{T3|Bc1zpPHg}p?dk#1vN!Ly*(_b&7TS=m=i5bkXo+AR zaB`Vd3d}o);xmso`)PLKpJll4Wkdq5wCQ!BGAmZpMCv0_>TuWnyD=&2(+{F)O0d8F zb@Vb4a%Cys_VmctaiIWDlVkLEJo`S=z?23EBzJKeoNT=HZc1KW*itZ2; z>&OAqjqCFZ_=R6}*oE(mz6`O%&En14jmoGxQ#Um8jq+SKH9=iR#hs3A9-=Ee(l zE<8JvQRVuD3cO?rHx`G5hlPc8441MkYjD9d)QntVlXkCz5=VOF3J~^ugqVKfl9D0Q z-effbM?rxB7a9rh+-Y520q$f7MW^0nLi8(&+%Wl!a*D13_-Z&WYAT!xKN!Kcr^=I> z%dr(98M?z~gsBcV$#Os5|5wo!!nVNGdmR{Kf2ks||E+;e0fsLc>f?mEn+R*)y1PYv=}>krPW&J2kUH_%xjl4p$)nXJlgZ11#i)b<=xUm4Wt3p_L;RI{u5gE zFEW9s&(~&zH^>(xW+zaqO~J*~D#DldxB5A2HxyX-L6Cvx{dv$%0BKyKtGeYtYsrvb zSX9PeDEZe|Wf;YIpMBH!0Q+jVmFo+y4WoCEl?vU4EdzwEIML4y+gj<+^Cyc9BYx*B z?VJp+3naA$HeV^JEL|l8L){Uy z0mfMz-*TP`xvhzJcdMS?-^5fL^go}CHn-oL2Zv<@kuC#X)Ee9BbRyICr52|zXw%V7q;OIn6yLAq^gwY6hE6# zO9TfY*amz#@hUT+j*0lOrJRS2MzxHfe2h_zUKO+&KzCs*-U?KA7R!zCx;>?^{TJI( z{sRgv4~4dO_X2di#+WKH6u=*foHSPIl}&gh4m zcf)b^n=uY9Ea@p1+G-Vep#%tBG@kXcD`reahvqR=7XX(%+hp{JtpEU}M+5%HsSTQg zxBSr^|MrxrcYDL2=^YPT7EnVk)c)0Mrs}ZpadY0F{!E71C@z$S4a8!v(=KDes#P@(ikuugVC0Y_ZL6Bbbn!%hBV6Y za)KI{3FGo2;dd&FvVYAe3sevx^pg*`Pt&j@L2Qo}_}|@Tzm8NCzS1(54oKujHP^9b2J94vM`HYvcah)S_H={rqzp0iEt;@JEDjkSdUZN4iqyrc@o3_75dS#%p2ZHDvw#T6yMeX{!ygD8nz zUO}C-Bd=zdzh$jPJ3|m9K{#jHY>MPR!x$^q_|+g2Awy0b$|(oV`j8tXM6ir#u>;B( zMCzxbIViCx$_>{W(wB#P|3z%aR8lf=;mY%o+1G+#Opb6?)<8>e@^FDr{TCu_HG^ba zz4E70ApKh1Pfi$K=Ww_cILlZwZ5%BWTIYhuqQ}*H2xct>_KXMaq^T!vX?^b+m*7kU zeysJ+B=oK7oh?MG=#wD)%1Nlc*W=6;5UzsgHDT1l)}OTM1MR0!C9$WRlHOmMDc1ymiI%-a^huPzHSK5;htvWd`*qMzIKkc;n#oV$26cC9x(n7khpUP%h@#H#2X}lk5dn$9j80gToroHkN(mIy+GUiMX_X3!zWW?)ooblbEnIncJbrz;o*+DEr zTb+j=s%?T}!O+#!(q(7qb4_^B`SHZ4+gh4~nJTBIqaGv~Q%~%Q(2C%hukVicW`pU? z##*jJj{ufA#4bdvPNR>_q#EL7iKy2y*)8jUULjAkQE(t1QtTwnvE%+{rcq~4mHENP zH~4os^BQh|mBYcT9Oz1&5~D?S(=3;Oe8QwAsADeNFF6%yz4L4RM}deJl{Y$ceqIZM zq?HI@DUCZq)9U{US!Xc04({h9}AFTNgdgk6{pZ|Gwm{T}bqx{?!J%pKH#4_ku=HDE4u*-ksHG@F6pU=1g<57Vk2 zXwujCk&81TApRUsmE5Xh4UULU@dt0Y0R)|q3#L zU&{bC&YD_Cl{`nL{e_xLsOA@XfTN z)!+VFj45iRXVDfCJ+IojhX6iI=Z=ZU!0Y~vjQ+7>-deGeo}%v>3{ zP*}gi=wfnl;Z5!3LDXua_YcpGbW`jNFBz3w6gQ1_PZ@vO#CHqu9CGTx^!6{FX>G|!eB~0oFFc7aAi;nwh zJEqJ$e)uiVhQPh1D@z}NnV6X*w?Al77?4UrS*&RTA4~T83`U_AVqrjPddFe45SITz z+L4j|cczH~*eGJ>^%NYK#do~y@^KYvkMRHr$CSikp-h$|j_P41D(|LDLlepWdPqW! z8~RBIr&O!VdQd3e{jo`s435Bpw1I_DG^`uZv^D?+z-_fYm$wRbHxCR*J4cr$0g@g^ zsDWr`;Cg!g>f@Jz-F_+5?;HL)&s1rxA-ar$X<9I8aQ{-UZr3Uw$d@^tuF7)!<*^De zAv+7edX$;6ZCXz*C6kwtin^KXl)5#5T^oFIMm$r(xwQ;Pe|b#GYqeuxVfj^=yxP|3 zD%)zQB=C)-g5JA+SXS%SyW*#L2{VfZQ)jIPwlQAHHer)Ncw{o>uTevT9+PTmdq-l% zq$YzLzJf8ctwapxK525=qJp(d9v}*2MS_BHfpvt;x1~GC4%jl8VCneV?;gp4oulSzo&Ciu z?H=e;YwKHTq~E@EE92umz0e3Z8fTo6kQDpblK+z->xpY4;3_it>h5%vePWyHQPwvY zbIf%3A8caPvjR@&n*KJHCJGnwG3K+Dj8u3ZjnNv_OZAMe)+@&@8YQNtSrg}xTU3v` z9Z6Q(o)a3G58dfrnytq9P(ZkxoYO~FNk}UVPfJ^e&z-}STPI|rQs8Fwr-LA!fMiZL zwAyj_sd{a{y)0BbQK8ixeX-Lt+W7Yh_7X%Gl;l7qs!7%NeEl4@ek%;=MC+}3VaP*n zh#*1`MnqAzxu59z!SP-%9%VtH|C#xYzt{gvHbnRefiJV2DLMpe{qxgtmhUt_3%2+* zbqn*&{>vPHrcej}KMW#7Lm>(Sm?I(bM_id){-N&hkEOhtj%8e`AB4G4xIMb z{58G>RU)q!MMb&Dut5kjfYr6>JzdcIt9qG~PIAc2n71ybRhudr2-L6J z)zny$KVn;|2Lf3lFO|Tpew(WegGX3-7dS9O?`E}rx~?~b?VX|76O_Mcl9hWrWj#wgsEV z=2}C|6sn&8ltJnp-Y1j7~@}a(8G$^UvV-A7#MsyYZ8FRi4v6%hI-<{Qjx#d-Fj{gAQ&FV4CLSv;=q0? z#f`_eG&EfkhmtwmqPmpR1gbh0-{ONeXHu#JRp(zgpb&=qQgJck!V$W$tn$lK+A?0U zXY0yMtp-E{U=AizdDwS(Z`*dbM zZ^^_EWc@H!6F08`4^*&2o}9yu;PLXZm3%7!t_0N>Kcr#EEE~=@YAd{9haN}|{3gL$2rPEii}2LXOele;_1>2NIkIN+te8S(AJ&vtVrFfq(I!tE-7N;%ktz7%1f$7LH|IDfY5`>T69!WgsPmq3+}jQ z3CPubrCIf*amaID7{zF$IB3@Etmk7+gUaTgDnWzaj`?FN3kJeI=dc5r_eI$c@F|f9 zZc4bl-$Tn0MF^r>^HUcq<1llDY=3l$swPG*)HzkJ7^po- zjNMjrI-a3~a7L)tQ=&E9ZGCH@mJ!0f0%VAI%n)<|rT-Z(<_Z`2lk}DMav{jVnP@lH z>m%L4^)@G_|3>Y1zayG9B#S>9eKk#x#It`3ic+R4{?3jFy@^~C194g% z+%d0zcK2*fbbotO)NsUa5;29C7f)XR)nlO+fY;^kJfhL%6q@N^3@;=xeJu3UdWg^W zuVWU6m>Q*4aP`f_HpV1onJc0X%mU(`li&H3aRV(#^`skkzkT(eiqF=sAPzM6$3teV zce$2-Fxiv;KopQ3{PWn}F^Na%FVe!hw>=C*FPVy?PQi#jVy)7Y)X$mbv20|DJ$J*F z1WL7$9H9Kry#X;zhu>c9Er6D>F?2|be>;bM#y$MvO32-W6nBAcImPzeooHJ+A^JgE zFtc3Kks(#N9%Tg0S)a4s&+hDOB?SeF(f$mX+tjE-ao=-I#6Fb z+DzgIQ?A`f$k$SlB!YrLq0vi}tK&?7R#l8vIF$Z*{-M|VQE7va@Af2Cp* z4lFz{U0Hq~*`Yb^!$N-l>@o*NSg@j+dFr7LgQ8UHWGoPqHh@&|a3Hn>SEg_)430Y- z#7UPvq?Jrap$wHK_zHo_Fyqw*zwmPdqV}4m z*x?tBe#A_8s=bO{H(dx3K(G=Lg-QyA8yXVup2`wP?kMH<6(lYNrtY21=pa{{;zwd^ zCq~**Uf_0Mjqbo6~!2bH9;QtJHs}iR`4~mlm&(o zd;CGW)tooEp+<|jE(t(_@A@u&%_1yJ)dS4!X;+zE6Vk!WiH@`U1jI=XTs&-7dhT0H zkYpM#2727Vuf?`wxU(U9^@aCd44u8<-%SylOiKTTlDz_3FaPQG;p+c$GIATgR^-R) zuc--5Z#xy!B8Sw4qfFt++4mzk$r+ftOOba+Tht|J>HY9hCSuS1@a?^=mhl@NJ+x>N zyBdOy6ijOk4BEyMT8dnLsY@E>0Kqa;{=w^hwuXMZ>USE9_Jm zG}Vy{tltdUw}5b?cIpYUZ966WFGeo*?A(d+cyEZ)Kyaf>DeV302{|wRrCW5jKVUNa zn)W1T_boD-(x6RI^oHwSxHzoV{<)TQ0Q(D-JJ||zio8T% zw;v97E8EXa3P(CJ6%xF{II93W*;}c~Ao&`&soxzY=wbIKu*Rpce^0K$&7$|i7jz~$ zl=fWm5ImK)EPzU4*lho`Zvpff7)Vi#PU0M6Hu=Jxt^4_e`H%+~fS`s!G_VXYxnxD# zPwqjYT%78syh*+~61SIti*d_b>--a{{U0BWh`r3I@FhG4FYNjFEDB7}zr(XS-*%<2 z(y0Wt6FCCe({ta=zSnbX;(9kLO@f`2QiiGw(YHCKaQw)~qBotGqB^ao$YRMl7TI&H zEWYa)+4XnFQQp)#IeQ7X!gdG0Xl~}lv&j0~>v~MzN)>OO?Xh?Je%G5#(&~+=~tJ0 z1%tFmFNZkVEOdjcG04;SL-wxuW|hXucNuj@!6di(nARaviwpFsP%;7EQLbA(K^y_8 z_l(w*CS?1ivQC(E4Ez(?7zXd>thO=lAR>6r?wi9s+ZJ8rCHfrDXCYNsEFzFDa6_N5 z`5np@%NQ}x_(SU=PX!#c2sW{MMCcy{g1O%jm2kl)Moxr}WNQAJ^N|5u*wnWuq9xlB z7xdn8rD2zQ3N8FjJSLP=`~N(y#9D)*qNQ0}eoS1D-6zP}M}he-EmZWVFe z^2{2=`+PWL!TDl-nx0oJl{)qe$vxvDy!~1aM!!8|5SKfn7IaZW_Q}_&6y%q&M546K#7n$2eeJXbc4DG z!;yQUOl~f7u#fRR=pgRYLz+2;ZVv0x0}{f1o_&f>ZM@Ma`+8FZ*qP=}tZlv`>K08N zzz?M~3DPSW0DmyA?7?LFkh^B)S1RFnE{YG9%e&x{fFKk9Ik` zmP?{|zmO3YmS}b%NU|o0{`YgH7BoGHYgXDBXN`){?6u<@VyWC!MdxI&hA-nMC?$<0EcMoPuN8`?%m2?-YlCrUDfwt)eJUPexa zH5}tQvVmXH2ly{cD4I%}MIN-AuZX?_9@aemp>=K?l1j|siqi`hz8#bwLw4e|oc0n) zJen#F#PFVZArJ9y3Dfa~lP0^uLpOZh#X`LbDyKx3EL0obBHaDL9K7)8Wl#M3>wGyf z-C~gIu8%S_$PiI-D*4mRvnUh4F|s0{q`oGy>!x256m0t7+a1!JN1358n`1j~X0xr` zJd>YV++j}_;xF4$X{DI#E6}5DGXhPgpoeb_RHD3PAp^f>3+7~Wa3~9 z;UZoCoG!)5Fzz~$zRBkJ#3%zc_xw~*Z_0NI#SVj=D93^72)*gVc=E$2i%VYJ;1P^_ zRS;Cmb+wUqqfNRI2r<$9#HC4AF2+|7cg%L-YRBK>PG$Q0wttzEYO?w&xYG*SX&w85 zELGGF(ebY5iTAcPoc<2T7z&*h^!<8-j7j}nAs(wG&ZRXmHnnxI8u2@IQdwFsH=PzGj zxuOe702JiS&SF2}^B$O>TcX{8<3w-2Q^?Svz7R|E0)L&@fu17eBXn|GvCl6IK zCgmrb2-RJ6*o!(IVBMZ(`6KP3xp!a)5Dl?U;9b|Ne_yiS9dY%6M#{N8DrV1LAkAbB zM9-EedC+_?g%~iYI?GPt?tLOjeWiRCyqq8~>y4+^N`vq4;eAg+0~JIiH#6_BmVYN|_SOnpw^3*aELCO~Jpgg+*9twoL<)j06MELg% z1hk^TcoA&vHN$eT@Td(|AL_y*XtlLzAfYOyDvmP|CF%b2>g~&?u8sTTQg+OhI$U#u zLyr)(9b%DUjh}>>eVw7=d-P=pGJ!92^^~t(&r88C{3gAx#!w8o24u}P-exj=hZMH4 z&R?Zm8%8!|a-||_hl84?2z;`Mu~!Vse6<*njNBoKjs&dkxNG8v;t4{q$f?Q_NE#l! zrDWJQj0HG~py#2;KdJsf-;G{5U~htx6P?Tquy_uhez~JHdzd7k`<)QGcz?F4X;6suD$ko^S@(uqh#~tq37?;-rYC7Z*j|PDB4C=) zSp>Fv{PFeX2%Im+qp#Qa>clm_F_aQVCmBH4_vDpqCS6`&aUg6=90&o+CgR8@;?W1< zjsFb9OG11Tb@3H`qK|go#c({^P%7gvg2k^C)D~@-*o3DODmeUZy9Tmx>LN%JEbXE4 zPUoWKZxDheK`>KZl3>9{njcWD6SY=c=H({Pjgbk>!AoZ85n)mK>Gs2L$zppEJt|oP?E#K|0YOY9 zAviZH*+Z7gbgp#}o8t0Kwc|>)da~0|EXBFZGbz5Lck)!N7AyD`o?3 z|0?nNB6Cpaz&gmYWl%n>2qO=a^1!lOv-o@4rToIob+Q#App!wtbnxYaRW*wF``oo) z^M#vgw`Ah}N1s;G_qq>7h;p}*w0{rnVxsUANX5@21a-7|wnwu!*wB$5rqs2fHxyQy zQ@(yVh(&oA8T)Hna)}p=cBK0#$!SuZlX-1yMEZHzG9UCm)97AaUiM_LU$*DFm5GFy zi44# z{y3hRc_4X^{65dV3cL0b()DhwKN&X|CE18u6B4=lr8rYGWy{<=iGK)T`4Er)1771U z!P%#+w6QiY0u)=8iy%T<%fBhsh=X7v1+#x2dvTv70X>}OxwxHomqe}~G`1g^bxOj< z!M9|s`A|f-SL3Qp@QD_dCK#g6JP0AQyUjh~qc6jkRU`WeDTTBO0o%Nnw^?xFmnU=#{C1`hA^Q8Vuk~EUCmiu|^cDtf=aaUdCqkoVz()zkqoHq2 zac+&6uNS7t2QJ(&V=L%-JAKFfC&*|j9(`g-c9_rO!`b<1mf7|GqRP$nL3>CIk&e;} z(%qxJ!_6$}HOi{nx^N8EjoCFFy(|#ij_2*X%cWK|x!!s^w`nHi`5d*)P9br)&c?j3 zR!8pj9IzeI&UZUq8nUkM5gI}Dt>dyk^oZ)OJ)^fm7@n+J2q<71Z1Fut12Ry}iVzXp zqRY#2oR{Z@9(wn)(zlII_Y?mbf-|XSeoa$t@wcNDYS0465^l<+_bOjO+4(oW=^ zeC{r4W0EojPff%4_8rq33=uMGafhPT^;w27m_H->;cL}Ehe{jXhft@|PZ;W)NFCx| z<7L0`xJ%eu4DbKkynNC5jqft#KL(eHz~id)g~2kWAx~t@UlOZA&!8HK^4^FyoTx$0 z{f(Uy=||*58`}|q+Gn=l!6%~q$WIy98LH;bebS@uYa?klpE{V$1 zvFn3hJE5P>o5i$|q)em}y#L^2U_#PAh$D}BoRn6%Lw=kL-Ee=zjEUMW5aW5(sD&hY z+seFaZWcjd6xy9)8?r55ZZFd{BKQxD)DoV5gHC|SdUoG!JXOdtGdz1;E2ZWVTrgzY zuMdfq8m{do6>p9-4?7?A^+tvUK_rslcw!!brWNU-?cg=h;#BVoPP|;0Ss!*wpfdEW zCB7}5qW`O!Z+_;1-by68FIE^Hy@`1xn+0M-nyOaU#oHr-=5SLcBg2!NC&S)9zv=)o z4KI!UyZx5R2JS5Y3nFsJd?8_S2^|A5VD7{^8-LEmBLrxvB#TF1hAkc@+ZwY|gVaT? zdIO^PwF6JxI#^E!r|2ynxZ&4@pha|onpXUVvVD}#m=A9)AeEPxkasUq)4S_5&7;<+ zUcafLDoV5Yvgom!Qb401PYu zr{U|%RG~?~7>qjJ)6&aIlMw^tI5g+-*#Jy#AGcqi?g=eI?8j97-mU9plsnUJE;j)9 zyuUu;xJj*0g+;AsK%~M<$yToJ(X^m?@7{bp)B$igZ4NHO=YFM3)zcry>p`BuP2l|0 z>S~hoh8F~d1wdO17^1<`cq$3yg}cvY+eIu(@_CCl7dt@GY@fFgNmP*qZG|jJD|H4X z@ge-Kt$IT*U<^wI`aqLd!sYIy=E!CDWCB;yE};Y1p{ZuqK}6|cWj#LZz9}Pb!XLtN z{10&hIOWAZF4fszTzzrfOH8hD=*y_Y!(O%^^EtgOYD+s!&v$`cg;a0c2BRBx?z7!K ztcE2(8l47t00E*nh;guDM5+(JU)ffJvCTXeoguQ&>*y*e=tc`QI2K433zSxaQiH>2 z!D0R&dNjNs0z9NJSpqsH@LP*50f|9l#sAa4DZ~HMCjq4iR4TN1)9{!vdrw^w*0$;X d|4;D%z)#(syeG-Ww>_h`OGZKwR3&cU|9=X8nSB5N From 07760d07146816bd9aa32786891bb24f467d713d Mon Sep 17 00:00:00 2001 From: Jeff Willette Date: Wed, 14 Sep 2016 02:45:24 +0900 Subject: [PATCH 709/756] [1.9.x] Fixed #27216 -- Corrected import location in admin deprecation messages. Backport of 7148929a5ef4a5fb8332a43f7d39394618313476 from master --- django/contrib/admin/helpers.py | 4 ++-- django/contrib/admin/templatetags/admin_list.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 9c3440fe0539..a0d44c24b07d 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -207,8 +207,8 @@ def contents(self): if getattr(attr, "allow_tags", False): warnings.warn( "Deprecated allow_tags attribute used on %s. " - "Use django.utils.safestring.format_html(), " - "format_html_join(), or mark_safe() instead." % attr, + "Use django.utils.html.format_html(), format_html_join(), " + "or django.utils.safestring.mark_safe() instead." % attr, RemovedInDjango20Warning ) result_repr = mark_safe(value) diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index f4033e235bac..83c3b8e7aeee 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -213,8 +213,8 @@ def link_in_col(is_first, field_name, cl): if allow_tags: warnings.warn( "Deprecated allow_tags attribute used on field {}. " - "Use django.utils.safestring.format_html(), " - "format_html_join(), or mark_safe() instead.".format(field_name), + "Use django.utils.html.format_html(), format_html_join(), " + "or django.utils.safestring.mark_safe() instead.".format(field_name), RemovedInDjango20Warning ) result_repr = mark_safe(result_repr) From d1bc980db1c0fffd6d60677e62f70beadb9fe64a Mon Sep 17 00:00:00 2001 From: Collin Anderson Date: Fri, 11 Mar 2016 21:36:08 -0500 Subject: [PATCH 710/756] [1.9.x] Fixed CVE-2016-7401 -- Fixed CSRF protection bypass on a site with Google Analytics. This is a security fix. Backport of "refs #26158 -- rewrote http.parse_cookie() to better match browsers." 93a135d111c2569d88d65a3f4ad9e6d9ad291452 from master --- django/http/cookie.py | 29 +++++++++++---------- docs/releases/1.8.15.txt | 18 +++++++++++++ docs/releases/1.9.10.txt | 18 +++++++++++++ docs/releases/index.txt | 2 ++ tests/httpwrappers/tests.py | 52 ++++++++++++++++++++++++++++++++++++- tests/requests/tests.py | 4 --- 6 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 docs/releases/1.8.15.txt create mode 100644 docs/releases/1.9.10.txt diff --git a/django/http/cookie.py b/django/http/cookie.py index a3dbd2a0b2fd..decb6db8a735 100644 --- a/django/http/cookie.py +++ b/django/http/cookie.py @@ -89,18 +89,21 @@ def _BaseCookie__set(self, key, real_value, coded_value): def parse_cookie(cookie): - if cookie == '': - return {} - if not isinstance(cookie, http_cookies.BaseCookie): - try: - c = SimpleCookie() - c.load(cookie) - except http_cookies.CookieError: - # Invalid cookie - return {} - else: - c = cookie + """ + Return a dictionary parsed from a `Cookie:` header string. + """ cookiedict = {} - for key in c.keys(): - cookiedict[key] = c.get(key).value + if six.PY2: + cookie = force_str(cookie) + for chunk in cookie.split(str(';')): + if str('=') in chunk: + key, val = chunk.split(str('='), 1) + else: + # Assume an empty name per + # https://bugzilla.mozilla.org/show_bug.cgi?id=169091 + key, val = str(''), chunk + key, val = key.strip(), val.strip() + if key or val: + # unquote using Python's algorithm. + cookiedict[key] = http_cookies._unquote(val) return cookiedict diff --git a/docs/releases/1.8.15.txt b/docs/releases/1.8.15.txt new file mode 100644 index 000000000000..e977cffbab6a --- /dev/null +++ b/docs/releases/1.8.15.txt @@ -0,0 +1,18 @@ +=========================== +Django 1.8.15 release notes +=========================== + +*September 26, 2016* + +Django 1.8.15 fixes a security issue in 1.8.14. + +CSRF protection bypass on a site with Google Analytics +====================================================== + +An interaction between Google Analytics and Django's cookie parsing could allow +an attacker to set arbitrary cookies leading to a bypass of CSRF protection. + +The parser for ``request.COOKIES`` is simplified to better match the behavior +of browsers and to mitigate this attack. ``request.COOKIES`` may now contain +cookies that are invalid according to :rfc:`6265` but are possible to set via +``document.cookie``. diff --git a/docs/releases/1.9.10.txt b/docs/releases/1.9.10.txt new file mode 100644 index 000000000000..6321cd8d03f6 --- /dev/null +++ b/docs/releases/1.9.10.txt @@ -0,0 +1,18 @@ +=========================== +Django 1.9.10 release notes +=========================== + +*September 26, 2016* + +Django 1.9.10 fixes a security issue in 1.9.9. + +CSRF protection bypass on a site with Google Analytics +====================================================== + +An interaction between Google Analytics and Django's cookie parsing could allow +an attacker to set arbitrary cookies leading to a bypass of CSRF protection. + +The parser for ``request.COOKIES`` is simplified to better match the behavior +of browsers and to mitigate this attack. ``request.COOKIES`` may now contain +cookies that are invalid according to :rfc:`6265` but are possible to set via +``document.cookie``. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index f0d3c3c8527c..b9cce6a60848 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.9.10 1.9.9 1.9.8 1.9.7 @@ -41,6 +42,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.8.15 1.8.14 1.8.13 1.8.12 diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py index 9edc7d12af9f..3c47c978c8b2 100644 --- a/tests/httpwrappers/tests.py +++ b/tests/httpwrappers/tests.py @@ -21,7 +21,7 @@ from django.test import SimpleTestCase from django.utils import six from django.utils._os import upath -from django.utils.encoding import force_text, smart_str +from django.utils.encoding import force_str, force_text, smart_str from django.utils.functional import lazy lazystr = lazy(force_text, six.text_type) @@ -657,6 +657,8 @@ def test_decode(self): c2 = SimpleCookie() c2.load(c.output()[12:]) self.assertEqual(c['test'].value, c2['test'].value) + c3 = parse_cookie(c.output()[12:]) + self.assertEqual(c['test'].value, c3['test']) def test_decode_2(self): """ @@ -667,6 +669,8 @@ def test_decode_2(self): c2 = SimpleCookie() c2.load(c.output()[12:]) self.assertEqual(c['test'].value, c2['test'].value) + c3 = parse_cookie(c.output()[12:]) + self.assertEqual(c['test'].value, c3['test']) def test_nonstandard_keys(self): """ @@ -680,6 +684,52 @@ def test_repeated_nonstandard_keys(self): """ self.assertIn('good_cookie', parse_cookie('a:=b; a:=c; good_cookie=yes').keys()) + def test_python_cookies(self): + """ + Test cases copied from Python's Lib/test/test_http_cookies.py + """ + self.assertEqual(parse_cookie('chips=ahoy; vienna=finger'), {'chips': 'ahoy', 'vienna': 'finger'}) + # Here parse_cookie() differs from Python's cookie parsing in that it + # treats all semicolons as delimiters, even within quotes. + self.assertEqual( + parse_cookie('keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'), + {'keebler': '"E=mc2', 'L': '\\"Loves\\"', 'fudge': '\\012', '': '"'} + ) + # Illegal cookies that have an '=' char in an unquoted value. + self.assertEqual(parse_cookie('keebler=E=mc2'), {'keebler': 'E=mc2'}) + # Cookies with ':' character in their name. + self.assertEqual(parse_cookie('key:term=value:term'), {'key:term': 'value:term'}) + # Cookies with '[' and ']'. + self.assertEqual(parse_cookie('a=b; c=[; d=r; f=h'), {'a': 'b', 'c': '[', 'd': 'r', 'f': 'h'}) + + def test_cookie_edgecases(self): + # Cookies that RFC6265 allows. + self.assertEqual(parse_cookie('a=b; Domain=example.com'), {'a': 'b', 'Domain': 'example.com'}) + # parse_cookie() has historically kept only the last cookie with the + # same name. + self.assertEqual(parse_cookie('a=b; h=i; a=c'), {'a': 'c', 'h': 'i'}) + + def test_invalid_cookies(self): + """ + Cookie strings that go against RFC6265 but browsers will send if set + via document.cookie. + """ + # Chunks without an equals sign appear as unnamed values per + # https://bugzilla.mozilla.org/show_bug.cgi?id=169091 + self.assertIn('django_language', parse_cookie('abc=def; unnamed; django_language=en').keys()) + # Even a double quote may be an unamed value. + self.assertEqual(parse_cookie('a=b; "; c=d'), {'a': 'b', '': '"', 'c': 'd'}) + # Spaces in names and values, and an equals sign in values. + self.assertEqual(parse_cookie('a b c=d e = f; gh=i'), {'a b c': 'd e = f', 'gh': 'i'}) + # More characters the spec forbids. + self.assertEqual(parse_cookie('a b,c<>@:/[]?{}=d " =e,f g'), {'a b,c<>@:/[]?{}': 'd " =e,f g'}) + # Unicode characters. The spec only allows ASCII. + self.assertEqual(parse_cookie('saint=André Bessette'), {'saint': force_str('André Bessette')}) + # Browsers don't send extra whitespace or semicolons in Cookie headers, + # but parse_cookie() should parse whitespace the same way + # document.cookie parses whitespace. + self.assertEqual(parse_cookie(' = b ; ; = ; c = ; '), {'': 'b', 'c': ''}) + def test_httponly_after_load(self): """ Test that we can use httponly attribute on cookies that we load diff --git a/tests/requests/tests.py b/tests/requests/tests.py index 843094fdf924..c0a48d610cc9 100644 --- a/tests/requests/tests.py +++ b/tests/requests/tests.py @@ -10,7 +10,6 @@ from django.core.handlers.wsgi import LimitedStream, WSGIRequest from django.http import ( HttpRequest, HttpResponse, RawPostDataException, UnreadablePostError, - parse_cookie, ) from django.test import RequestFactory, SimpleTestCase, override_settings from django.test.client import FakePayload @@ -154,9 +153,6 @@ def wsgi_str(path_info): request = WSGIRequest({'PATH_INFO': wsgi_str("/سلام/"), 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')}) self.assertEqual(request.path, "/سلام/") - def test_parse_cookie(self): - self.assertEqual(parse_cookie('invalid@key=true'), {}) - def test_httprequest_location(self): request = HttpRequest() self.assertEqual(request.build_absolute_uri(location="https://www.example.com/asdf"), From f49602ad46b447c5a27d47b0e89b3440109211a4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 26 Sep 2016 13:56:06 -0400 Subject: [PATCH 711/756] [1.9.x] Bumped version for 1.9.10 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 227c6fab7619..ea9162f3d64e 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 10, 'alpha', 0) +VERSION = (1, 9, 10, 'final', 0) __version__ = get_version(VERSION) From 71ea94492f35fb5112d9198ae0c9f1bbb0f77c3a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 26 Sep 2016 14:37:37 -0400 Subject: [PATCH 712/756] [1.9.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index ea9162f3d64e..f21647c01cc1 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 10, 'final', 0) +VERSION = (1, 9, 11, 'alpha', 0) __version__ = get_version(VERSION) From 4335d121ca0a57087c988d3574205e0a3999a8df Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 26 Sep 2016 15:37:08 -0400 Subject: [PATCH 713/756] [1.9.x] Added a CVE role for Sphinx. Backport of a46742e738b91f79dd7b2e6ecba6dd1604e14d05 from master --- docs/_ext/cve_role.py | 27 +++ docs/conf.py | 4 +- docs/releases/security.txt | 428 ++++++++++++++++++++----------------- 3 files changed, 258 insertions(+), 201 deletions(-) create mode 100644 docs/_ext/cve_role.py diff --git a/docs/_ext/cve_role.py b/docs/_ext/cve_role.py new file mode 100644 index 000000000000..254d3e679fed --- /dev/null +++ b/docs/_ext/cve_role.py @@ -0,0 +1,27 @@ +""" +An interpreted text role to link docs to CVE issues. To use: :cve:`XXXXX` +""" +from docutils import nodes, utils +from docutils.parsers.rst import roles + + +def cve_role(name, rawtext, text, lineno, inliner, options=None, content=None): + if options is None: + options = {} + + url_pattern = inliner.document.settings.env.app.config.cve_url + if url_pattern is None: + msg = inliner.reporter.warning("cve not configured: please configure cve_url in conf.py") + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + + url = url_pattern % text + roles.set_classes(options) + node = nodes.reference(rawtext, utils.unescape('CVE-%s' % text), refuri=url, **options) + return [node], [] + + +def setup(app): + app.add_config_value('cve_url', None, 'env') + app.add_role('cve', cve_role) + return {'parallel_read_safe': True} diff --git a/docs/conf.py b/docs/conf.py index 7d237f6d068b..ad5bbe60c7c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -45,6 +45,7 @@ "sphinx.ext.intersphinx", "sphinx.ext.viewcode", "ticket_role", + "cve_role", ] # Spelling check needs an additional module that is not installed by default. @@ -376,5 +377,6 @@ def django_release(): # If false, no index is generated. # epub_use_index = True -# -- ticket options ------------------------------------------------------------ +# -- custom extension options -------------------------------------------------- +cve_url = 'https://web.nvd.nist.gov/view/vuln/detail?vulnId=%s' ticket_url = 'https://code.djangoproject.com/ticket/%s' diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 509ef7f244db..8a9d73de3638 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -37,10 +37,11 @@ Some security issues were handled before Django had a formalized security process in use. For these, new releases may not have been issued at the time and CVEs may not have been assigned. -August 16, 2006 - CVE-2007-0404 -------------------------------- +August 16, 2006 - :cve:`2007-0404` +---------------------------------- -`CVE-2007-0404 `_: Filename validation issue in translation framework. `Full description `__ +Filename validation issue in translation framework. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -49,10 +50,11 @@ Versions affected * Django 0.91 `(patch) `__ * Django 0.95 `(patch) `__ (released January 21 2007) -January 21, 2007 - CVE-2007-0405 --------------------------------- +January 21, 2007 - :cve:`2007-0405` +----------------------------------- -`CVE-2007-0405 `_: Apparent "caching" of authenticated user. `Full description `__ +Apparent "caching" of authenticated user. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -65,10 +67,11 @@ Issues under Django's security process All other security issues have been handled under versions of Django's security process. These are listed below. -October 26, 2007 - CVE-2007-5712 --------------------------------- +October 26, 2007 - :cve:`2007-5712` +----------------------------------- -`CVE-2007-5712 `_: Denial-of-service via arbitrarily-large ``Accept-Language`` header. `Full description `__ +Denial-of-service via arbitrarily-large ``Accept-Language`` header. `Full +description `__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -77,10 +80,11 @@ Versions affected * Django 0.95 `(patch) `__ * Django 0.96 `(patch) `__ -May 14, 2008 - CVE-2008-2302 ----------------------------- +May 14, 2008 - :cve:`2008-2302` +------------------------------- -`CVE-2008-2302 `_: XSS via admin login redirect. `Full description `__ +XSS via admin login redirect. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -89,10 +93,11 @@ Versions affected * Django 0.95 `(patch) `__ * Django 0.96 `(patch) `__ -September 2, 2008 - CVE-2008-3909 ---------------------------------- +September 2, 2008 - :cve:`2008-3909` +------------------------------------ -`CVE-2008-3909 `_: CSRF via preservation of POST data during admin login. `Full description `__ +CSRF via preservation of POST data during admin login. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -101,10 +106,11 @@ Versions affected * Django 0.95 `(patch) `__ * Django 0.96 `(patch) `__ -July 28, 2009 - CVE-2009-2659 ------------------------------ +July 28, 2009 - :cve:`2009-2659` +-------------------------------- -`CVE-2009-2659 `_: Directory-traversal in development server media handler. `Full description `__ +Directory-traversal in development server media handler. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -112,10 +118,11 @@ Versions affected * Django 0.96 `(patch) `__ * Django 1.0 `(patch) `__ -October 9, 2009 - CVE-2009-3965 -------------------------------- +October 9, 2009 - :cve:`2009-3965` +---------------------------------- -`CVE-2009-3965 `_: Denial-of-service via pathological regular expression performance. `Full description `__ +Denial-of-service via pathological regular expression performance. `Full +description `__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -123,20 +130,22 @@ Versions affected * Django 1.0 `(patch) `__ * Django 1.1 `(patch) `__ -September 8, 2010 - CVE-2010-3082 ---------------------------------- +September 8, 2010 - :cve:`2010-3082` +------------------------------------ -`CVE-2010-3082 `_: XSS via trusting unsafe cookie value. `Full description `__ +XSS via trusting unsafe cookie value. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ * Django 1.2 `(patch) `__ -December 22, 2010 - CVE-2010-4534 ---------------------------------- +December 22, 2010 - :cve:`2010-4534` +------------------------------------ -`CVE-2010-4534 `_: Information leakage in administrative interface. `Full description `__ +Information leakage in administrative interface. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -144,10 +153,11 @@ Versions affected * Django 1.1 `(patch) `__ * Django 1.2 `(patch) `__ -December 22, 2010 - CVE-2010-4535 ---------------------------------- +December 22, 2010 - :cve:`2010-4535` +------------------------------------ -`CVE-2010-4535 `_: Denial-of-service in password-reset mechanism. `Full description `__ +Denial-of-service in password-reset mechanism. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -155,10 +165,11 @@ Versions affected * Django 1.1 `(patch) `__ * Django 1.2 `(patch) `__ -February 8, 2011 - CVE-2011-0696 --------------------------------- +February 8, 2011 - :cve:`2011-0696` +----------------------------------- -`CVE-2011-0696 `_: CSRF via forged HTTP headers. `Full description `__ +CSRF via forged HTTP headers. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -166,10 +177,11 @@ Versions affected * Django 1.1 `(patch) `__ * Django 1.2 `(patch) `__ -February 8, 2011 - CVE-2011-0697 --------------------------------- +February 8, 2011 - :cve:`2011-0697` +----------------------------------- -`CVE-2011-0697 `_: XSS via unsanitized names of uploaded files. `Full description `__ +XSS via unsanitized names of uploaded files. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -177,10 +189,11 @@ Versions affected * Django 1.1 `(patch) `__ * Django 1.2 `(patch) `__ -February 8, 2011 - CVE-2011-0698 --------------------------------- +February 8, 2011 - :cve:`2011-0698` +----------------------------------- -`CVE-2011-0698 `_: Directory-traversal on Windows via incorrect path-separator handling. `Full description `__ +Directory-traversal on Windows via incorrect path-separator handling. `Full +description `__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -188,10 +201,11 @@ Versions affected * Django 1.1 `(patch) `__ * Django 1.2 `(patch) `__ -September 9, 2011 - CVE-2011-4136 ---------------------------------- +September 9, 2011 - :cve:`2011-4136` +------------------------------------ -`CVE-2011-4136 `_: Session manipulation when using memory-cache-backed session. `Full description `__ +Session manipulation when using memory-cache-backed session. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -199,10 +213,11 @@ Versions affected * Django 1.2 `(patch) `__ * Django 1.3 `(patch) `__ -September 9, 2011 - CVE-2011-4137 ---------------------------------- +September 9, 2011 - :cve:`2011-4137` +------------------------------------ -`CVE-2011-4137 `_: Denial-of-service via ``URLField.verify_exists``. `Full description `__ +Denial-of-service via ``URLField.verify_exists``. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -210,10 +225,12 @@ Versions affected * Django 1.2 `(patch) `__ * Django 1.3 `(patch) `__ -September 9, 2011 - CVE-2011-4138 ---------------------------------- +September 9, 2011 - :cve:`2011-4138` +------------------------------------ -`CVE-2011-4138 `_: Information leakage/arbitrary request issuance via ``URLField.verify_exists``. `Full description `__ +Information leakage/arbitrary request issuance via ``URLField.verify_exists``. +`Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -221,10 +238,11 @@ Versions affected * Django 1.2: `(patch) `__ * Django 1.3: `(patch) `__ -September 9, 2011 - CVE-2011-4139 ---------------------------------- +September 9, 2011 - :cve:`2011-4139` +------------------------------------ -`CVE-2011-4139 `_: ``Host`` header cache poisoning. `Full description `__ +``Host`` header cache poisoning. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -232,10 +250,11 @@ Versions affected * Django 1.2 `(patch) `__ * Django 1.3 `(patch) `__ -September 9, 2011 - CVE-2011-4140 ---------------------------------- +September 9, 2011 - :cve:`2011-4140` +------------------------------------ -`CVE-2011-4140 `_: Potential CSRF via ``Host`` header. `Full description `__ +Potential CSRF via ``Host`` header. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -245,10 +264,11 @@ This notification was an advisory only, so no patches were issued. * Django 1.2 * Django 1.3 -July 30, 2012 - CVE-2012-3442 ------------------------------ +July 30, 2012 - :cve:`2012-3442` +-------------------------------- -`CVE-2012-3442 `_: XSS via failure to validate redirect scheme. `Full description `__ +XSS via failure to validate redirect scheme. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -256,10 +276,11 @@ Versions affected * Django 1.3: `(patch) `__ * Django 1.4: `(patch) `__ -July 30, 2012 - CVE-2012-3443 ------------------------------ +July 30, 2012 - :cve:`2012-3443` +-------------------------------- -`CVE-2012-3443 `_: Denial-of-service via compressed image files. `Full description `__ +Denial-of-service via compressed image files. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -267,10 +288,11 @@ Versions affected * Django 1.3: `(patch) `__ * Django 1.4: `(patch) `__ -July 30, 2012 - CVE-2012-3444 ------------------------------ +July 30, 2012 - :cve:`2012-3444` +-------------------------------- -`CVE-2012-3444 `_: Denial-of-service via large image files. `Full description `__ +Denial-of-service via large image files. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -278,10 +300,11 @@ Versions affected * Django 1.3 `(patch) `__ * Django 1.4 `(patch) `__ -October 17, 2012 - CVE-2012-4520 --------------------------------- +October 17, 2012 - :cve:`2012-4520` +----------------------------------- -`CVE-2012-4520 `_: ``Host`` header poisoning. `Full description `__ +``Host`` header poisoning. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -292,7 +315,8 @@ Versions affected December 10, 2012 - No CVE 1 ---------------------------- -Additional hardening of ``Host`` header handling. `Full description `__ +Additional hardening of ``Host`` header handling. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -303,7 +327,8 @@ Versions affected December 10, 2012 - No CVE 2 ---------------------------- -Additional hardening of redirect validation. `Full description `__ +Additional hardening of redirect validation. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -314,7 +339,8 @@ Versions affected February 19, 2013 - No CVE -------------------------- -Additional hardening of ``Host`` header handling. `Full description `__ +Additional hardening of ``Host`` header handling. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -322,10 +348,11 @@ Versions affected * Django 1.3 `(patch) `__ * Django 1.4 `(patch) `__ -February 19, 2013 - CVE-2013-1664/1665 --------------------------------------- +February 19, 2013 - :cve:`2013-1664` / :cve:`2013-1665` +------------------------------------------------------- -`CVE-2013-1664 `_ and `CVE-2013-1665 `_: Entity-based attacks against Python XML libraries. `Full description `__ +Entity-based attacks against Python XML libraries. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -333,10 +360,11 @@ Versions affected * Django 1.3 `(patch) `__ * Django 1.4 `(patch) `__ -February 19, 2013 - CVE-2013-0305 ---------------------------------- +February 19, 2013 - :cve:`2013-0305` +------------------------------------ -`CVE-2013-0305 `_: Information leakage via admin history log. `Full description `__ +Information leakage via admin history log. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -344,10 +372,11 @@ Versions affected * Django 1.3 `(patch) `__ * Django 1.4 `(patch) `__ -February 19, 2013 - CVE-2013-0306 ---------------------------------- +February 19, 2013 - :cve:`2013-0306` +------------------------------------ -`CVE-2013-0306 `_: Denial-of-service via formset ``max_num`` bypass. `Full description `__ +Denial-of-service via formset ``max_num`` bypass. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -355,20 +384,22 @@ Versions affected * Django 1.3 `(patch) `__ * Django 1.4 `(patch) `__ -August 13, 2013 - CVE-2013-4249 -------------------------------- +August 13, 2013 - :cve:`2013-4249` +---------------------------------- -`CVE-2013-4249 `_: XSS via admin trusting ``URLField`` values. `Full description `__ +XSS via admin trusting ``URLField`` values. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ * Django 1.5 `(patch) `__ -August 13, 2013 - CVE-2013-6044 -------------------------------- +August 13, 2013 - :cve:`2013-6044` +---------------------------------- -`CVE-2013-6044 `_: Possible XSS via unvalidated URL redirect schemes. `Full description `__ +Possible XSS via unvalidated URL redirect schemes. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -376,10 +407,11 @@ Versions affected * Django 1.4 `(patch) `__ * Django 1.5 `(patch) `__ -September 10, 2013 - CVE-2013-4315 ----------------------------------- +September 10, 2013 - :cve:`2013-4315` +------------------------------------- -`CVE-2013-4315 `_ Directory-traversal via ``ssi`` template tag. `Full description `__ +Directory-traversal via ``ssi`` template tag. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -387,10 +419,11 @@ Versions affected * Django 1.4 `(patch) `__ * Django 1.5 `(patch) `__ -September 14, 2013 - CVE-2013-1443 ----------------------------------- +September 14, 2013 - :cve:`2013-1443` +------------------------------------- -CVE-2013-1443: Denial-of-service via large passwords. `Full description `__ +Denial-of-service via large passwords. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -398,10 +431,11 @@ Versions affected * Django 1.4 `(patch `__ and `Python compatibility fix) `__ * Django 1.5 `(patch) `__ -April 21, 2014 - CVE-2014-0472 ------------------------------- +April 21, 2014 - :cve:`2014-0472` +--------------------------------- -`CVE-2014-0472 `_: Unexpected code execution using ``reverse()``. `Full description `__ +Unexpected code execution using ``reverse()``. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -411,10 +445,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -April 21, 2014 - CVE-2014-0473 ------------------------------- +April 21, 2014 - :cve:`2014-0473` +--------------------------------- -`CVE-2014-0473 `_: Caching of anonymous pages could reveal CSRF token. `Full description `__ +Caching of anonymous pages could reveal CSRF token. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -424,10 +459,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -April 21, 2014 - CVE-2014-0474 ------------------------------- +April 21, 2014 - :cve:`2014-0474` +--------------------------------- -`CVE-2014-0474 `_: MySQL typecasting causes unexpected query results. `Full description `__ +MySQL typecasting causes unexpected query results. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -437,10 +473,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -May 18, 2014 - CVE-2014-1418 ----------------------------- +May 18, 2014 - :cve:`2014-1418` +------------------------------- -`CVE-2014-1418 `_: Caches may be allowed to store and serve private data. `Full description `__ +Caches may be allowed to store and serve private data. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -450,10 +487,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -May 18, 2014 - CVE-2014-3730 ----------------------------- +May 18, 2014 - :cve:`2014-3730` +------------------------------- -`CVE-2014-3730 `_: Malformed URLs from user input incorrectly validated. `Full description `__ +Malformed URLs from user input incorrectly validated. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -463,10 +501,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -August 20, 2014 - CVE-2014-0480 -------------------------------- +August 20, 2014 - :cve:`2014-0480` +---------------------------------- -`CVE-2014-0480 `_: reverse() can generate URLs pointing to other hosts. `Full description `__ +``reverse()`` can generate URLs pointing to other hosts. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -476,10 +515,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -August 20, 2014 - CVE-2014-0481 -------------------------------- +August 20, 2014 - :cve:`2014-0481` +---------------------------------- -`CVE-2014-0481 `_: File upload denial of service. `Full description `__ +File upload denial of service. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -489,10 +529,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -August 20, 2014 - CVE-2014-0482 -------------------------------- +August 20, 2014 - :cve:`2014-0482` +---------------------------------- -`CVE-2014-0482 `_: RemoteUserMiddleware session hijacking. `Full description `__ +``RemoteUserMiddleware`` session hijacking. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -502,10 +543,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -August 20, 2014 - CVE-2014-0483 -------------------------------- +August 20, 2014 - :cve:`2014-0483` +---------------------------------- -`CVE-2014-0483 `_: Data leakage via querystring manipulation in admin. `Full description `__ +Data leakage via querystring manipulation in admin. +`Full description `__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -515,12 +557,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -January 13, 2015 - CVE-2015-0219 --------------------------------- +January 13, 2015 - :cve:`2015-0219` +----------------------------------- -`CVE-2015-0219 `_: -WSGI header spoofing via underscore/dash conflation. -`Full description `__ +WSGI header spoofing via underscore/dash conflation. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -529,10 +570,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -January 13, 2015 - CVE-2015-0220 --------------------------------- +January 13, 2015 - :cve:`2015-0220` +----------------------------------- -`CVE-2015-0220 `_: Mitigated possible XSS attack via user-supplied redirect URLs. `Full description `__ +Mitigated possible XSS attack via user-supplied redirect URLs. `Full +description `__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -541,12 +583,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -January 13, 2015 - CVE-2015-0221 --------------------------------- +January 13, 2015 - :cve:`2015-0221` +----------------------------------- -`CVE-2015-0221 `_: -Denial-of-service attack against ``django.views.static.serve()``. -`Full description `__ +Denial-of-service attack against ``django.views.static.serve()``. `Full +description `__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -555,12 +596,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -January 13, 2015 - CVE-2015-0222 --------------------------------- +January 13, 2015 - :cve:`2015-0222` +----------------------------------- -`CVE-2015-0222 `_: -Database denial-of-service with ``ModelMultipleChoiceField``. -`Full description `__ +Database denial-of-service with ``ModelMultipleChoiceField``. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -568,12 +608,11 @@ Versions affected * Django 1.6 `(patch) `__ * Django 1.7 `(patch) `__ -March 9, 2015 - CVE-2015-2241 ------------------------------ +March 9, 2015 - :cve:`2015-2241` +-------------------------------- -`CVE-2015-2241 `_: -XSS attack via properties in ``ModelAdmin.readonly_fields``. -`Full description `__ +XSS attack via properties in ``ModelAdmin.readonly_fields``. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -581,12 +620,11 @@ Versions affected * Django 1.7 `(patch) `__ * Django 1.8 `(patch) `_ -March 18, 2015 - CVE-2015-2316 ------------------------------- +March 18, 2015 - :cve:`2015-2316` +--------------------------------- -`CVE-2015-2316 `_: -Denial-of-service possibility with ``strip_tags()``. -`Full description `__ +Denial-of-service possibility with ``strip_tags()``. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -595,12 +633,11 @@ Versions affected * Django 1.7 `(patch) `__ * Django 1.8 `(patch) `__ -March 18, 2015 - CVE-2015-2317 ------------------------------- +March 18, 2015 - :cve:`2015-2317` +--------------------------------- -`CVE-2015-2317 `_: -Mitigated possible XSS attack via user-supplied redirect URLs. -`Full description `__ +Mitigated possible XSS attack via user-supplied redirect URLs. `Full +description `__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -610,24 +647,22 @@ Versions affected * Django 1.7 `(patch) `__ * Django 1.8 `(patch) `__ -May 20, 2015 - CVE-2015-3982 ----------------------------- +May 20, 2015 - :cve:`2015-3982` +------------------------------- -`CVE-2015-3982 `_: -Fixed session flushing in the cached_db backend. -`Full description `__ +Fixed session flushing in the cached_db backend. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ * Django 1.8 `(patch) `__ -July 8, 2015 - CVE-2015-5143 ----------------------------- +July 8, 2015 - :cve:`2015-5143` +------------------------------- -`CVE-2015-5143 `_: -Denial-of-service possibility by filling session store. -`Full description `__ +Denial-of-service possibility by filling session store. `Full +description `__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -636,12 +671,11 @@ Versions affected * Django 1.7 `(patch) `__ * Django 1.4 `(patch) `__ -July 8, 2015 - CVE-2015-5144 ----------------------------- +July 8, 2015 - :cve:`2015-5144` +------------------------------- -`CVE-2015-5144 `_: -Header injection possibility since validators accept newlines in input. -`Full description `__ +Header injection possibility since validators accept newlines in input. `Full +description `__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -650,24 +684,20 @@ Versions affected * Django 1.7 `(patch) `__ * Django 1.4 `(patch) `__ -July 8, 2015 - CVE-2015-5145 ----------------------------- +July 8, 2015 - :cve:`2015-5145` +------------------------------- -`CVE-2015-5145 `_: -Denial-of-service possibility in URL validation. -`Full description `__ +Denial-of-service possibility in URL validation. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ * Django 1.8 `(patch) `__ -August 18, 2015 - CVE-2015-5963/CVE-2015-5964 ---------------------------------------------- +August 18, 2015 - :cve:`2015-5963` / :cve:`2015-5964` +----------------------------------------------------- -`CVE-2015-5963 `_ -and -`CVE-2015-5964 `_: Denial-of-service possibility in ``logout()`` view by filling session store. `Full description `__ @@ -678,12 +708,11 @@ Versions affected * Django 1.7 `(patch) `__ * Django 1.4 `(patch) `__ -November 24, 2015 - CVE-2015-8213 ---------------------------------- +November 24, 2015 - :cve:`2015-8213` +------------------------------------ -`CVE-2015-8213 `_: -Settings leak possibility in ``date`` template filter. -`Full description `__ +Settings leak possibility in ``date`` template filter. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -691,24 +720,24 @@ Versions affected * Django 1.8 `(patch) `__ * Django 1.7 `(patch) `__ -February 1, 2016 - CVE-2016-2048 --------------------------------- +February 1, 2016 - :cve:`2016-2048` +----------------------------------- -`CVE-2016-2048 `_: -User with "change" but not "add" permission can create objects for ``ModelAdmin``’s with ``save_as=True``. -`Full description `__ +User with "change" but not "add" permission can create objects for +``ModelAdmin``’s with ``save_as=True``. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ * Django 1.9 `(patch) `__ -March 1, 2016 - CVE-2016-2512 ------------------------------ +March 1, 2016 - :cve:`2016-2512` +-------------------------------- -`CVE-2016-2512 `_: -Malicious redirect and possible XSS attack via user-supplied redirect URLs containing basic auth. -`Full description `__ +Malicious redirect and possible XSS attack via user-supplied redirect URLs +containing basic auth. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -716,12 +745,12 @@ Versions affected * Django 1.9 `(patch) `__ * Django 1.8 `(patch) `__ -March 1, 2016 - CVE-2016-2513 ------------------------------ +March 1, 2016 - :cve:`2016-2513` +-------------------------------- -`CVE-2016-2513 `_: -User enumeration through timing difference on password hasher work factor upgrade. -`Full description `__ +User enumeration through timing difference on password hasher work factor +upgrade. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -729,12 +758,11 @@ Versions affected * Django 1.9 `(patch) `__ * Django 1.8 `(patch) `__ -July 18, 2016 - CVE-2016-6186 ------------------------------ +July 18, 2016 - :cve:`2016-6186` +-------------------------------- -`CVE-2016-6186 `_: -XSS in admin's add/change related popup. -`Full description `__ +XSS in admin's add/change related popup. `Full description +`__ Versions affected ~~~~~~~~~~~~~~~~~ From 48b1e9fb1746569b13f84158f98c47a557a92ec4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 26 Sep 2016 18:01:19 -0400 Subject: [PATCH 714/756] [1.9.x] Added CVE-2016-7401 to the security release archive. Backport of 6fe846a8f08dc959003f298b5407e321c6fe3735 from master --- docs/releases/security.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 8a9d73de3638..898b7f3c3099 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -769,3 +769,15 @@ Versions affected * Django 1.9 `(patch) `__ * Django 1.8 `(patch) `__ + +September 26, 2016 - :cve:`2016-7401` +------------------------------------- + +CSRF protection bypass on a site with Google Analytics. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 1.9 `(patch) `__ +* Django 1.8 `(patch) `__ From 108613eab7a96bcf7c5568e30066e0316aac132e Mon Sep 17 00:00:00 2001 From: Alasdair Nicol Date: Wed, 28 Sep 2016 11:50:49 +0100 Subject: [PATCH 715/756] [1.9.x] Fixed 27283 -- Fixed typo in 1.8 release notes. Backport of 32031718320e1b4d708b15d8c67738e4c77c9bc7 from master --- docs/releases/1.8.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 7393c858f6f4..a109c5415f1e 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -1190,7 +1190,7 @@ Miscellaneous Be careful if you upgrade to Django 1.8 and skip Django 1.7. If you run ``manage.py migrate --fake``, this migration will be skipped and you'll see a ``RuntimeError: Error creating new content types.`` exception because the - ``name`` column won't be dropped from the database. Use ``migrate.py migrate + ``name`` column won't be dropped from the database. Use ``manage.py migrate --fake-initial`` to fake only the initial migration instead. * The new :option:`migrate --fake-initial` option allows faking initial From f0b55f160f908b1944e98fcf22289fde2b2f67a0 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 30 Sep 2016 09:07:10 -0400 Subject: [PATCH 716/756] [1.9.x] Relaxed a test for PostGIS 2.3. Backport of 79c91070e5797f647347c2f8bdfc4c7a0f835fb3 from master --- tests/gis_tests/geoapp/test_functions.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py index 13b49db1b583..841c7d503a5f 100644 --- a/tests/gis_tests/geoapp/test_functions.py +++ b/tests/gis_tests/geoapp/test_functions.py @@ -153,13 +153,16 @@ def test_assvg(self): @skipUnlessDBFeature("has_BoundingCircle_function") def test_bounding_circle(self): + # The weak precision in the assertions is because the BoundingCircle + # calculation changed on PostGIS 2.3. qs = Country.objects.annotate(circle=functions.BoundingCircle('mpoly')).order_by('name') - self.assertAlmostEqual(qs[0].circle.area, 168.89, 2) - self.assertAlmostEqual(qs[1].circle.area, 135.95, 2) + self.assertAlmostEqual(qs[0].circle.area, 169, 0) + self.assertAlmostEqual(qs[1].circle.area, 136, 0) qs = Country.objects.annotate(circle=functions.BoundingCircle('mpoly', num_seg=12)).order_by('name') - self.assertAlmostEqual(qs[0].circle.area, 168.44, 2) - self.assertAlmostEqual(qs[1].circle.area, 135.59, 2) + self.assertGreater(qs[0].circle.area, 168.4, 0) + self.assertLess(qs[0].circle.area, 169.5, 0) + self.assertAlmostEqual(qs[1].circle.area, 136, 0) @skipUnlessDBFeature("has_Centroid_function") def test_centroid(self): From 69a2d0c5bff7e67ec00910491a3939982f75c466 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 3 Oct 2016 16:04:30 -0400 Subject: [PATCH 717/756] [1.9.x] Fixed #27307 -- Added missing url names in sitemaps docs. Backport of fe1aee6b98d2a94ecc983463938135d192ef9afc from master --- docs/ref/contrib/sitemaps.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index a5eac078244c..8fb6a4d4b5a5 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -366,7 +366,8 @@ Here's what the relevant URLconf lines would look like for the example above:: urlpatterns = [ url(r'^sitemap\.xml$', views.index, {'sitemaps': sitemaps}), - url(r'^sitemap-(?P
    .+)\.xml$', views.sitemap, {'sitemaps': sitemaps}), + url(r'^sitemap-(?P
    .+)\.xml$', views.sitemap, {'sitemaps': sitemaps}, + name='django.contrib.sitemaps.views.sitemap'), ] This will automatically generate a :file:`sitemap.xml` file that references @@ -412,7 +413,7 @@ parameter to the ``sitemap`` and ``index`` views via the URLconf:: url(r'^custom-sitemap-(?P
    .+)\.xml$', views.sitemap, { 'sitemaps': sitemaps, 'template_name': 'custom_sitemap.html' - }), + }, name='django.contrib.sitemaps.views.sitemap'), ] From f0dd9dd72cdaa848dfddd83e5f9539d2ba712470 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 11 Oct 2016 19:31:13 -0400 Subject: [PATCH 718/756] [1.9.x] Fixed nonexistent tmc.edu domain in GeoIP test. The test was silently skipped. Backport of e1f6eba033abea25cd6b0577bd66c655ec453325 from master --- tests/gis_tests/test_geoip.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/gis_tests/test_geoip.py b/tests/gis_tests/test_geoip.py index e95fef44964e..b8a906075b1b 100644 --- a/tests/gis_tests/test_geoip.py +++ b/tests/gis_tests/test_geoip.py @@ -30,8 +30,8 @@ "GeoIP is required along with the GEOIP_PATH setting.") @ignore_warnings(category=RemovedInDjango20Warning) class GeoIPTest(unittest.TestCase): - addr = '128.249.1.1' - fqdn = 'tmc.edu' + addr = '162.242.220.127' + fqdn = 'www.djangoproject.com' def _is_dns_available(self, domain): # Naive check to see if there is DNS available to use. @@ -119,12 +119,12 @@ def test04_city(self): # City information dictionary. d = g.city(query) self.assertEqual('USA', d['country_code3']) - self.assertEqual('Houston', d['city']) + self.assertEqual('San Antonio', d['city']) self.assertEqual('TX', d['region']) - self.assertEqual(713, d['area_code']) + self.assertEqual(210, d['area_code']) geom = g.geos(query) self.assertIsInstance(geom, GEOSGeometry) - lon, lat = (-95.4010, 29.7079) + lon, lat = (-98, 29) lat_lon = g.lat_lon(query) lat_lon = (lat_lon[1], lat_lon[0]) for tup in (geom.tuple, g.coords(query), g.lon_lat(query), lat_lon): From 9c956ff2f559e7074b5e49449a16857e9c47e3bd Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 13 Oct 2016 11:02:02 -0400 Subject: [PATCH 719/756] [1.9.x] Fixed #27342 -- Corrected QuerySet.update_or_create() example. Backport of 51b83d9e5113ea5b81d04f4d117bd5acd3c1b822 from master --- docs/ref/models/querysets.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 9d9c312466ae..5834e3d3e202 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1793,21 +1793,25 @@ the given ``kwargs``. If a match is found, it updates the fields passed in the This is meant as a shortcut to boilerplatish code. For example:: + defaults = {'first_name': 'Bob'} try: obj = Person.objects.get(first_name='John', last_name='Lennon') - for key, value in updated_values.iteritems(): + for key, value in defaults.items(): setattr(obj, key, value) obj.save() except Person.DoesNotExist: - updated_values.update({'first_name': 'John', 'last_name': 'Lennon'}) - obj = Person(**updated_values) + new_values = {'first_name': 'John', 'last_name': 'Lennon'} + new_values.update(defaults) + obj = Person(**new_values) obj.save() This pattern gets quite unwieldy as the number of fields in a model goes up. The above example can be rewritten using ``update_or_create()`` like so:: obj, created = Person.objects.update_or_create( - first_name='John', last_name='Lennon', defaults=updated_values) + first_name='John', last_name='Lennon', + defaults={'first_name': 'Bob'}, + ) For detailed description how names passed in ``kwargs`` are resolved see :meth:`get_or_create`. From 2ed85c18730fc8f1f527ba3dcbe0191f9a1b7564 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 24 Oct 2016 11:56:31 -0400 Subject: [PATCH 720/756] [1.9.x] Added stub release notes for 1.9.11/1.8.16. --- docs/releases/1.8.16.txt | 7 +++++++ docs/releases/1.9.11.txt | 7 +++++++ docs/releases/index.txt | 2 ++ 3 files changed, 16 insertions(+) create mode 100644 docs/releases/1.8.16.txt create mode 100644 docs/releases/1.9.11.txt diff --git a/docs/releases/1.8.16.txt b/docs/releases/1.8.16.txt new file mode 100644 index 000000000000..b6503403302e --- /dev/null +++ b/docs/releases/1.8.16.txt @@ -0,0 +1,7 @@ +=========================== +Django 1.8.16 release notes +=========================== + +*November 1, 2016* + +Django 1.8.16 fixes two security issues in 1.8.15. diff --git a/docs/releases/1.9.11.txt b/docs/releases/1.9.11.txt new file mode 100644 index 000000000000..664a52d1a24b --- /dev/null +++ b/docs/releases/1.9.11.txt @@ -0,0 +1,7 @@ +=========================== +Django 1.9.11 release notes +=========================== + +*November 1, 2016* + +Django 1.9.11 fixes two security issues in 1.9.10. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index b9cce6a60848..ec5f30a4f135 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.9.11 1.9.10 1.9.9 1.9.8 @@ -42,6 +43,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.8.16 1.8.15 1.8.14 1.8.13 From 4844d86c7728c1a5a3bbce4ad336a8d32304072b Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Mon, 24 Oct 2016 15:22:00 -0400 Subject: [PATCH 721/756] [1.9.x] Fixed CVE-2016-9013 -- Generated a random database user password when running tests on Oracle. This is a security fix. --- django/db/backends/oracle/creation.py | 16 ++++++++++++---- docs/ref/settings.txt | 7 ++++++- docs/releases/1.8.16.txt | 14 ++++++++++++++ docs/releases/1.9.11.txt | 14 ++++++++++++++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index 01b4056b4b73..18653c39aad7 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -4,11 +4,11 @@ from django.conf import settings from django.db.backends.base.creation import BaseDatabaseCreation from django.db.utils import DatabaseError +from django.utils.crypto import get_random_string from django.utils.functional import cached_property from django.utils.six.moves import input TEST_DATABASE_PREFIX = 'test_' -PASSWORD = 'Im_a_lumberjack' class DatabaseCreation(BaseDatabaseCreation): @@ -223,7 +223,11 @@ def _create_test_user(self, cursor, parameters, verbosity, keepdb=False): ] # Ignore "user already exists" error when keepdb is on acceptable_ora_err = 'ORA-01920' if keepdb else None - self._execute_allow_fail_statements(cursor, statements, parameters, verbosity, acceptable_ora_err) + success = self._execute_allow_fail_statements(cursor, statements, parameters, verbosity, acceptable_ora_err) + # If the password was randomly generated, change the user accordingly. + if not success and self._test_settings_get('PASSWORD') is None: + set_password = "ALTER USER %(user)s IDENTIFIED BY %(password)s" + self._execute_statements(cursor, [set_password], parameters, verbosity) # Most test-suites can be run without the create-view privilege. But some need it. extra = "GRANT CREATE VIEW TO %(user)s" success = self._execute_allow_fail_statements(cursor, [extra], parameters, verbosity, 'ORA-01031') @@ -298,7 +302,7 @@ def _test_settings_get(self, key, default=None, prefixed=None): """ settings_dict = self.connection.settings_dict val = settings_dict['TEST'].get(key, default) - if val is None: + if val is None and prefixed: val = TEST_DATABASE_PREFIX + settings_dict[prefixed] return val @@ -315,7 +319,11 @@ def _test_database_user(self): return self._test_settings_get('USER', prefixed='USER') def _test_database_passwd(self): - return self._test_settings_get('PASSWORD', default=PASSWORD) + password = self._test_settings_get('PASSWORD') + if password is None and self._test_user_create(): + # Oracle passwords are limited to 30 chars and can't contain symbols. + password = get_random_string(length=30) + return password def _test_database_tblspace(self): return self._test_settings_get('TBLSPACE', prefixed='USER') diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index a139f21eba65..63aca2e97803 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -814,7 +814,12 @@ Default: ``None`` This is an Oracle-specific setting. The password to use when connecting to the Oracle database that will be used -when running tests. If not provided, Django will use a hardcoded default value. +when running tests. If not provided, Django will generate a random password. + +.. versionchanged:: 1.9.11 + + Older versions used a hardcoded default password. This was also changed + in 1.8.16 to fix possible security implications. .. setting:: TEST_TBLSPACE diff --git a/docs/releases/1.8.16.txt b/docs/releases/1.8.16.txt index b6503403302e..aa5d9cccea1f 100644 --- a/docs/releases/1.8.16.txt +++ b/docs/releases/1.8.16.txt @@ -5,3 +5,17 @@ Django 1.8.16 release notes *November 1, 2016* Django 1.8.16 fixes two security issues in 1.8.15. + +User with hardcoded password created when running tests on Oracle +================================================================= + +When running tests with an Oracle database, Django creates a temporary database +user. In older versions, if a password isn't manually specified in the database +settings ``TEST`` dictionary, a hardcoded password is used. This could allow +an attacker with network access to the database server to connect. + +This user is usually dropped after the test suite completes, but not when using +the ``manage.py test --keepdb`` option or if the user has an active session +(such as an attacker's connection). + +A randomly generated password is now used for each test run. diff --git a/docs/releases/1.9.11.txt b/docs/releases/1.9.11.txt index 664a52d1a24b..3c29187e864a 100644 --- a/docs/releases/1.9.11.txt +++ b/docs/releases/1.9.11.txt @@ -5,3 +5,17 @@ Django 1.9.11 release notes *November 1, 2016* Django 1.9.11 fixes two security issues in 1.9.10. + +User with hardcoded password created when running tests on Oracle +================================================================= + +When running tests with an Oracle database, Django creates a temporary database +user. In older versions, if a password isn't manually specified in the database +settings ``TEST`` dictionary, a hardcoded password is used. This could allow +an attacker with network access to the database server to connect. + +This user is usually dropped after the test suite completes, but not when using +the ``manage.py test --keepdb`` option or if the user has an active session +(such as an attacker's connection). + +A randomly generated password is now used for each test run. From 45acd6d836895a4c36575f48b3fb36a3dae98d19 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 17 Oct 2016 12:14:49 -0400 Subject: [PATCH 722/756] [1.9.x] Fixed CVE-2016-9014 -- Validated Host header when DEBUG=True. This is a security fix. --- django/http/request.py | 9 +++++---- docs/ref/settings.txt | 10 +++++++--- docs/releases/1.8.16.txt | 22 ++++++++++++++++++++++ docs/releases/1.9.11.txt | 22 ++++++++++++++++++++++ tests/requests/tests.py | 29 +++++++++++++++-------------- 5 files changed, 71 insertions(+), 21 deletions(-) diff --git a/django/http/request.py b/django/http/request.py index 22405d8306e8..cec2add58e27 100644 --- a/django/http/request.py +++ b/django/http/request.py @@ -92,12 +92,13 @@ def get_host(self): """Return the HTTP host using the environment or request headers.""" host = self._get_raw_host() - # There is no hostname validation when DEBUG=True - if settings.DEBUG: - return host + # Allow variants of localhost if ALLOWED_HOSTS is empty and DEBUG=True. + allowed_hosts = settings.ALLOWED_HOSTS + if settings.DEBUG and not allowed_hosts: + allowed_hosts = ['localhost', '127.0.0.1', '[::1]'] domain, port = split_domain_port(host) - if domain and validate_host(domain, settings.ALLOWED_HOSTS): + if domain and validate_host(domain, allowed_hosts): return host else: msg = "Invalid HTTP_HOST header: %r." % host diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 63aca2e97803..ba5ae2e351fb 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -90,14 +90,18 @@ If the ``Host`` header (or ``X-Forwarded-Host`` if list, the :meth:`django.http.HttpRequest.get_host()` method will raise :exc:`~django.core.exceptions.SuspiciousOperation`. -When :setting:`DEBUG` is ``True`` or when running tests, host validation is -disabled; any host will be accepted. Thus it's usually only necessary to set it -in production. +When :setting:`DEBUG` is ``True`` and ``ALLOWED_HOSTS`` is empty, the host +is validated against ``['localhost', '127.0.0.1', '[::1]']``. This validation only applies via :meth:`~django.http.HttpRequest.get_host()`; if your code accesses the ``Host`` header directly from ``request.META`` you are bypassing this security protection. +.. versionchanged:: 1.9.11 + + In older versions, ``ALLOWED_HOSTS`` wasn't checked if ``DEBUG=True``. + This was also changed in Django 1.8.16 to prevent a DNS rebinding attack. + .. setting:: ALLOWED_INCLUDE_ROOTS ALLOWED_INCLUDE_ROOTS diff --git a/docs/releases/1.8.16.txt b/docs/releases/1.8.16.txt index aa5d9cccea1f..9cd82d8d7acd 100644 --- a/docs/releases/1.8.16.txt +++ b/docs/releases/1.8.16.txt @@ -19,3 +19,25 @@ the ``manage.py test --keepdb`` option or if the user has an active session (such as an attacker's connection). A randomly generated password is now used for each test run. + +DNS rebinding vulnerability when ``DEBUG=True`` +=============================================== + +Older versions of Django don't validate the ``Host`` header against +``settings.ALLOWED_HOSTS`` when ``settings.DEBUG=True``. This makes them +vulnerable to a `DNS rebinding attack +`_. + +While Django doesn't ship a module that allows remote code execution, this is +at least a cross-site scripting vector, which could be quite serious if +developers load a copy of the production database in development or connect to +some production services for which there's no development instance, for +example. If a project uses a package like the ``django-debug-toolbar``, then +the attacker could execute arbitrary SQL, which could be especially bad if the +developers connect to the database with a superuser account. + +``settings.ALLOWED_HOSTS`` is now validated regardless of ``DEBUG``. For +convenience, if ``ALLOWED_HOSTS`` is empty and ``DEBUG=True``, the following +variations of localhost are allowed ``['localhost', '127.0.0.1', '::1']``. If +your local settings file has your production ``ALLOWED_HOSTS`` value, you must +now omit it to get those fallback values. diff --git a/docs/releases/1.9.11.txt b/docs/releases/1.9.11.txt index 3c29187e864a..4a7b3ba08678 100644 --- a/docs/releases/1.9.11.txt +++ b/docs/releases/1.9.11.txt @@ -19,3 +19,25 @@ the ``manage.py test --keepdb`` option or if the user has an active session (such as an attacker's connection). A randomly generated password is now used for each test run. + +DNS rebinding vulnerability when ``DEBUG=True`` +=============================================== + +Older versions of Django don't validate the ``Host`` header against +``settings.ALLOWED_HOSTS`` when ``settings.DEBUG=True``. This makes them +vulnerable to a `DNS rebinding attack +`_. + +While Django doesn't ship a module that allows remote code execution, this is +at least a cross-site scripting vector, which could be quite serious if +developers load a copy of the production database in development or connect to +some production services for which there's no development instance, for +example. If a project uses a package like the ``django-debug-toolbar``, then +the attacker could execute arbitrary SQL, which could be especially bad if the +developers connect to the database with a superuser account. + +``settings.ALLOWED_HOSTS`` is now validated regardless of ``DEBUG``. For +convenience, if ``ALLOWED_HOSTS`` is empty and ``DEBUG=True``, the following +variations of localhost are allowed ``['localhost', '127.0.0.1', '::1']``. If +your local settings file has your production ``ALLOWED_HOSTS`` value, you must +now omit it to get those fallback values. diff --git a/tests/requests/tests.py b/tests/requests/tests.py index c0a48d610cc9..772ddc51f7c3 100644 --- a/tests/requests/tests.py +++ b/tests/requests/tests.py @@ -709,21 +709,22 @@ def test_get_port_with_x_forwarded_port(self): self.assertEqual(request.get_port(), '8080') @override_settings(DEBUG=True, ALLOWED_HOSTS=[]) - def test_host_validation_disabled_in_debug_mode(self): - """If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass.""" - request = HttpRequest() - request.META = { - 'HTTP_HOST': 'example.com', - } - self.assertEqual(request.get_host(), 'example.com') + def test_host_validation_in_debug_mode(self): + """ + If ALLOWED_HOSTS is empty and DEBUG is True, variants of localhost are + allowed. + """ + valid_hosts = ['localhost', '127.0.0.1', '[::1]'] + for host in valid_hosts: + request = HttpRequest() + request.META = {'HTTP_HOST': host} + self.assertEqual(request.get_host(), host) - # Invalid hostnames would normally raise a SuspiciousOperation, - # but we have DEBUG=True, so this check is disabled. - request = HttpRequest() - request.META = { - 'HTTP_HOST': "invalid_hostname.com", - } - self.assertEqual(request.get_host(), "invalid_hostname.com") + # Other hostnames raise a SuspiciousOperation. + with self.assertRaises(SuspiciousOperation): + request = HttpRequest() + request.META = {'HTTP_HOST': 'example.com'} + request.get_host() @override_settings(ALLOWED_HOSTS=[]) def test_get_host_suggestion_of_allowed_host(self): From 52db0d5742777a77717df5b1e85d056910a2515d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Nov 2016 09:37:53 -0400 Subject: [PATCH 723/756] [1.9.x] Bumped version for 1.9.11 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index f21647c01cc1..8a83ff2c44d9 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 11, 'alpha', 0) +VERSION = (1, 9, 11, 'final', 0) __version__ = get_version(VERSION) From a7710f622ad5483e79900c5eaaa716c1c4210e92 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Nov 2016 10:22:43 -0400 Subject: [PATCH 724/756] [1.9.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 8a83ff2c44d9..98dfceff0ff7 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 11, 'final', 0) +VERSION = (1, 9, 12, 'alpha', 0) __version__ = get_version(VERSION) From 734ef022a17180288a2ea65e2319676692bc9926 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Nov 2016 10:48:11 -0400 Subject: [PATCH 725/756] [1.9.x] Added CVE-2016-9013,14 to the security release archive. Backport of b8ae2c16cfc4bf88c1720eafd8e35438181a7413 from master --- docs/releases/security.txt | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 898b7f3c3099..171e19d85e7b 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -781,3 +781,29 @@ Versions affected * Django 1.9 `(patch) `__ * Django 1.8 `(patch) `__ + +November 1, 2016 - :cve:`2016-9013` +----------------------------------- + +User with hardcoded password created when running tests on Oracle. `Full +description `__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 1.10 `(patch) `__ +* Django 1.9 `(patch) `__ +* Django 1.8 `(patch) `__ + +November 1, 2016 - :cve:`2016-9014` +----------------------------------- + +DNS rebinding vulnerability when ``DEBUG=True``. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 1.10 `(patch) `__ +* Django 1.9 `(patch) `__ +* Django 1.8 `(patch) `__ From a3fa2c463122974460adcab91defb479841de745 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 2 Nov 2016 14:44:15 +0100 Subject: [PATCH 726/756] [1.9.x] Fixed #27420 -- Quoted the Oracle test user password in queries. Backport of c4b04e1598c4325454c808183dce17b284ed9e28 from master --- django/db/backends/oracle/creation.py | 4 ++-- docs/ref/databases.txt | 1 + docs/releases/1.8.17.txt | 14 ++++++++++++++ docs/releases/1.9.12.txt | 14 ++++++++++++++ docs/releases/index.txt | 2 ++ 5 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 docs/releases/1.8.17.txt create mode 100644 docs/releases/1.9.12.txt diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index 18653c39aad7..aa5279fdce1e 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -209,7 +209,7 @@ def _create_test_user(self, cursor, parameters, verbosity, keepdb=False): print("_create_test_user(): username = %s" % parameters['user']) statements = [ """CREATE USER %(user)s - IDENTIFIED BY %(password)s + IDENTIFIED BY "%(password)s" DEFAULT TABLESPACE %(tblspace)s TEMPORARY TABLESPACE %(tblspace_temp)s QUOTA UNLIMITED ON %(tblspace)s @@ -226,7 +226,7 @@ def _create_test_user(self, cursor, parameters, verbosity, keepdb=False): success = self._execute_allow_fail_statements(cursor, statements, parameters, verbosity, acceptable_ora_err) # If the password was randomly generated, change the user accordingly. if not success and self._test_settings_get('PASSWORD') is None: - set_password = "ALTER USER %(user)s IDENTIFIED BY %(password)s" + set_password = 'ALTER USER %(user)s IDENTIFIED BY "%(password)s"' self._execute_statements(cursor, [set_password], parameters, verbosity) # Most test-suites can be run without the create-view privilege. But some need it. extra = "GRANT CREATE VIEW TO %(user)s" diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index fda7a0af6e6d..9c726697d5c5 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -750,6 +750,7 @@ To run a project's test suite, the user usually needs these *additional* privileges: * CREATE USER +* ALTER USER * DROP USER * CREATE TABLESPACE * DROP TABLESPACE diff --git a/docs/releases/1.8.17.txt b/docs/releases/1.8.17.txt new file mode 100644 index 000000000000..2464d155da48 --- /dev/null +++ b/docs/releases/1.8.17.txt @@ -0,0 +1,14 @@ +=========================== +Django 1.8.17 release notes +=========================== + +*Under development* + +Django 1.8.17 fixes a regression in 1.8.16. + +Bugfixes +======== + +* Quoted the Oracle test user's password in queries to fix the "ORA-00922: + missing or invalid option" error when the password starts with a number or + special character (:ticket:`27420`). diff --git a/docs/releases/1.9.12.txt b/docs/releases/1.9.12.txt new file mode 100644 index 000000000000..d8a8af8d9dae --- /dev/null +++ b/docs/releases/1.9.12.txt @@ -0,0 +1,14 @@ +=========================== +Django 1.9.12 release notes +=========================== + +*Under development* + +Django 1.9.12 fixes a regression in 1.9.11. + +Bugfixes +======== + +* Quoted the Oracle test user's password in queries to fix the "ORA-00922: + missing or invalid option" error when the password starts with a number or + special character (:ticket:`27420`). diff --git a/docs/releases/index.txt b/docs/releases/index.txt index ec5f30a4f135..a628ee18759a 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.9.12 1.9.11 1.9.10 1.9.9 @@ -43,6 +44,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.8.17 1.8.16 1.8.15 1.8.14 From 0ed5c94156c0434cbc28dc9ed1c5119659a09f36 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 14 Nov 2016 18:22:39 -0500 Subject: [PATCH 727/756] [1.9.x] Ignored flake8's newly added E305, E741, E743. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9ba33169ae72..fc8350eeae95 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,7 +4,7 @@ install-script = scripts/rpm-install.sh [flake8] exclude = build,.git,./django/utils/lru_cache.py,./django/utils/six.py,./django/conf/app_template/*,./django/dispatch/weakref_backports.py,./tests/.env,./xmlrunner,tests/view_tests/tests/py3_test_debug.py,tests/template_tests/annotated_tag_function.py -ignore = E123,E128,E402,W503,E731,W601 +ignore = E123,E128,E305,E402,W503,E731,E741,E743,W601 max-line-length = 119 [isort] From 82701c3c686e9c3d4fd055ae6e0edadb8829779b Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Tue, 15 Nov 2016 08:25:02 -0500 Subject: [PATCH 728/756] [1.9.x] Refs #25284 -- Corrected an obsolete implicit __in lookup example. Thanks IRC alias rpkilby for the report. Backport of 9e4fd3301d0a4e40cab8df4b51a2b04274f61da0 from master --- docs/topics/db/queries.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 4c07f2182999..64ef6e08cd07 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -597,7 +597,7 @@ may not be the same as the entries in the first filter. We are filtering the that were published in 2008, you need to make two queries:: Blog.objects.exclude( - entry=Entry.objects.filter( + entry__in=Entry.objects.filter( headline__contains='Lennon', pub_date__year=2008, ), From 0ee4c0f4812aa12e9c13ed01c92425f46e0221c6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Dec 2016 17:14:22 -0500 Subject: [PATCH 729/756] [1.9.x] Added release dates for 1.9.12, 1.8.17. Backport of 9ea9686532336caefcd5fedb76ad9a68a512d243 from master --- docs/releases/1.8.17.txt | 2 +- docs/releases/1.9.12.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/1.8.17.txt b/docs/releases/1.8.17.txt index 2464d155da48..fc45b8a92461 100644 --- a/docs/releases/1.8.17.txt +++ b/docs/releases/1.8.17.txt @@ -2,7 +2,7 @@ Django 1.8.17 release notes =========================== -*Under development* +*December 1, 2016* Django 1.8.17 fixes a regression in 1.8.16. diff --git a/docs/releases/1.9.12.txt b/docs/releases/1.9.12.txt index d8a8af8d9dae..565f378610a2 100644 --- a/docs/releases/1.9.12.txt +++ b/docs/releases/1.9.12.txt @@ -2,7 +2,7 @@ Django 1.9.12 release notes =========================== -*Under development* +*December 1, 2016* Django 1.9.12 fixes a regression in 1.9.11. From 448fcd66e6ffce0d000d38a07e6d61823fa14107 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Dec 2016 17:24:41 -0500 Subject: [PATCH 730/756] [1.9.x] Bumped version for 1.9.12 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 98dfceff0ff7..47a5b6986955 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 12, 'alpha', 0) +VERSION = (1, 9, 12, 'final', 0) __version__ = get_version(VERSION) From 61ad2287a8991f020011ed5cac76c44e856becc5 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Dec 2016 18:18:00 -0500 Subject: [PATCH 731/756] [1.9.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 47a5b6986955..9da5e1834e60 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 12, 'final', 0) +VERSION = (1, 9, 13, 'alpha', 0) __version__ = get_version(VERSION) From 899f53e5da8213ca7cb3d8f63fbb43467adaf9db Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Mon, 5 Dec 2016 17:31:25 -0500 Subject: [PATCH 732/756] [1.9.x] Fixed unrendered rows in GIS DB functions table in docs. Cells in the first column must contain some text. http://docutils.sourceforge.net/docs/dev/rst/problems.html#tables Backport of d4822148d09d715b742d290383fe642c0b77faba from master --- docs/ref/contrib/gis/functions.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt index 6f2af37f1b3c..f12de6fd5ecc 100644 --- a/docs/ref/contrib/gis/functions.txt +++ b/docs/ref/contrib/gis/functions.txt @@ -30,9 +30,9 @@ Measurement Relationships Operations Editors :class:`Length` :class:`Envelope` :class:`SymDifference` :class:`Scale` :class:`AsKML` :class:`NumPoints` :class:`Perimeter` :class:`PointOnSurface` :class:`Union` :class:`SnapToGrid` :class:`AsSVG` - :class:`Transform` :class:`GeoHash` +.. :class:`Transform` :class:`GeoHash` - :class:`Translate` +.. :class:`Translate` ================== ======================= ====================== =================== ================== ===================== ``Area`` From 282f27aed5f1b3ee6502b0c243575393163f1a1b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 15 Dec 2016 13:01:00 -0500 Subject: [PATCH 733/756] [1.9.x] Corrected docs claiming AsGML is supported on Oracle. Backport of ebb85d1e790bb697bff3e0a101c87c5f9c6dff7c from master --- docs/ref/contrib/gis/db-api.txt | 2 +- docs/ref/contrib/gis/functions.txt | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index dde9c5e4ea34..2b10ffbd5a9c 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -307,7 +307,7 @@ Function PostGIS Oracle MySQL SpatiaLite ==================================== ======= ====== =========== ========== :class:`Area` X X X X :class:`AsGeoJSON` X X -:class:`AsGML` X X X +:class:`AsGML` X X :class:`AsKML` X X :class:`AsSVG` X X :class:`BoundingCircle` X diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt index f12de6fd5ecc..89cc4182c05d 100644 --- a/docs/ref/contrib/gis/functions.txt +++ b/docs/ref/contrib/gis/functions.txt @@ -84,7 +84,7 @@ Keyword Argument Description .. class:: AsGML(expression, version=2, precision=8, **extra) -*Availability*: Oracle, PostGIS, SpatiaLite (≥ 2.4.0-RC4) +*Availability*: PostGIS, SpatiaLite (≥ 2.4.0-RC4) Accepts a single geographic field or expression and returns a `Geographic Markup Language (GML)`__ representation of the geometry. @@ -99,13 +99,12 @@ Example:: ===================== ===================================================== Keyword Argument Description ===================== ===================================================== -``precision`` Not used on Oracle. It may be used to specify the number - of significant digits for the coordinates in the GML - representation -- the default value is 8. +``precision`` It may be used to specify the number of significant + digits for the coordinates in the GML representation -- + the default value is 8. -``version`` Not used on Oracle. It may be used to specify the GML - version used, and may only be values of 2 or 3. The - default value is 2. +``version`` It may be used to specify the GML version used, and may + only be values of 2 or 3. The default value is 2. ===================== ===================================================== __ https://en.wikipedia.org/wiki/Geography_Markup_Language From 7418690bf71da5ce9b6bdab6086515c2763b5820 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 20 Dec 2016 08:29:12 -0500 Subject: [PATCH 734/756] [1.9.x] Fixed #27616 -- Fixed incorrect vary_on_headers() example. Backport of 5e239ae907291d07a3fcf9329f83c27fa6d72981 from master --- docs/ref/request-response.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 564a36cf135d..3eabbae1b64f 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -352,7 +352,7 @@ Methods If a response varies on whether or not it's requested via AJAX and you are using some form of caching like Django's :mod:`cache middleware `, you should decorate the view with - :func:`vary_on_headers('HTTP_X_REQUESTED_WITH') + :func:`vary_on_headers('X-Requested-With') ` so that the responses are properly cached. From 50830aa1d93a1a703a5c2c546f730c1afb3f4a0a Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 27 Dec 2016 15:29:11 +0100 Subject: [PATCH 735/756] [1.9.x] Fixed #27637 -- Fixed timesince, timeuntil in leap year edge case. Backport of 3e5c5e6754648590e87725b66d11d283079728e6 from master --- django/utils/timesince.py | 9 +++++++-- docs/releases/1.9.13.txt | 13 +++++++++++++ docs/releases/index.txt | 1 + tests/utils_tests/test_timesince.py | 6 ++++++ 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 docs/releases/1.9.13.txt diff --git a/django/utils/timesince.py b/django/utils/timesince.py index 06accab2e040..1bf24809d49e 100644 --- a/django/utils/timesince.py +++ b/django/utils/timesince.py @@ -40,10 +40,15 @@ def timesince(d, now=None, reversed=False): if not now: now = datetime.datetime.now(utc if is_aware(d) else None) - delta = (d - now) if reversed else (now - d) + if reversed: + d, now = now, d + delta = now - d # Deal with leapyears by subtracing the number of leapdays - delta -= datetime.timedelta(calendar.leapdays(d.year, now.year)) + leapdays = calendar.leapdays(d.year, now.year) + if leapdays != 0 and calendar.isleap(d.year): + leapdays -= 1 + delta -= datetime.timedelta(leapdays) # ignore microseconds since = delta.days * 24 * 60 * 60 + delta.seconds diff --git a/docs/releases/1.9.13.txt b/docs/releases/1.9.13.txt new file mode 100644 index 000000000000..093b40f0572f --- /dev/null +++ b/docs/releases/1.9.13.txt @@ -0,0 +1,13 @@ +=========================== +Django 1.9.13 release notes +=========================== + +*Under development* + +Django 1.9.13 fixes a bug in 1.9.12. + +Bugfixes +======== + +* Fixed a regression in the ``timesince`` and ``timeuntil`` filters that caused + incorrect results for dates in a leap year. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index a628ee18759a..82288e159f9b 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.9.13 1.9.12 1.9.11 1.9.10 diff --git a/tests/utils_tests/test_timesince.py b/tests/utils_tests/test_timesince.py index ea1064f902e3..ecd0dfa336cd 100644 --- a/tests/utils_tests/test_timesince.py +++ b/tests/utils_tests/test_timesince.py @@ -123,6 +123,11 @@ def test_both_date_objects(self): self.assertEqual(timeuntil(today - self.oneday, today), '0\xa0minutes') self.assertEqual(timeuntil(today + self.oneweek, today), '1\xa0week') + def test_leap_year(self): + start_date = datetime.date(2016, 12, 25) + self.assertEqual(timeuntil(start_date + self.oneweek, start_date), '1\xa0week') + self.assertEqual(timesince(start_date, start_date + self.oneweek), '1\xa0week') + def test_naive_datetime_with_tzinfo_attribute(self): class naive(datetime.tzinfo): def utcoffset(self, dt): @@ -135,3 +140,4 @@ def utcoffset(self, dt): def test_thousand_years_ago(self): t = datetime.datetime(1007, 8, 14, 13, 46, 0) self.assertEqual(timesince(t, self.t), '1000\xa0years') + self.assertEqual(timeuntil(self.t, t), '1000\xa0years') From 9e8bfc5c62a40826a136c02d91e276e129551e54 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 31 Dec 2016 14:02:44 -0500 Subject: [PATCH 736/756] [1.9.x] Refs #24154 -- Added check_aggregate_support() to deprecation timeline. Backport of 2b4bb78f7944adbf88f9bf3b868632707c79b5dd 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 28b25270d971..4950d48827a8 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -18,6 +18,9 @@ details on these changes. * The ``weak`` argument to ``django.dispatch.signals.Signal.disconnect()`` will be removed. +* ``django.db.backends.base.BaseDatabaseOperations.check_aggregate_support()`` + will be removed. + * The ``django.forms.extras`` package will be removed. * The ``assignment_tag`` helper will be removed. From 589a091b3bf70a379c0f96dbdce700e6756f4a36 Mon Sep 17 00:00:00 2001 From: vinay karanam Date: Mon, 2 Jan 2017 19:10:44 +0530 Subject: [PATCH 737/756] [1.9.x] Refs #27637 -- Fixed timesince, timeuntil on New Year's Eve in a leap year. Backport of 6128c1736de98d8ab22829184409731b030cbff5 from master --- django/utils/timesince.py | 7 +++++-- docs/releases/1.9.13.txt | 2 +- tests/utils_tests/test_timesince.py | 6 ++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/django/utils/timesince.py b/django/utils/timesince.py index 1bf24809d49e..b0eefaf73437 100644 --- a/django/utils/timesince.py +++ b/django/utils/timesince.py @@ -46,8 +46,11 @@ def timesince(d, now=None, reversed=False): # Deal with leapyears by subtracing the number of leapdays leapdays = calendar.leapdays(d.year, now.year) - if leapdays != 0 and calendar.isleap(d.year): - leapdays -= 1 + if leapdays != 0: + if calendar.isleap(d.year): + leapdays -= 1 + elif calendar.isleap(now.year): + leapdays += 1 delta -= datetime.timedelta(leapdays) # ignore microseconds diff --git a/docs/releases/1.9.13.txt b/docs/releases/1.9.13.txt index 093b40f0572f..03ffd804009f 100644 --- a/docs/releases/1.9.13.txt +++ b/docs/releases/1.9.13.txt @@ -10,4 +10,4 @@ Bugfixes ======== * Fixed a regression in the ``timesince`` and ``timeuntil`` filters that caused - incorrect results for dates in a leap year. + incorrect results for dates in a leap year (:ticket:`27637`). diff --git a/tests/utils_tests/test_timesince.py b/tests/utils_tests/test_timesince.py index ecd0dfa336cd..ee74c3fb4ef1 100644 --- a/tests/utils_tests/test_timesince.py +++ b/tests/utils_tests/test_timesince.py @@ -128,6 +128,12 @@ def test_leap_year(self): self.assertEqual(timeuntil(start_date + self.oneweek, start_date), '1\xa0week') self.assertEqual(timesince(start_date, start_date + self.oneweek), '1\xa0week') + def test_leap_year_new_years_eve(self): + t = datetime.date(2016, 12, 31) + now = datetime.datetime(2016, 12, 31, 18, 0, 0) + self.assertEqual(timesince(t + self.oneday, now), '0\xa0minutes') + self.assertEqual(timeuntil(t - self.oneday, now), '0\xa0minutes') + def test_naive_datetime_with_tzinfo_attribute(self): class naive(datetime.tzinfo): def utcoffset(self, dt): From 906b5dfd6091c21aaf78935d7fc4a7d87d3ef5eb Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 2 Jan 2017 10:38:54 -0500 Subject: [PATCH 738/756] [1.9.x] Corrected unrendered versionadded annotation. --- docs/ref/contrib/postgres/fields.txt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt index a70cebea08a3..9149218a58d8 100644 --- a/docs/ref/contrib/postgres/fields.txt +++ b/docs/ref/contrib/postgres/fields.txt @@ -701,14 +701,13 @@ operators ``@>``, ``<@``, and ``&&`` respectively. >>> Event.objects.filter(ages__contained_by=NumericRange(0, 15)) [] -.. versionadded 1.9 - - The `contained_by` lookup is also available on the non-range field types: - :class:`~django.db.models.fields.IntegerField`, - :class:`~django.db.models.fields.BigIntegerField`, - :class:`~django.db.models.fields.FloatField`, - :class:`~django.db.models.fields.DateField`, and - :class:`~django.db.models.fields.DateTimeField`. For example:: +.. versionadded:: 1.9 + +The ``contained_by`` lookup is also available on the non-range field types: +:class:`~django.db.models.IntegerField`, +:class:`~django.db.models.BigIntegerField`, +:class:`~django.db.models.FloatField`, :class:`~django.db.models.DateField`, +and :class:`~django.db.models.DateTimeField`. For example:: >>> from psycopg2.extras import DateTimeTZRange >>> Event.objects.filter(start__contained_by=DateTimeTZRange( From 29b3bdc25c0fe37c22931181ed19907005923fd6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 13 Jan 2016 11:33:25 -0500 Subject: [PATCH 739/756] [1.9.x] Declared Sphinx extensions safe for parallel reading. Backport of 03306a187ec3bb5e2592728a900d4a41185f9e30 from master --- docs/_ext/djangodocs.py | 1 + docs/_ext/ticket_role.py | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index 4e97ad7ab018..cce682e7b78d 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -63,6 +63,7 @@ def setup(app): man=(visit_snippet_literal, depart_snippet_literal), text=(visit_snippet_literal, depart_snippet_literal), texinfo=(visit_snippet_literal, depart_snippet_literal)) + return {'parallel_read_safe': True} class snippet_with_filename(nodes.literal_block): diff --git a/docs/_ext/ticket_role.py b/docs/_ext/ticket_role.py index b53778558e2f..809b4239b2a7 100644 --- a/docs/_ext/ticket_role.py +++ b/docs/_ext/ticket_role.py @@ -36,3 +36,4 @@ def ticket_role(name, rawtext, text, lineno, inliner, options=None, content=None def setup(app): app.add_config_value('ticket_url', None, 'env') app.add_role('ticket', ticket_role) + return {'parallel_read_safe': True} From e1b8ac5a37d685c3a320bea4446ec54d6d07167d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 5 Dec 2016 14:00:10 -0500 Subject: [PATCH 740/756] [1.9.x] Removed deprecated html_translator_class sphinx config option. Backport of bacdfbf3d1eb23ff8e8110681728a5b467443446 and e5d3f98abcdc20d64d99609bd0aa82331835b439 from master --- docs/_ext/djangodocs.py | 2 ++ docs/conf.py | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index cce682e7b78d..59db23c13cfe 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -63,6 +63,8 @@ def setup(app): man=(visit_snippet_literal, depart_snippet_literal), text=(visit_snippet_literal, depart_snippet_literal), texinfo=(visit_snippet_literal, depart_snippet_literal)) + app.set_translator('djangohtml', DjangoHTMLTranslator) + app.set_translator('json', DjangoHTMLTranslator) return {'parallel_read_safe': True} diff --git a/docs/conf.py b/docs/conf.py index ad5bbe60c7c2..19170f182006 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -192,9 +192,6 @@ def django_release(): # typographically correct entities. html_use_smartypants = True -# HTML translator class for the builder -html_translator_class = "djangodocs.DjangoHTMLTranslator" - # Content template for the index page. # html_index = '' From 39fe2713ffab2d26a770cd61dc2eed11bcf648ed Mon Sep 17 00:00:00 2001 From: Arkadiusz Adamski Date: Mon, 30 Jan 2017 18:07:14 +0100 Subject: [PATCH 741/756] [1.9.x] Fixed incorrect namespace in sitemap example. Backport of 274ca999825bb782bbbddd769783cf2aa91de7f9 from master --- docs/ref/contrib/sitemaps.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index 8fb6a4d4b5a5..6126c0c448ee 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -459,7 +459,7 @@ generate a Google News compatible sitemap: + xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"> {% spaceless %} {% for url in urlset %} From 740cdbea0496e519c1c3ab503c1c68f0e1198579 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 14 Feb 2017 09:05:55 -0500 Subject: [PATCH 742/756] [1.9.x] Fixed GeoIP test failure with the latest data. Backport of da2e92d25ea32a25679d99525365ac7ac8e297ee from stable/1.11.x --- tests/gis_tests/test_geoip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gis_tests/test_geoip.py b/tests/gis_tests/test_geoip.py index b8a906075b1b..1f0a651bccf2 100644 --- a/tests/gis_tests/test_geoip.py +++ b/tests/gis_tests/test_geoip.py @@ -134,7 +134,7 @@ def test04_city(self): def test05_unicode_response(self): "Testing that GeoIP strings are properly encoded, see #16553." g = GeoIP() - fqdn = "hs-duesseldorf.de" + fqdn = "messe-duesseldorf.com" if self._is_dns_available(fqdn): d = g.city(fqdn) self.assertEqual('Düsseldorf', d['city']) From e2a30959a773b7899f5888ef259a62120ae5ba78 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 1 Mar 2017 13:14:35 -0500 Subject: [PATCH 743/756] [1.9.x] Fixed a backends test with psycopg2 2.7. Backport of 49a63d08d3b3e2ac32e391d1413a4ac99429e4af from master --- tests/backends/tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 1bb019bb5589..9af662b6fb32 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -294,6 +294,7 @@ def test_connect_isolation_level(self): """ Regression test for #18130 and #24318. """ + import psycopg2 from psycopg2.extensions import ( ISOLATION_LEVEL_READ_COMMITTED as read_committed, ISOLATION_LEVEL_SERIALIZABLE as serializable, @@ -304,7 +305,8 @@ def test_connect_isolation_level(self): # PostgreSQL is configured with the default isolation level. # Check the level on the psycopg2 connection, not the Django wrapper. - self.assertEqual(connection.connection.isolation_level, read_committed) + default_level = read_committed if psycopg2.__version__ < '2.7' else None + self.assertEqual(connection.connection.isolation_level, default_level) new_connection = connection.copy() new_connection.settings_dict['OPTIONS']['isolation_level'] = serializable From b9ad6b645d16c9bd50cc0546111c7c68cbc0ca54 Mon Sep 17 00:00:00 2001 From: albertoconnor Date: Wed, 1 Mar 2017 15:42:00 -0500 Subject: [PATCH 744/756] [1.9.x] Fixed typo in docs/howto/custom-template-tags.txt. Backport of 2863e79a5c061b58e57ea7edc12701010725537c from master --- docs/howto/custom-template-tags.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt index 08e666d86ce8..0a1175e5f1c2 100644 --- a/docs/howto/custom-template-tags.txt +++ b/docs/howto/custom-template-tags.txt @@ -510,7 +510,7 @@ you see fit: .. code-block:: html+django - {% get_current_time "%Y-%m-%d %I:%M %p" as the_time %} + {% current_time "%Y-%m-%d %I:%M %p" as the_time %}

    The time is {{ the_time }}.

    .. _howto-custom-template-tags-inclusion-tags: From f05219a9d389ebf4c5ba8fc191498ecbcf5eac74 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sat, 11 Mar 2017 21:46:50 +0100 Subject: [PATCH 745/756] [1.9.x] Refs #27924 -- Doc'd that cx_Oracle < 5.3 is required. Thanks Tim Graham for the review. Backport of 46d602dcea624bef3fb6e1dbf71378d837c0957b from stable/1.10.x --- docs/ref/databases.txt | 6 +++--- tests/requirements/oracle.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 9c726697d5c5..7a1844bf8b66 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -722,9 +722,9 @@ rather than a list. SQLite does not support this. Oracle notes ============ -Django supports `Oracle Database Server`_ versions 11.2 and higher. Version -4.3.1 or higher of the `cx_Oracle`_ Python driver is required, although we -recommend version 5.1.3 or later as these versions support Python 3. +Django supports `Oracle Database Server`_ versions 11.2 and higher. Versions +4.3.1 through 5.2.1 of the `cx_Oracle`_ Python driver are supported, although +5.1.3 or later is recommended as these versions support Python 3. Note that due to a Unicode-corruption bug in ``cx_Oracle`` 5.0, that version of the driver should **not** be used with Django; diff --git a/tests/requirements/oracle.txt b/tests/requirements/oracle.txt index ae5b7349cde3..7fc059efdddb 100644 --- a/tests/requirements/oracle.txt +++ b/tests/requirements/oracle.txt @@ -1 +1 @@ -cx_oracle +cx_oracle < 5.3 From 76508a5119f5e39a66d177699b0af57e1a57fa40 Mon Sep 17 00:00:00 2001 From: lb1c Date: Wed, 15 Mar 2017 13:09:48 +0100 Subject: [PATCH 746/756] [1.9.x] Fixed incorrect import in docs/ref/models/expressions.txt. Backport of 36f2262741311159e5c13eb5114fb9e902e2df31 from master --- docs/ref/models/expressions.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/models/expressions.txt b/docs/ref/models/expressions.txt index 051d92d54384..ea69ab922a8f 100644 --- a/docs/ref/models/expressions.txt +++ b/docs/ref/models/expressions.txt @@ -30,8 +30,8 @@ Some examples .. code-block:: python - from django.db.models import F, Count - from django.db.models.functions import Length, Upper, Value + from django.db.models import F, Count, Value + from django.db.models.functions import Length, Upper # Find companies that have more employees than chairs. Company.objects.filter(num_employees__gt=F('num_chairs')) From 14d53d78bebf944260a37da5542817687f02c301 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sat, 25 Mar 2017 15:59:17 -0400 Subject: [PATCH 747/756] [1.9.x] Removed unexpected initial attribute in data migration examples. Inadvertently added in db97a8849519a3933bf4abd2184efd68ebc21965. Backport of 899c42cc8e1820948f4091f815ce7890057c4a81 from master --- AUTHORS | 1 + docs/topics/migrations.txt | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index c7bb9249c453..026a5204ed2d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -670,6 +670,7 @@ answer newbie questions, and generally made Django that much better: Stanislaus Madueke starrynight Stefane Fermgier + Stefano Rivera Stéphane Raimbault Stephan Jaekel Stephen Burrows diff --git a/docs/topics/migrations.txt b/docs/topics/migrations.txt index 8bb292bfc4b1..e2ab65db6863 100644 --- a/docs/topics/migrations.txt +++ b/docs/topics/migrations.txt @@ -456,7 +456,6 @@ Then, open up the file; it should look something like this:: from django.db import migrations, models class Migration(migrations.Migration): - initial = True dependencies = [ ('yourappname', '0001_initial'), @@ -494,7 +493,6 @@ need to do is use the historical model and iterate over the rows:: person.save() class Migration(migrations.Migration): - initial = True dependencies = [ ('yourappname', '0001_initial'), From 80f8d7d19687463fcd4eeb510c2d5936e4da6359 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 28 Mar 2017 10:38:00 -0400 Subject: [PATCH 748/756] [1.9.x] Fixed #27988 -- Fixed typo in docs/ref/django-admin.txt. Backport of ea36e7454c267aab933e92481288e1eab636cd62 from master --- docs/ref/django-admin.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index d0e88fd457b3..6869313d8545 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1595,7 +1595,7 @@ Example usage:: django-admin migrate --traceback -.. django-admin-option:: --verbosity {0,1,2,3}, --v {0,1,2,3} +.. django-admin-option:: --verbosity {0,1,2,3}, -v {0,1,2,3} Specifies the amount of notification and debug information that a command should print to the console. From cb87583c57e2f662c28172a8b3538615ae580c7f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 22 Mar 2017 11:47:45 -0400 Subject: [PATCH 749/756] [1.9.x] Added stub release notes for security releases. --- docs/releases/1.8.18.txt | 7 +++++++ docs/releases/1.9.13.txt | 5 +++-- docs/releases/index.txt | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 docs/releases/1.8.18.txt diff --git a/docs/releases/1.8.18.txt b/docs/releases/1.8.18.txt new file mode 100644 index 000000000000..92492c782507 --- /dev/null +++ b/docs/releases/1.8.18.txt @@ -0,0 +1,7 @@ +=========================== +Django 1.8.18 release notes +=========================== + +*April 4, 2017* + +Django 1.8.18 fixes two security issues in 1.8.17. diff --git a/docs/releases/1.9.13.txt b/docs/releases/1.9.13.txt index 03ffd804009f..42f22a8f00f8 100644 --- a/docs/releases/1.9.13.txt +++ b/docs/releases/1.9.13.txt @@ -2,9 +2,10 @@ Django 1.9.13 release notes =========================== -*Under development* +*April 4, 2017* -Django 1.9.13 fixes a bug in 1.9.12. +Django 1.9.13 fixes two security issues and a bug in 1.9.12. This is the final +release of the 1.9.x series. Bugfixes ======== diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 82288e159f9b..2e252a55cdb6 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -45,6 +45,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.8.18 1.8.17 1.8.16 1.8.15 From 5f1ffb07afc1e59729ce2b283124116d6c0659e4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 14 Mar 2017 12:33:15 -0400 Subject: [PATCH 750/756] [1.9.x] Fixed CVE-2017-7234 -- Fixed open redirect vulnerability in views.static.serve(). This is a security fix. --- django/views/static.py | 22 ++++------------------ docs/releases/1.8.18.txt | 11 +++++++++++ docs/releases/1.9.13.txt | 11 +++++++++++ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/django/views/static.py b/django/views/static.py index 6187f6ffd946..63048a6cffcc 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -12,9 +12,9 @@ from django.http import ( FileResponse, Http404, HttpResponse, HttpResponseNotModified, - HttpResponseRedirect, ) from django.template import Context, Engine, TemplateDoesNotExist, loader +from django.utils._os import safe_join from django.utils.http import http_date, parse_http_date from django.utils.six.moves.urllib.parse import unquote from django.utils.translation import ugettext as _, ugettext_lazy @@ -36,25 +36,11 @@ def serve(request, path, document_root=None, show_indexes=False): but if you'd like to override it, you can create a template called ``static/directory_index.html``. """ - path = posixpath.normpath(unquote(path)) - path = path.lstrip('/') - newpath = '' - for part in path.split('/'): - if not part: - # Strip empty path components. - continue - drive, part = os.path.splitdrive(part) - head, part = os.path.split(part) - if part in (os.curdir, os.pardir): - # Strip '.' and '..' in path. - continue - newpath = os.path.join(newpath, part).replace('\\', '/') - if newpath and path != newpath: - return HttpResponseRedirect(newpath) - fullpath = os.path.join(document_root, newpath) + path = posixpath.normpath(unquote(path)).lstrip('/') + fullpath = safe_join(document_root, path) if os.path.isdir(fullpath): if show_indexes: - return directory_index(newpath, fullpath) + return directory_index(path, fullpath) raise Http404(_("Directory indexes are not allowed here.")) if not os.path.exists(fullpath): raise Http404(_('"%(path)s" does not exist') % {'path': fullpath}) diff --git a/docs/releases/1.8.18.txt b/docs/releases/1.8.18.txt index 92492c782507..7b1e08046cd6 100644 --- a/docs/releases/1.8.18.txt +++ b/docs/releases/1.8.18.txt @@ -5,3 +5,14 @@ Django 1.8.18 release notes *April 4, 2017* Django 1.8.18 fixes two security issues in 1.8.17. + +CVE-2017-7234: Open redirect vulnerability in ``django.views.static.serve()`` +============================================================================= + +A maliciously crafted URL to a Django site using the +:func:`~django.views.static.serve` view could redirect to any other domain. The +view no longer does any redirects as they don't provide any known, useful +functionality. + +Note, however, that this view has always carried a warning that it is not +hardened for production use and should be used only as a development aid. diff --git a/docs/releases/1.9.13.txt b/docs/releases/1.9.13.txt index 42f22a8f00f8..f9d203eafe80 100644 --- a/docs/releases/1.9.13.txt +++ b/docs/releases/1.9.13.txt @@ -7,6 +7,17 @@ Django 1.9.13 release notes Django 1.9.13 fixes two security issues and a bug in 1.9.12. This is the final release of the 1.9.x series. +CVE-2017-7234: Open redirect vulnerability in ``django.views.static.serve()`` +============================================================================= + +A maliciously crafted URL to a Django site using the +:func:`~django.views.static.serve` view could redirect to any other domain. The +view no longer does any redirects as they don't provide any known, useful +functionality. + +Note, however, that this view has always carried a warning that it is not +hardened for production use and should be used only as a development aid. + Bugfixes ======== From 254326cb3682389f55f886804d2c43f7b9f23e4f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 14 Mar 2017 10:46:53 -0400 Subject: [PATCH 751/756] [1.9.x] Fixed #27912, CVE-2017-7233 -- Fixed is_safe_url() with numeric URLs. This is a security fix. --- django/utils/http.py | 67 +++++++++++++++++++++++++++++++++- docs/releases/1.8.18.txt | 12 ++++++ docs/releases/1.9.13.txt | 12 ++++++ tests/utils_tests/test_http.py | 5 ++- 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/django/utils/http.py b/django/utils/http.py index c053185a8a87..a110d710740c 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -15,9 +15,20 @@ from django.utils.functional import allow_lazy from django.utils.six.moves.urllib.parse import ( quote, quote_plus, unquote, unquote_plus, urlencode as original_urlencode, - urlparse, ) +if six.PY2: + from urlparse import ( + ParseResult, SplitResult, _splitnetloc, _splitparams, scheme_chars, + uses_params, + ) + _coerce_args = None +else: + from urllib.parse import ( + ParseResult, SplitResult, _coerce_args, _splitnetloc, _splitparams, + scheme_chars, uses_params, + ) + ETAG_MATCH = re.compile(r'(?:W/)?"((?:\\.|[^"])*)"') MONTHS = 'jan feb mar apr may jun jul aug sep oct nov dec'.split() @@ -300,12 +311,64 @@ def is_safe_url(url, host=None): return _is_safe_url(url, host) and _is_safe_url(url.replace('\\', '/'), host) +# Copied from urllib.parse.urlparse() but uses fixed urlsplit() function. +def _urlparse(url, scheme='', allow_fragments=True): + """Parse a URL into 6 components: + :///;?# + Return a 6-tuple: (scheme, netloc, path, params, query, fragment). + Note that we don't break the components up in smaller bits + (e.g. netloc is a single string) and we don't expand % escapes.""" + if _coerce_args: + url, scheme, _coerce_result = _coerce_args(url, scheme) + splitresult = _urlsplit(url, scheme, allow_fragments) + scheme, netloc, url, query, fragment = splitresult + if scheme in uses_params and ';' in url: + url, params = _splitparams(url) + else: + params = '' + result = ParseResult(scheme, netloc, url, params, query, fragment) + return _coerce_result(result) if _coerce_args else result + + +# Copied from urllib.parse.urlsplit() with +# https://github.com/python/cpython/pull/661 applied. +def _urlsplit(url, scheme='', allow_fragments=True): + """Parse a URL into 5 components: + :///?# + Return a 5-tuple: (scheme, netloc, path, query, fragment). + Note that we don't break the components up in smaller bits + (e.g. netloc is a single string) and we don't expand % escapes.""" + if _coerce_args: + url, scheme, _coerce_result = _coerce_args(url, scheme) + allow_fragments = bool(allow_fragments) + netloc = query = fragment = '' + i = url.find(':') + if i > 0: + for c in url[:i]: + if c not in scheme_chars: + break + else: + scheme, url = url[:i].lower(), url[i + 1:] + + if url[:2] == '//': + netloc, url = _splitnetloc(url, 2) + if (('[' in netloc and ']' not in netloc) or + (']' in netloc and '[' not in netloc)): + raise ValueError("Invalid IPv6 URL") + if allow_fragments and '#' in url: + url, fragment = url.split('#', 1) + if '?' in url: + url, query = url.split('?', 1) + v = SplitResult(scheme, netloc, url, query, fragment) + return _coerce_result(v) if _coerce_args else v + + def _is_safe_url(url, host): # Chrome considers any URL with more than two slashes to be absolute, but # urlparse is not so flexible. Treat any url with three slashes as unsafe. if url.startswith('///'): return False - url_info = urlparse(url) + url_info = _urlparse(url) # Forbid URLs like http:///example.com - with a scheme, but without a hostname. # In that URL, example.com is not the hostname but, a path component. However, # Chrome will still consider example.com to be the hostname, so we must not diff --git a/docs/releases/1.8.18.txt b/docs/releases/1.8.18.txt index 7b1e08046cd6..f41c7d080f99 100644 --- a/docs/releases/1.8.18.txt +++ b/docs/releases/1.8.18.txt @@ -6,6 +6,18 @@ Django 1.8.18 release notes Django 1.8.18 fixes two security issues in 1.8.17. +CVE-2017-7233: Open redirect and possible XSS attack via user-supplied numeric redirect URLs +============================================================================================ + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n `) +to redirect the user to an "on success" URL. The security check for these +redirects (namely ``django.utils.http.is_safe_url()``) considered some numeric +URLs (e.g. ``http:999999999``) "safe" when they shouldn't be. + +Also, if a developer relies on ``is_safe_url()`` to provide safe redirect +targets and puts such a URL into a link, they could suffer from an XSS attack. + CVE-2017-7234: Open redirect vulnerability in ``django.views.static.serve()`` ============================================================================= diff --git a/docs/releases/1.9.13.txt b/docs/releases/1.9.13.txt index f9d203eafe80..4828096da9fd 100644 --- a/docs/releases/1.9.13.txt +++ b/docs/releases/1.9.13.txt @@ -7,6 +7,18 @@ Django 1.9.13 release notes Django 1.9.13 fixes two security issues and a bug in 1.9.12. This is the final release of the 1.9.x series. +CVE-2017-7233: Open redirect and possible XSS attack via user-supplied numeric redirect URLs +============================================================================================ + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n `) +to redirect the user to an "on success" URL. The security check for these +redirects (namely ``django.utils.http.is_safe_url()``) considered some numeric +URLs (e.g. ``http:999999999``) "safe" when they shouldn't be. + +Also, if a developer relies on ``is_safe_url()`` to provide safe redirect +targets and puts such a URL into a link, they could suffer from an XSS attack. + CVE-2017-7234: Open redirect vulnerability in ``django.views.static.serve()`` ============================================================================= diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py index dc117869addc..d1b8876fde73 100644 --- a/tests/utils_tests/test_http.py +++ b/tests/utils_tests/test_http.py @@ -98,6 +98,8 @@ def test_is_safe_url(self): r'http://testserver\me:pass@example.com', r'http://testserver\@example.com', r'http:\\testserver\confirm\me@example.com', + 'http:999999999', + 'ftp:9999999999', '\n'): self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url) for good_url in ('/view/?param=http://example.com', @@ -108,7 +110,8 @@ def test_is_safe_url(self): 'HTTPS://testserver/', '//testserver/', 'http://testserver/confirm?email=me@example.com', - '/url%20with%20spaces/'): + '/url%20with%20spaces/', + 'path/http:2222222222'): self.assertTrue(http.is_safe_url(good_url, host='testserver'), "%s should be allowed" % good_url) if six.PY2: From 335aa088245a4cc6f1b04ba098458845344290bd Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 4 Apr 2017 10:10:23 -0400 Subject: [PATCH 752/756] [1.9.x] Bumped version for 1.9.13 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 9da5e1834e60..6fc3935a22f3 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 13, 'alpha', 0) +VERSION = (1, 9, 13, 'final', 0) __version__ = get_version(VERSION) From be43b857a969e3c7b0a13862d8ace800989429c8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 4 Apr 2017 12:17:13 -0400 Subject: [PATCH 753/756] [1.9.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 6fc3935a22f3..f8d63321f24f 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 9, 13, 'final', 0) +VERSION = (1, 9, 14, 'alpha', 0) __version__ = get_version(VERSION) From 2c3c029edeaa75e01373f3ec4a02fcc9184a2a76 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 4 Apr 2017 21:42:30 -0400 Subject: [PATCH 754/756] [1.9.x] Added CVE-2017-7233,4 to the security release archive. Backport of b749c980a066a15b58b236656e57b66073a35a52 from master --- docs/releases/security.txt | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 171e19d85e7b..0e92d6a185d5 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -807,3 +807,29 @@ Versions affected * Django 1.10 `(patch) `__ * Django 1.9 `(patch) `__ * Django 1.8 `(patch) `__ + +April 4, 2017 - :cve:`2017-7233` +-------------------------------- + +Open redirect and possible XSS attack via user-supplied numeric redirect URLs. +`Full description `__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 1.10 `(patch) `__ +* Django 1.9 `(patch) `__ +* Django 1.8 `(patch) `__ + +April 4, 2017 - :cve:`2017-7234` +-------------------------------- + +Open redirect vulnerability in ``django.views.static.serve()``. `Full +description `__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 1.10 `(patch) `__ +* Django 1.9 `(patch) `__ +* Django 1.8 `(patch) `__ From 89cb0a451c9cb01ace7e996231c8991dc6637419 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Mon, 21 Aug 2017 10:16:22 +0300 Subject: [PATCH 755/756] [1.9.x] Removed redundant backticks in docs/releases/1.8.txt Backport of 8d095c6378666e6d5f6cabc9e485c9db2618ff88 from master. --- docs/releases/1.8.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index a109c5415f1e..f44245a46cbb 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -1447,11 +1447,11 @@ Old :tfilter:`unordered_list` syntax An older (pre-1.0), more restrictive and verbose input format for the :tfilter:`unordered_list` template filter has been deprecated:: - ``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]`` + ['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]] Using the new syntax, this becomes:: - ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]`` + ['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']] ``django.forms.Field._has_changed()`` ------------------------------------- From 3df8ccf6fc3fa0ab2acf9a03da43fea87f8ff392 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 6 Sep 2017 19:21:38 -0400 Subject: [PATCH 756/756] [1.9.x] Fixed #28561 -- Removed inaccurate docs about QuerySet.order_by() and joins. As of ccbba98131ace3beb43790c65e8f4eee94e9631c, both examples don't use a join. Backport of 44a6c27fd461e1d2f37388c26c629f8f170e8722 from master --- docs/ref/models/querysets.txt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 5834e3d3e202..6bf37bc72bb0 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -320,15 +320,6 @@ identical to:: Entry.objects.order_by('blog__name') -It is also possible to order a queryset by a related field, without incurring -the cost of a JOIN, by referring to the ``_id`` of the related field:: - - # No Join - Entry.objects.order_by('blog_id') - - # Join - Entry.objects.order_by('blog__id') - You can also order by :doc:`query expressions ` by calling ``asc()`` or ``desc()`` on the expression::