From c72448b59725c619f4f9d6e38264484c12c4c3b9 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 16 Jan 2015 17:06:32 -0500 Subject: [PATCH 0001/1125] Bumped version to 1.8 alpha 1. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 9dcb9863975b..503c1a0e189e 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (1, 8, 0, 'alpha', 0) +VERSION = (1, 8, 0, 'alpha', 1) __version__ = get_version(VERSION) From 801287bff262e3beb52d4b4fd21ec529416e079e Mon Sep 17 00:00:00 2001 From: Rick Hutcheson Date: Fri, 16 Jan 2015 17:50:37 -0500 Subject: [PATCH 0002/1125] [1.8.x] Fixed a typo in the test responses docs. Backport of 996292d6498d25c6b3e84435e82edeff5aaa0257 from master --- docs/topics/testing/tools.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index b986ca248136..b9755305cbd9 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -396,7 +396,7 @@ Testing responses The ``get()`` and ``post()`` methods both return a ``Response`` object. This ``Response`` object is *not* the same as the ``HttpResponse`` object returned -Django views; the test response object has some additional data useful for +by Django views; the test response object has some additional data useful for test code to verify. Specifically, a ``Response`` object has the following attributes: From 8be1b8b488760959410be93e460944970b8199d6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 16 Jan 2015 18:30:49 -0500 Subject: [PATCH 0003/1125] [1.8.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 68b0389c5157..a8ad09028739 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.8' +django_next_version = '1.9' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From eb6a07e0697cf4dbaf33915e60fce56d7705ec22 Mon Sep 17 00:00:00 2001 From: David Robles Date: Fri, 16 Jan 2015 17:47:06 -0800 Subject: [PATCH 0004/1125] [1.8.x] Fixed typo in 'Django Template Language' Backport of d60b96d98881b47c845125e82269ea6a9b268fbb from master --- django/template/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/template/__init__.py b/django/template/__init__.py index 045ec72529e5..3d26bcc945ef 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -5,7 +5,7 @@ 1. Multiple Template Engines: support for pluggable template backends, built-in backends and backend-independent APIs -2. Django Template Langage: Django's own template engine, including its +2. Django Template Language: Django's own template engine, including its built-in loaders, context processors, tags and filters. Ideally these subsystems would be implemented in distinct packages. However From dec5157a7256b91534eaa64afb91c6e569ce17e3 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 17 Jan 2015 10:01:17 +0100 Subject: [PATCH 0005/1125] [1.8.x] Complemented test about non-supported aggregation exception Backport of d69ecf922dd from master. --- django/contrib/gis/tests/geoapp/tests.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index b5b765d21b90..8d20ef843ab4 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -630,14 +630,21 @@ def test_kml(self): for ptown in [ptown1, ptown2]: self.assertEqual('-104.609252,38.255001', ptown.kml) - # Only PostGIS has support for the MakeLine aggregate. - @skipUnlessDBFeature("supports_make_line_aggr") @ignore_warnings(category=RemovedInDjango20Warning) def test_make_line(self): """ Testing the (deprecated) `make_line` GeoQuerySet method and the MakeLine aggregate. """ + if not connection.features.supports_make_line_aggr: + # Only PostGIS has support for the MakeLine aggregate. For other + # backends, test that NotImplementedError is raised + self.assertRaises( + NotImplementedError, + City.objects.all().aggregate, MakeLine('point') + ) + return + # Ensuring that a `TypeError` is raised on models without PointFields. self.assertRaises(TypeError, State.objects.make_line) self.assertRaises(TypeError, Country.objects.make_line) From 666c12e5295d021c8d791d4410e5753d4a2cc98e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 17 Jan 2015 11:18:45 +0100 Subject: [PATCH 0006/1125] [1.8.x] Updated en translation catalogs --- django/conf/locale/en/LC_MESSAGES/django.po | 518 +++++++++++------- .../admin/locale/en/LC_MESSAGES/django.po | 244 +++++---- .../admin/locale/en/LC_MESSAGES/djangojs.po | 20 +- .../admindocs/locale/en/LC_MESSAGES/django.po | 55 +- .../auth/locale/en/LC_MESSAGES/django.po | 169 +++--- .../locale/en/LC_MESSAGES/django.po | 10 +- .../flatpages/locale/en/LC_MESSAGES/django.po | 20 +- .../gis/locale/en/LC_MESSAGES/django.po | 24 +- .../humanize/locale/en/LC_MESSAGES/django.po | 36 +- .../messages/locale/en/LC_MESSAGES/django.po | 4 +- .../redirects/locale/en/LC_MESSAGES/django.po | 2 +- .../sessions/locale/en/LC_MESSAGES/django.po | 12 +- .../sites/locale/en/LC_MESSAGES/django.po | 12 +- 13 files changed, 627 insertions(+), 499 deletions(-) diff --git a/django/conf/locale/en/LC_MESSAGES/django.po b/django/conf/locale/en/LC_MESSAGES/django.po index 8587e1107c68..267f765bdab4 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: 2014-08-23 14:10+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -25,330 +25,410 @@ msgstr "" msgid "Asturian" msgstr "" -#: conf/global_settings.py:53 +#: conf/global_settings.py:54 msgid "Azerbaijani" msgstr "" -#: conf/global_settings.py:54 +#: conf/global_settings.py:55 msgid "Bulgarian" msgstr "" -#: conf/global_settings.py:55 +#: conf/global_settings.py:56 msgid "Belarusian" msgstr "" -#: conf/global_settings.py:56 +#: conf/global_settings.py:57 msgid "Bengali" msgstr "" -#: conf/global_settings.py:57 +#: conf/global_settings.py:58 msgid "Breton" msgstr "" -#: conf/global_settings.py:58 +#: conf/global_settings.py:59 msgid "Bosnian" msgstr "" -#: conf/global_settings.py:59 +#: conf/global_settings.py:60 msgid "Catalan" msgstr "" -#: conf/global_settings.py:60 +#: conf/global_settings.py:61 msgid "Czech" msgstr "" -#: conf/global_settings.py:61 +#: conf/global_settings.py:62 msgid "Welsh" msgstr "" -#: conf/global_settings.py:62 +#: conf/global_settings.py:63 msgid "Danish" msgstr "" -#: conf/global_settings.py:63 +#: conf/global_settings.py:64 msgid "German" msgstr "" -#: conf/global_settings.py:64 +#: conf/global_settings.py:65 msgid "Greek" msgstr "" -#: conf/global_settings.py:65 +#: conf/global_settings.py:66 msgid "English" msgstr "" -#: conf/global_settings.py:66 +#: conf/global_settings.py:67 msgid "Australian English" msgstr "" -#: conf/global_settings.py:67 +#: conf/global_settings.py:68 msgid "British English" msgstr "" -#: conf/global_settings.py:68 +#: conf/global_settings.py:69 msgid "Esperanto" msgstr "" -#: conf/global_settings.py:69 +#: conf/global_settings.py:70 msgid "Spanish" msgstr "" -#: conf/global_settings.py:70 +#: conf/global_settings.py:71 msgid "Argentinian Spanish" msgstr "" -#: conf/global_settings.py:71 +#: conf/global_settings.py:72 msgid "Mexican Spanish" msgstr "" -#: conf/global_settings.py:72 +#: conf/global_settings.py:73 msgid "Nicaraguan Spanish" msgstr "" -#: conf/global_settings.py:73 +#: conf/global_settings.py:74 msgid "Venezuelan Spanish" msgstr "" -#: conf/global_settings.py:74 +#: conf/global_settings.py:75 msgid "Estonian" msgstr "" -#: conf/global_settings.py:75 +#: conf/global_settings.py:76 msgid "Basque" msgstr "" -#: conf/global_settings.py:76 +#: conf/global_settings.py:77 msgid "Persian" msgstr "" -#: conf/global_settings.py:77 +#: conf/global_settings.py:78 msgid "Finnish" msgstr "" -#: conf/global_settings.py:78 +#: conf/global_settings.py:79 msgid "French" msgstr "" -#: conf/global_settings.py:79 +#: conf/global_settings.py:80 msgid "Frisian" msgstr "" -#: conf/global_settings.py:80 +#: conf/global_settings.py:81 msgid "Irish" msgstr "" -#: conf/global_settings.py:81 +#: conf/global_settings.py:82 msgid "Galician" msgstr "" -#: conf/global_settings.py:82 +#: conf/global_settings.py:83 msgid "Hebrew" msgstr "" -#: conf/global_settings.py:83 +#: conf/global_settings.py:84 msgid "Hindi" msgstr "" -#: conf/global_settings.py:84 +#: conf/global_settings.py:85 msgid "Croatian" msgstr "" -#: conf/global_settings.py:85 +#: conf/global_settings.py:86 msgid "Hungarian" msgstr "" -#: conf/global_settings.py:86 +#: conf/global_settings.py:87 msgid "Interlingua" msgstr "" -#: conf/global_settings.py:87 +#: conf/global_settings.py:88 msgid "Indonesian" msgstr "" -#: conf/global_settings.py:88 +#: conf/global_settings.py:89 msgid "Ido" msgstr "" -#: conf/global_settings.py:88 +#: conf/global_settings.py:90 msgid "Icelandic" msgstr "" -#: conf/global_settings.py:89 +#: conf/global_settings.py:91 msgid "Italian" msgstr "" -#: conf/global_settings.py:90 +#: conf/global_settings.py:92 msgid "Japanese" msgstr "" -#: conf/global_settings.py:91 +#: conf/global_settings.py:93 msgid "Georgian" msgstr "" -#: conf/global_settings.py:92 +#: conf/global_settings.py:94 msgid "Kazakh" msgstr "" -#: conf/global_settings.py:93 +#: conf/global_settings.py:95 msgid "Khmer" msgstr "" -#: conf/global_settings.py:94 +#: conf/global_settings.py:96 msgid "Kannada" msgstr "" -#: conf/global_settings.py:95 +#: conf/global_settings.py:97 msgid "Korean" msgstr "" -#: conf/global_settings.py:96 +#: conf/global_settings.py:98 msgid "Luxembourgish" msgstr "" -#: conf/global_settings.py:97 +#: conf/global_settings.py:99 msgid "Lithuanian" msgstr "" -#: conf/global_settings.py:98 +#: conf/global_settings.py:100 msgid "Latvian" msgstr "" -#: conf/global_settings.py:99 +#: conf/global_settings.py:101 msgid "Macedonian" msgstr "" -#: conf/global_settings.py:100 +#: conf/global_settings.py:102 msgid "Malayalam" msgstr "" -#: conf/global_settings.py:101 +#: conf/global_settings.py:103 msgid "Mongolian" msgstr "" -#: conf/global_settings.py:102 +#: conf/global_settings.py:104 msgid "Marathi" msgstr "" -#: conf/global_settings.py:102 +#: conf/global_settings.py:105 msgid "Burmese" msgstr "" -#: conf/global_settings.py:103 +#: conf/global_settings.py:106 msgid "Norwegian Bokmal" msgstr "" -#: conf/global_settings.py:104 +#: conf/global_settings.py:107 msgid "Nepali" msgstr "" -#: conf/global_settings.py:105 +#: conf/global_settings.py:108 msgid "Dutch" msgstr "" -#: conf/global_settings.py:106 +#: conf/global_settings.py:109 msgid "Norwegian Nynorsk" msgstr "" -#: conf/global_settings.py:107 +#: conf/global_settings.py:110 msgid "Ossetic" msgstr "" -#: conf/global_settings.py:108 +#: conf/global_settings.py:111 msgid "Punjabi" msgstr "" -#: conf/global_settings.py:109 +#: conf/global_settings.py:112 msgid "Polish" msgstr "" -#: conf/global_settings.py:110 +#: conf/global_settings.py:113 msgid "Portuguese" msgstr "" -#: conf/global_settings.py:111 +#: conf/global_settings.py:114 msgid "Brazilian Portuguese" msgstr "" -#: conf/global_settings.py:112 +#: conf/global_settings.py:115 msgid "Romanian" msgstr "" -#: conf/global_settings.py:113 +#: conf/global_settings.py:116 msgid "Russian" msgstr "" -#: conf/global_settings.py:114 +#: conf/global_settings.py:117 msgid "Slovak" msgstr "" -#: conf/global_settings.py:115 +#: conf/global_settings.py:118 msgid "Slovenian" msgstr "" -#: conf/global_settings.py:116 +#: conf/global_settings.py:119 msgid "Albanian" msgstr "" -#: conf/global_settings.py:117 +#: conf/global_settings.py:120 msgid "Serbian" msgstr "" -#: conf/global_settings.py:118 +#: conf/global_settings.py:121 msgid "Serbian Latin" msgstr "" -#: conf/global_settings.py:119 +#: conf/global_settings.py:122 msgid "Swedish" msgstr "" -#: conf/global_settings.py:120 +#: conf/global_settings.py:123 msgid "Swahili" msgstr "" -#: conf/global_settings.py:121 +#: conf/global_settings.py:124 msgid "Tamil" msgstr "" -#: conf/global_settings.py:122 +#: conf/global_settings.py:125 msgid "Telugu" msgstr "" -#: conf/global_settings.py:123 +#: conf/global_settings.py:126 msgid "Thai" msgstr "" -#: conf/global_settings.py:124 +#: conf/global_settings.py:127 msgid "Turkish" msgstr "" -#: conf/global_settings.py:125 +#: conf/global_settings.py:128 msgid "Tatar" msgstr "" -#: conf/global_settings.py:126 +#: conf/global_settings.py:129 msgid "Udmurt" msgstr "" -#: conf/global_settings.py:127 +#: conf/global_settings.py:130 msgid "Ukrainian" msgstr "" -#: conf/global_settings.py:128 +#: conf/global_settings.py:131 msgid "Urdu" msgstr "" -#: conf/global_settings.py:129 +#: conf/global_settings.py:132 msgid "Vietnamese" msgstr "" -#: conf/global_settings.py:130 conf/global_settings.py:131 +#: conf/global_settings.py:133 conf/global_settings.py:134 msgid "Simplified Chinese" msgstr "" -#: conf/global_settings.py:132 conf/global_settings.py:133 +#: conf/global_settings.py:135 conf/global_settings.py:136 msgid "Traditional Chinese" msgstr "" +#: contrib/postgres/apps.py:12 +msgid "PostgreSQL extensions" +msgstr "" + +#: contrib/postgres/fields/array.py:23 contrib/postgres/forms/array.py:13 +#: contrib/postgres/forms/array.py:143 +#, python-format +msgid "Item %(nth)s in the array did not validate: " +msgstr "" + +#: contrib/postgres/fields/array.py:24 +msgid "Nested arrays must have the same length." +msgstr "" + +#: contrib/postgres/fields/hstore.py:16 +msgid "Map of strings to strings" +msgstr "" + +#: contrib/postgres/fields/hstore.py:18 +#, python-format +msgid "The value of \"%(key)s\" is not a string." +msgstr "" + +#: contrib/postgres/forms/hstore.py:16 +msgid "Could not load JSON data." +msgstr "" + +#: contrib/postgres/forms/ranges.py:13 +msgid "Enter two valid values." +msgstr "" + +#: contrib/postgres/forms/ranges.py:14 +msgid "The start of the range must not exceed the end of the range." +msgstr "" + +#: contrib/postgres/validators.py:14 +#, python-format +msgid "" +"List contains %(show_value)d item, it should contain no more than " +"%(limit_value)d." +msgid_plural "" +"List contains %(show_value)d items, it should contain no more than " +"%(limit_value)d." +msgstr[0] "" +msgstr[1] "" + +#: contrib/postgres/validators.py:21 +#, python-format +msgid "" +"List contains %(show_value)d item, it should contain no fewer than " +"%(limit_value)d." +msgid_plural "" +"List contains %(show_value)d items, it should contain no fewer than " +"%(limit_value)d." +msgstr[0] "" +msgstr[1] "" + +#: contrib/postgres/validators.py:31 +#, python-format +msgid "Some keys were missing: %(keys)s" +msgstr "" + +#: contrib/postgres/validators.py:32 +#, python-format +msgid "Some unknown keys were provided: %(keys)s" +msgstr "" + +#: contrib/postgres/validators.py:73 +#, python-format +msgid "" +"Ensure that this range is completely less than or equal to %(limit_value)s." +msgstr "" + +#: contrib/postgres/validators.py:78 +#, python-format +msgid "" +"Ensure that this range is completely greater than or equal to " +"%(limit_value)s." +msgstr "" + #: contrib/sitemaps/apps.py:8 msgid "Site Maps" msgstr "" @@ -369,55 +449,55 @@ msgstr "" msgid "Enter a valid value." msgstr "" -#: core/validators.py:77 forms/fields.py:678 +#: core/validators.py:88 forms/fields.py:708 msgid "Enter a valid URL." msgstr "" -#: core/validators.py:115 +#: core/validators.py:134 msgid "Enter a valid integer." msgstr "" -#: core/validators.py:120 +#: core/validators.py:139 msgid "Enter a valid email address." msgstr "" -#: core/validators.py:185 +#: core/validators.py:213 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" -#: core/validators.py:188 core/validators.py:207 +#: core/validators.py:218 core/validators.py:237 msgid "Enter a valid IPv4 address." msgstr "" -#: core/validators.py:193 core/validators.py:208 +#: core/validators.py:223 core/validators.py:238 msgid "Enter a valid IPv6 address." msgstr "" -#: core/validators.py:203 core/validators.py:206 +#: core/validators.py:233 core/validators.py:236 msgid "Enter a valid IPv4 or IPv6 address." msgstr "" -#: core/validators.py:229 db/models/fields/__init__.py:1070 +#: core/validators.py:261 db/models/fields/__init__.py:1133 msgid "Enter only digits separated by commas." msgstr "" -#: core/validators.py:236 +#: core/validators.py:270 #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." msgstr "" -#: core/validators.py:255 +#: core/validators.py:296 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "" -#: core/validators.py:262 +#: core/validators.py:303 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "" -#: core/validators.py:271 +#: core/validators.py:312 #, python-format msgid "" "Ensure this value has at least %(limit_value)d character (it has " @@ -428,7 +508,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: core/validators.py:282 +#: core/validators.py:323 #, python-format msgid "" "Ensure this value has at most %(limit_value)d character (it has " @@ -439,255 +519,271 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: db/models/base.py:978 forms/models.py:713 +#: db/models/base.py:1089 forms/models.py:721 msgid "and" msgstr "" -#: db/models/base.py:980 +#: db/models/base.py:1091 #, python-format msgid "%(model_name)s with this %(field_labels)s already exists." msgstr "" -#: db/models/fields/__init__.py:104 +#: db/models/fields/__init__.py:107 #, python-format msgid "Value %(value)r is not a valid choice." msgstr "" -#: db/models/fields/__init__.py:105 +#: db/models/fields/__init__.py:108 msgid "This field cannot be null." msgstr "" -#: db/models/fields/__init__.py:106 +#: db/models/fields/__init__.py:109 msgid "This field cannot be blank." msgstr "" -#: db/models/fields/__init__.py:107 +#: db/models/fields/__init__.py:110 #, 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:111 +#: db/models/fields/__init__.py:114 #, python-format msgid "" "%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." msgstr "" -#: db/models/fields/__init__.py:116 +#: db/models/fields/__init__.py:132 #, python-format msgid "Field of type: %(field_type)s" msgstr "" -#: db/models/fields/__init__.py:847 db/models/fields/__init__.py:1696 +#: db/models/fields/__init__.py:911 db/models/fields/__init__.py:1814 msgid "Integer" msgstr "" -#: db/models/fields/__init__.py:851 db/models/fields/__init__.py:1694 +#: db/models/fields/__init__.py:915 db/models/fields/__init__.py:1812 #, python-format msgid "'%(value)s' value must be an integer." msgstr "" -#: db/models/fields/__init__.py:926 +#: db/models/fields/__init__.py:990 #, python-format msgid "'%(value)s' value must be either True or False." msgstr "" -#: db/models/fields/__init__.py:928 +#: db/models/fields/__init__.py:992 msgid "Boolean (Either True or False)" msgstr "" -#: db/models/fields/__init__.py:1004 +#: db/models/fields/__init__.py:1067 #, python-format msgid "String (up to %(max_length)s)" msgstr "" -#: db/models/fields/__init__.py:1065 +#: db/models/fields/__init__.py:1128 msgid "Comma-separated integers" msgstr "" -#: db/models/fields/__init__.py:1114 +#: db/models/fields/__init__.py:1177 #, 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:1116 db/models/fields/__init__.py:1266 +#: db/models/fields/__init__.py:1179 db/models/fields/__init__.py:1329 #, 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:1119 +#: db/models/fields/__init__.py:1182 msgid "Date (without time)" msgstr "" -#: db/models/fields/__init__.py:1264 +#: db/models/fields/__init__.py:1327 #, 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:1268 +#: db/models/fields/__init__.py:1331 #, 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:1272 +#: db/models/fields/__init__.py:1335 msgid "Date (with time)" msgstr "" -#: db/models/fields/__init__.py:1420 +#: db/models/fields/__init__.py:1487 #, python-format msgid "'%(value)s' value must be a decimal number." msgstr "" -#: db/models/fields/__init__.py:1422 +#: db/models/fields/__init__.py:1489 msgid "Decimal number" msgstr "" -#: db/models/fields/__init__.py:1567 +#: db/models/fields/__init__.py:1640 +#, 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 +msgid "Duration" +msgstr "" + +#: db/models/fields/__init__.py:1687 msgid "Email address" msgstr "" -#: db/models/fields/__init__.py:1593 +#: db/models/fields/__init__.py:1711 msgid "File path" msgstr "" -#: db/models/fields/__init__.py:1660 +#: db/models/fields/__init__.py:1778 #, python-format msgid "'%(value)s' value must be a float." msgstr "" -#: db/models/fields/__init__.py:1662 +#: db/models/fields/__init__.py:1780 msgid "Floating point number" msgstr "" -#: db/models/fields/__init__.py:1746 +#: db/models/fields/__init__.py:1881 msgid "Big (8 byte) integer" msgstr "" -#: db/models/fields/__init__.py:1761 +#: db/models/fields/__init__.py:1896 msgid "IPv4 address" msgstr "" -#: db/models/fields/__init__.py:1791 +#: db/models/fields/__init__.py:1932 msgid "IP address" msgstr "" -#: db/models/fields/__init__.py:1870 +#: db/models/fields/__init__.py:2011 #, python-format msgid "'%(value)s' value must be either None, True or False." msgstr "" -#: db/models/fields/__init__.py:1872 +#: db/models/fields/__init__.py:2013 msgid "Boolean (Either True, False or None)" msgstr "" -#: db/models/fields/__init__.py:1932 +#: db/models/fields/__init__.py:2073 msgid "Positive integer" msgstr "" -#: db/models/fields/__init__.py:1944 +#: db/models/fields/__init__.py:2085 msgid "Positive small integer" msgstr "" -#: db/models/fields/__init__.py:1957 +#: db/models/fields/__init__.py:2098 #, python-format msgid "Slug (up to %(max_length)s)" msgstr "" -#: db/models/fields/__init__.py:1986 +#: db/models/fields/__init__.py:2127 msgid "Small integer" msgstr "" -#: db/models/fields/__init__.py:1993 +#: db/models/fields/__init__.py:2134 msgid "Text" msgstr "" -#: db/models/fields/__init__.py:2016 +#: db/models/fields/__init__.py:2157 #, 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:2018 +#: db/models/fields/__init__.py:2159 #, 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:2021 +#: db/models/fields/__init__.py:2162 msgid "Time" msgstr "" -#: db/models/fields/__init__.py:2146 +#: db/models/fields/__init__.py:2290 msgid "URL" msgstr "" -#: db/models/fields/__init__.py:2169 +#: db/models/fields/__init__.py:2313 msgid "Raw binary data" msgstr "" -#: db/models/fields/files.py:224 +#: db/models/fields/__init__.py:2357 +#, python-format +msgid "'%(value)s' is not a valid UUID." +msgstr "" + +#: db/models/fields/files.py:239 msgid "File" msgstr "" -#: db/models/fields/files.py:374 +#: db/models/fields/files.py:389 msgid "Image" msgstr "" -#: db/models/fields/related.py:1594 +#: db/models/fields/related.py:1798 #, python-format -msgid "%(model)s instance with pk %(pk)r does not exist." +msgid "%(model)s instance with %(field)s %(value)r does not exist." msgstr "" -#: db/models/fields/related.py:1596 +#: db/models/fields/related.py:1800 msgid "Foreign Key (type determined by related field)" msgstr "" -#: db/models/fields/related.py:1787 +#: db/models/fields/related.py:2013 msgid "One-to-one relationship" msgstr "" -#: db/models/fields/related.py:1857 +#: db/models/fields/related.py:2103 msgid "Many-to-many relationship" msgstr "" -#: forms/fields.py:55 +#: forms/fields.py:64 msgid "This field is required." msgstr "" -#: forms/fields.py:239 +#: forms/fields.py:237 msgid "Enter a whole number." msgstr "" -#: forms/fields.py:282 forms/fields.py:319 +#: forms/fields.py:280 forms/fields.py:317 msgid "Enter a number." msgstr "" -#: forms/fields.py:321 +#: 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:325 +#: 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:329 +#: forms/fields.py:327 #, python-format msgid "" "Ensure that there are no more than %(max)s digit before the decimal point." @@ -696,31 +792,35 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: forms/fields.py:440 forms/fields.py:1139 +#: forms/fields.py:438 forms/fields.py:1189 msgid "Enter a valid date." msgstr "" -#: forms/fields.py:464 forms/fields.py:1140 +#: forms/fields.py:462 forms/fields.py:1190 msgid "Enter a valid time." msgstr "" -#: forms/fields.py:486 +#: forms/fields.py:484 msgid "Enter a valid date/time." msgstr "" -#: forms/fields.py:567 +#: forms/fields.py:525 +msgid "Enter a valid duration." +msgstr "" + +#: forms/fields.py:589 msgid "No file was submitted. Check the encoding type on the form." msgstr "" -#: forms/fields.py:568 +#: forms/fields.py:590 msgid "No file was submitted." msgstr "" -#: forms/fields.py:569 +#: forms/fields.py:591 msgid "The submitted file is empty." msgstr "" -#: forms/fields.py:571 +#: forms/fields.py:593 #, python-format msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." msgid_plural "" @@ -728,42 +828,46 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: forms/fields.py:574 +#: forms/fields.py:596 msgid "Please either submit a file or check the clear checkbox, not both." msgstr "" -#: forms/fields.py:635 +#: forms/fields.py:658 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" -#: forms/fields.py:782 forms/fields.py:871 forms/models.py:1182 +#: forms/fields.py:823 forms/fields.py:917 forms/models.py:1220 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" -#: forms/fields.py:872 forms/fields.py:987 forms/models.py:1181 +#: forms/fields.py:918 forms/fields.py:1033 forms/models.py:1219 msgid "Enter a list of values." msgstr "" -#: forms/fields.py:988 +#: forms/fields.py:1034 msgid "Enter a complete value." msgstr "" +#: forms/fields.py:1260 +msgid "Enter a valid UUID." +msgstr "" + #. Translators: This is the default suffix added to form field labels -#: forms/forms.py:122 +#: forms/forms.py:125 msgid ":" msgstr "" -#: forms/forms.py:192 +#: forms/forms.py:207 #, 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:625 +#: forms/forms.py:652 msgid ":?.!" msgstr "" @@ -793,103 +897,103 @@ msgstr "" msgid "Delete" msgstr "" -#: forms/models.py:707 +#: forms/models.py:715 #, python-format msgid "Please correct the duplicate data for %(field)s." msgstr "" -#: forms/models.py:711 +#: forms/models.py:719 #, python-format msgid "Please correct the duplicate data for %(field)s, which must be unique." msgstr "" -#: forms/models.py:717 +#: forms/models.py:725 #, 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:725 +#: forms/models.py:733 msgid "Please correct the duplicate values below." msgstr "" -#: forms/models.py:1018 +#: forms/models.py:1039 msgid "The inline foreign key did not match the parent instance primary key." msgstr "" -#: forms/models.py:1084 +#: forms/models.py:1105 msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" -#: forms/models.py:1184 +#: forms/models.py:1222 #, python-format msgid "\"%(pk)s\" is not a valid value for a primary key." msgstr "" -#: forms/utils.py:145 +#: forms/utils.py:165 #, 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:340 +#: forms/widgets.py:343 msgid "Currently" msgstr "" -#: forms/widgets.py:341 +#: forms/widgets.py:344 msgid "Change" msgstr "" -#: forms/widgets.py:342 +#: forms/widgets.py:345 msgid "Clear" msgstr "" -#: forms/widgets.py:536 +#: forms/widgets.py:553 msgid "Unknown" msgstr "" -#: forms/widgets.py:537 +#: forms/widgets.py:554 msgid "Yes" msgstr "" -#: forms/widgets.py:538 +#: forms/widgets.py:555 msgid "No" msgstr "" -#: template/defaultfilters.py:849 +#: template/defaultfilters.py:859 msgid "yes,no,maybe" msgstr "" -#: template/defaultfilters.py:878 template/defaultfilters.py:890 +#: template/defaultfilters.py:888 template/defaultfilters.py:900 #, python-format msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "" msgstr[1] "" -#: template/defaultfilters.py:892 +#: template/defaultfilters.py:902 #, python-format msgid "%s KB" msgstr "" -#: template/defaultfilters.py:894 +#: template/defaultfilters.py:904 #, python-format msgid "%s MB" msgstr "" -#: template/defaultfilters.py:896 +#: template/defaultfilters.py:906 #, python-format msgid "%s GB" msgstr "" -#: template/defaultfilters.py:898 +#: template/defaultfilters.py:908 #, python-format msgid "%s TB" msgstr "" -#: template/defaultfilters.py:900 +#: template/defaultfilters.py:910 #, python-format msgid "%s PB" msgstr "" @@ -1255,15 +1359,15 @@ msgstr[1] "" msgid "0 minutes" msgstr "" -#: views/csrf.py:105 +#: views/csrf.py:106 msgid "Forbidden" msgstr "" -#: views/csrf.py:106 +#: views/csrf.py:107 msgid "CSRF verification failed. Request aborted." msgstr "" -#: views/csrf.py:110 +#: views/csrf.py:111 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 " @@ -1271,49 +1375,49 @@ msgid "" "hijacked by third parties." msgstr "" -#: views/csrf.py:115 +#: views/csrf.py:116 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:120 +#: views/csrf.py:121 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:125 +#: views/csrf.py:126 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:129 +#: views/csrf.py:131 msgid "More information is available with DEBUG=True." msgstr "" -#: views/debug.py:517 +#: views/debug.py:582 msgid "Welcome to Django" msgstr "" -#: views/debug.py:518 +#: views/debug.py:583 msgid "It worked!" msgstr "" -#: views/debug.py:519 +#: views/debug.py:584 msgid "Congratulations on your first Django-powered page." msgstr "" -#: views/debug.py:520 +#: views/debug.py:585 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:522 +#: views/debug.py:587 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!" @@ -1335,52 +1439,52 @@ msgstr "" msgid "No week specified" msgstr "" -#: views/generic/dates.py:369 views/generic/dates.py:397 +#: views/generic/dates.py:373 views/generic/dates.py:401 #, python-format msgid "No %(verbose_name_plural)s available" msgstr "" -#: views/generic/dates.py:650 +#: views/generic/dates.py:655 #, python-format msgid "" "Future %(verbose_name_plural)s not available because %(class_name)s." "allow_future is False." msgstr "" -#: views/generic/dates.py:682 +#: views/generic/dates.py:689 #, python-format msgid "Invalid date string '%(datestr)s' given format '%(format)s'" msgstr "" -#: views/generic/detail.py:54 +#: views/generic/detail.py:55 #, python-format msgid "No %(verbose_name)s found matching the query" msgstr "" -#: views/generic/list.py:62 +#: views/generic/list.py:76 msgid "Page is not 'last', nor can it be converted to an int." msgstr "" -#: views/generic/list.py:67 +#: views/generic/list.py:81 #, python-format msgid "Invalid page (%(page_number)s): %(message)s" msgstr "" -#: views/generic/list.py:158 +#: views/generic/list.py:172 #, python-format msgid "Empty list and '%(class_name)s.allow_empty' is False." msgstr "" -#: views/static.py:54 +#: views/static.py:56 msgid "Directory indexes are not allowed here." msgstr "" -#: views/static.py:56 +#: views/static.py:58 #, python-format msgid "\"%(path)s\" does not exist" msgstr "" -#: views/static.py:97 +#: views/static.py:98 #, 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 7303a2529e79..8bce0976762a 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: 2014-05-19 15:17+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -18,16 +18,16 @@ msgstr "" msgid "Successfully deleted %(count)d %(items)s." msgstr "" -#: contrib/admin/actions.py:62 contrib/admin/options.py:1615 +#: contrib/admin/actions.py:62 contrib/admin/options.py:1722 #, python-format msgid "Cannot delete %(name)s" msgstr "" -#: contrib/admin/actions.py:64 contrib/admin/options.py:1617 +#: contrib/admin/actions.py:64 contrib/admin/options.py:1724 msgid "Are you sure?" msgstr "" -#: contrib/admin/actions.py:84 +#: contrib/admin/actions.py:88 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "" @@ -36,41 +36,41 @@ msgstr "" msgid "Administration" msgstr "" -#: contrib/admin/filters.py:104 contrib/admin/filters.py:199 -#: contrib/admin/filters.py:239 contrib/admin/filters.py:276 -#: contrib/admin/filters.py:387 +#: 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 msgid "All" msgstr "" -#: contrib/admin/filters.py:240 +#: contrib/admin/filters.py:244 msgid "Yes" msgstr "" -#: contrib/admin/filters.py:241 +#: contrib/admin/filters.py:245 msgid "No" msgstr "" -#: contrib/admin/filters.py:255 +#: contrib/admin/filters.py:259 msgid "Unknown" msgstr "" -#: contrib/admin/filters.py:315 +#: contrib/admin/filters.py:319 msgid "Any date" msgstr "" -#: contrib/admin/filters.py:316 +#: contrib/admin/filters.py:320 msgid "Today" msgstr "" -#: contrib/admin/filters.py:320 +#: contrib/admin/filters.py:324 msgid "Past 7 days" msgstr "" -#: contrib/admin/filters.py:324 +#: contrib/admin/filters.py:328 msgid "This month" msgstr "" -#: contrib/admin/filters.py:328 +#: contrib/admin/filters.py:332 msgid "This year" msgstr "" @@ -81,222 +81,223 @@ msgid "" "that both fields may be case-sensitive." msgstr "" -#: contrib/admin/helpers.py:23 +#: contrib/admin/helpers.py:27 msgid "Action:" msgstr "" -#: contrib/admin/models.py:25 +#: contrib/admin/models.py:30 msgid "action time" msgstr "" -#: contrib/admin/models.py:28 +#: contrib/admin/models.py:33 msgid "object id" msgstr "" -#: contrib/admin/models.py:29 +#: contrib/admin/models.py:34 msgid "object repr" msgstr "" -#: contrib/admin/models.py:30 +#: contrib/admin/models.py:35 msgid "action flag" msgstr "" -#: contrib/admin/models.py:31 +#: contrib/admin/models.py:36 msgid "change message" msgstr "" -#: contrib/admin/models.py:36 +#: contrib/admin/models.py:41 msgid "log entry" msgstr "" -#: contrib/admin/models.py:37 +#: contrib/admin/models.py:42 msgid "log entries" msgstr "" -#: contrib/admin/models.py:46 +#: contrib/admin/models.py:51 #, python-format msgid "Added \"%(object)s\"." msgstr "" -#: contrib/admin/models.py:48 +#: contrib/admin/models.py:53 #, python-format msgid "Changed \"%(object)s\" - %(changes)s" msgstr "" -#: contrib/admin/models.py:53 +#: contrib/admin/models.py:58 #, python-format msgid "Deleted \"%(object)s.\"" msgstr "" -#: contrib/admin/models.py:55 +#: contrib/admin/models.py:60 msgid "LogEntry Object" msgstr "" -#: contrib/admin/options.py:225 contrib/admin/options.py:254 +#: contrib/admin/options.py:228 contrib/admin/options.py:257 msgid "None" msgstr "" -#: contrib/admin/options.py:287 +#: contrib/admin/options.py:293 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" -#: contrib/admin/options.py:955 +#: contrib/admin/options.py:1021 #, python-format msgid "Changed %s." msgstr "" -#: contrib/admin/options.py:955 contrib/admin/options.py:965 -#: contrib/admin/options.py:1807 +#: contrib/admin/options.py:1021 contrib/admin/options.py:1031 +#: contrib/admin/options.py:1926 msgid "and" msgstr "" -#: contrib/admin/options.py:960 +#: contrib/admin/options.py:1026 #, python-format msgid "Added %(name)s \"%(object)s\"." msgstr "" -#: contrib/admin/options.py:964 +#: contrib/admin/options.py:1030 #, python-format msgid "Changed %(list)s for %(name)s \"%(object)s\"." msgstr "" -#: contrib/admin/options.py:969 +#: contrib/admin/options.py:1035 #, python-format msgid "Deleted %(name)s \"%(object)s\"." msgstr "" -#: contrib/admin/options.py:973 +#: contrib/admin/options.py:1039 msgid "No fields changed." msgstr "" -#: contrib/admin/options.py:1097 contrib/admin/options.py:1137 +#: contrib/admin/options.py:1165 contrib/admin/options.py:1221 #, python-format msgid "" "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." msgstr "" -#: contrib/admin/options.py:1108 +#: contrib/admin/options.py:1179 #, python-format msgid "" "The %(name)s \"%(obj)s\" was added successfully. You may add another " "%(name)s below." msgstr "" -#: contrib/admin/options.py:1115 +#: contrib/admin/options.py:1186 #, python-format msgid "The %(name)s \"%(obj)s\" was added successfully." msgstr "" -#: contrib/admin/options.py:1130 +#: contrib/admin/options.py:1214 #, python-format msgid "" "The %(name)s \"%(obj)s\" was changed successfully. You may edit it again " "below." msgstr "" -#: contrib/admin/options.py:1147 +#: contrib/admin/options.py:1231 #, python-format msgid "" "The %(name)s \"%(obj)s\" was changed successfully. You may add another " "%(name)s below." msgstr "" -#: contrib/admin/options.py:1156 +#: contrib/admin/options.py:1240 #, python-format msgid "The %(name)s \"%(obj)s\" was changed successfully." msgstr "" -#: contrib/admin/options.py:1239 contrib/admin/options.py:1480 +#: contrib/admin/options.py:1323 contrib/admin/options.py:1579 msgid "" "Items must be selected in order to perform actions on them. No items have " "been changed." msgstr "" -#: contrib/admin/options.py:1258 +#: contrib/admin/options.py:1342 msgid "No action selected." msgstr "" -#: contrib/admin/options.py:1270 +#: contrib/admin/options.py:1360 #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "" -#: contrib/admin/options.py:1347 contrib/admin/options.py:1592 +#: contrib/admin/options.py:1447 contrib/admin/options.py:1697 #, python-format msgid "%(name)s object with primary key %(key)r does not exist." msgstr "" -#: contrib/admin/options.py:1397 +#: contrib/admin/options.py:1497 #, python-format msgid "Add %s" msgstr "" -#: contrib/admin/options.py:1397 +#: contrib/admin/options.py:1497 #, python-format msgid "Change %s" msgstr "" -#: contrib/admin/options.py:1459 +#: contrib/admin/options.py:1558 msgid "Database error" msgstr "" -#: contrib/admin/options.py:1522 +#: contrib/admin/options.py:1621 #, 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:1549 +#: contrib/admin/options.py:1648 #, python-format msgid "%(total_count)s selected" msgid_plural "All %(total_count)s selected" msgstr[0] "" msgstr[1] "" -#: contrib/admin/options.py:1555 +#: contrib/admin/options.py:1654 #, python-format msgid "0 of %(cnt)s selected" msgstr "" -#: contrib/admin/options.py:1654 +#: contrib/admin/options.py:1765 #, 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:1801 +#. Translators: Model verbose name and instance representation, +#. suitable to be an item in a list. +#: contrib/admin/options.py:1920 #, python-format msgid "%(class_name)s %(instance)s" msgstr "" -#: contrib/admin/options.py:1808 +#: contrib/admin/options.py:1927 #, 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:36 contrib/admin/templates/admin/base_site.html:3 +#: contrib/admin/sites.py:39 contrib/admin/templates/admin/base_site.html:3 msgid "Django site admin" msgstr "" -#: contrib/admin/sites.py:39 contrib/admin/templates/admin/base_site.html:6 +#: contrib/admin/sites.py:42 contrib/admin/templates/admin/base_site.html:6 msgid "Django administration" msgstr "" -#: contrib/admin/sites.py:42 +#: contrib/admin/sites.py:45 msgid "Site administration" msgstr "" -#: contrib/admin/sites.py:347 contrib/admin/templates/admin/login.html:47 +#: contrib/admin/sites.py:373 contrib/admin/templates/admin/login.html:49 #: contrib/admin/templates/registration/password_reset_complete.html:18 #: contrib/admin/tests.py:113 msgid "Log in" msgstr "" -#: contrib/admin/sites.py:474 +#: contrib/admin/sites.py:505 #, python-format msgid "%(app)s administration" msgstr "" @@ -313,7 +314,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:50 +#: contrib/admin/templates/admin/base.html:58 #: contrib/admin/templates/admin/change_form.html:18 #: contrib/admin/templates/admin/change_list.html:40 #: contrib/admin/templates/admin/delete_confirmation.html:8 @@ -380,8 +381,8 @@ msgid "Enter a username and password." msgstr "" #: contrib/admin/templates/admin/auth/user/change_password.html:17 -#: contrib/admin/templates/admin/auth/user/change_password.html:49 -#: contrib/admin/templates/admin/base.html:39 +#: contrib/admin/templates/admin/auth/user/change_password.html:54 +#: 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 "Change password" @@ -390,7 +391,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:17 +#: contrib/admin/templates/admin/login.html:19 #: contrib/admin/templates/registration/password_change_form.html:21 msgid "Please correct the error below." msgstr "" @@ -398,7 +399,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:17 +#: contrib/admin/templates/admin/login.html:19 #: contrib/admin/templates/registration/password_change_form.html:21 msgid "Please correct the errors below." msgstr "" @@ -408,21 +409,21 @@ msgstr "" msgid "Enter a new password for the user %(username)s." msgstr "" -#: contrib/admin/templates/admin/auth/user/change_password.html:43 -msgid "Enter the same password as above, for verification." +#: contrib/admin/templates/admin/base.html:32 +msgid "Welcome," msgstr "" -#: contrib/admin/templates/admin/base.html:30 -msgid "Welcome," +#: contrib/admin/templates/admin/base.html:37 +msgid "View site" msgstr "" -#: contrib/admin/templates/admin/base.html:36 +#: contrib/admin/templates/admin/base.html:42 #: 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:41 +#: contrib/admin/templates/admin/base.html:48 #: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_form.html:4 msgid "Log out" @@ -430,6 +431,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 msgid "Add" msgstr "" @@ -439,8 +441,8 @@ msgid "History" msgstr "" #: contrib/admin/templates/admin/change_form.html:35 -#: contrib/admin/templates/admin/edit_inline/stacked.html:9 -#: contrib/admin/templates/admin/edit_inline/tabular.html:30 +#: contrib/admin/templates/admin/edit_inline/stacked.html:10 +#: contrib/admin/templates/admin/edit_inline/tabular.html:33 msgid "View on site" msgstr "" @@ -467,11 +469,12 @@ 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/submit_line.html:6 msgid "Delete" msgstr "" -#: contrib/admin/templates/admin/delete_confirmation.html:19 +#: contrib/admin/templates/admin/delete_confirmation.html:18 #, python-format msgid "" "Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " @@ -479,30 +482,40 @@ msgid "" "following types of objects:" msgstr "" -#: contrib/admin/templates/admin/delete_confirmation.html:27 +#: contrib/admin/templates/admin/delete_confirmation.html:25 #, python-format msgid "" "Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " "following protected related objects:" msgstr "" -#: contrib/admin/templates/admin/delete_confirmation.html:35 +#: contrib/admin/templates/admin/delete_confirmation.html:32 #, python-format msgid "" "Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " "All of the following related items will be deleted:" msgstr "" -#: contrib/admin/templates/admin/delete_confirmation.html:40 -#: contrib/admin/templates/admin/delete_selected_confirmation.html:45 +#: contrib/admin/templates/admin/delete_confirmation.html:34 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:33 +msgid "Objects" +msgstr "" + +#: contrib/admin/templates/admin/delete_confirmation.html:41 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:44 msgid "Yes, I'm sure" msgstr "" +#: contrib/admin/templates/admin/delete_confirmation.html:42 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:45 +msgid "No, take me back" +msgstr "" + #: contrib/admin/templates/admin/delete_selected_confirmation.html:11 msgid "Delete multiple objects" msgstr "" -#: contrib/admin/templates/admin/delete_selected_confirmation.html:18 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:17 #, python-format msgid "" "Deleting the selected %(objects_name)s would result in deleting related " @@ -510,27 +523,34 @@ msgid "" "types of objects:" msgstr "" -#: contrib/admin/templates/admin/delete_selected_confirmation.html:26 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:24 #, python-format msgid "" "Deleting the selected %(objects_name)s would require deleting the following " "protected related objects:" msgstr "" -#: contrib/admin/templates/admin/delete_selected_confirmation.html:34 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:31 #, python-format msgid "" "Are you sure you want to delete the selected %(objects_name)s? All of the " "following objects and their related items will be deleted:" msgstr "" -#: contrib/admin/templates/admin/edit_inline/stacked.html:26 -#: contrib/admin/templates/admin/edit_inline/tabular.html:78 -msgid "Remove" +#: 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 +msgid "Change" msgstr "" #: contrib/admin/templates/admin/edit_inline/stacked.html:27 -#: contrib/admin/templates/admin/edit_inline/tabular.html:77 +#: contrib/admin/templates/admin/edit_inline/tabular.html:81 +msgid "Remove" +msgstr "" + +#: contrib/admin/templates/admin/edit_inline/stacked.html:28 +#: contrib/admin/templates/admin/edit_inline/tabular.html:80 #, python-format msgid "Add another %(verbose_name)s" msgstr "" @@ -544,15 +564,15 @@ msgstr "" msgid " By %(filter_title)s " msgstr "" +#: contrib/admin/templates/admin/includes/object_delete_summary.html:2 +msgid "Summary" +msgstr "" + #: contrib/admin/templates/admin/index.html:20 #, python-format msgid "Models in the %(name)s application" msgstr "" -#: contrib/admin/templates/admin/index.html:37 -msgid "Change" -msgstr "" - #: contrib/admin/templates/admin/index.html:47 msgid "You don't have permission to edit anything." msgstr "" @@ -580,7 +600,7 @@ msgid "" "the appropriate user." msgstr "" -#: contrib/admin/templates/admin/login.html:43 +#: contrib/admin/templates/admin/login.html:45 msgid "Forgotten your password or username?" msgstr "" @@ -603,6 +623,7 @@ msgid "" msgstr "" #: contrib/admin/templates/admin/pagination.html:10 +#: contrib/admin/templates/admin/search_form.html:9 msgid "Show all" msgstr "" @@ -611,6 +632,21 @@ msgstr "" msgid "Save" msgstr "" +#: contrib/admin/templates/admin/related_widget_wrapper.html:8 +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#: contrib/admin/templates/admin/related_widget_wrapper.html:16 +#, python-format +msgid "Add another %(model)s" +msgstr "" + +#: contrib/admin/templates/admin/related_widget_wrapper.html:24 +#, python-format +msgid "Delete selected %(model)s" +msgstr "" + #: contrib/admin/templates/admin/search_form.html:7 msgid "Search" msgstr "" @@ -662,7 +698,7 @@ msgid "" "password twice so we can verify you typed it in correctly." msgstr "" -#: contrib/admin/templates/registration/password_change_form.html:48 +#: contrib/admin/templates/registration/password_change_form.html:54 #: contrib/admin/templates/registration/password_reset_confirm.html:24 msgid "Change my password" msgstr "" @@ -703,8 +739,8 @@ msgstr "" #: contrib/admin/templates/registration/password_reset_done.html:15 msgid "" -"We've emailed you instructions for setting your password. You should be " -"receiving them shortly." +"We've emailed you instructions for setting your password, if an account " +"exists with the email you entered. You should receive them shortly." msgstr "" #: contrib/admin/templates/registration/password_reset_done.html:17 @@ -751,29 +787,29 @@ msgstr "" msgid "Reset my password" msgstr "" -#: contrib/admin/templatetags/admin_list.py:379 +#: contrib/admin/templatetags/admin_list.py:382 msgid "All dates" msgstr "" -#: contrib/admin/views/main.py:32 +#: contrib/admin/views/main.py:33 msgid "(None)" msgstr "" -#: contrib/admin/views/main.py:76 +#: contrib/admin/views/main.py:80 #, python-format msgid "Select %s" msgstr "" -#: contrib/admin/views/main.py:78 +#: contrib/admin/views/main.py:82 #, python-format msgid "Select %s to change" msgstr "" -#: contrib/admin/widgets.py:91 +#: contrib/admin/widgets.py:93 msgid "Date:" msgstr "" -#: contrib/admin/widgets.py:92 +#: contrib/admin/widgets.py:94 msgid "Time:" msgstr "" @@ -781,14 +817,10 @@ msgstr "" msgid "Lookup" msgstr "" -#: contrib/admin/widgets.py:280 -msgid "Add Another" -msgstr "" - -#: contrib/admin/widgets.py:333 +#: contrib/admin/widgets.py:365 msgid "Currently:" msgstr "" -#: contrib/admin/widgets.py:334 +#: contrib/admin/widgets.py:366 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 01195bdbc305..47af03bbca77 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: 2014-05-19 15:17+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -14,19 +14,19 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" #: contrib/admin/static/admin/js/SelectFilter2.js:45 -#, c-format +#, javascript-format msgid "Available %s" msgstr "" #: contrib/admin/static/admin/js/SelectFilter2.js:46 -#, c-format +#, 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 -#, c-format +#, javascript-format msgid "Type into this box to filter down the list of available %s." msgstr "" @@ -39,7 +39,7 @@ msgid "Choose all" msgstr "" #: contrib/admin/static/admin/js/SelectFilter2.js:61 -#, c-format +#, javascript-format msgid "Click to choose all %s at once." msgstr "" @@ -52,12 +52,12 @@ msgid "Remove" msgstr "" #: contrib/admin/static/admin/js/SelectFilter2.js:75 -#, c-format +#, javascript-format msgid "Chosen %s" msgstr "" #: contrib/admin/static/admin/js/SelectFilter2.js:76 -#, c-format +#, 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." @@ -68,7 +68,7 @@ msgid "Remove all" msgstr "" #: contrib/admin/static/admin/js/SelectFilter2.js:80 -#, c-format +#, javascript-format msgid "Click to remove all chosen %s at once." msgstr "" @@ -103,14 +103,14 @@ msgid "" msgstr "" #: contrib/admin/static/admin/js/admin/DateTimeShortcuts.js:79 -#, c-format +#, 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 -#, c-format +#, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." msgstr[0] "" diff --git a/django/contrib/admindocs/locale/en/LC_MESSAGES/django.po b/django/contrib/admindocs/locale/en/LC_MESSAGES/django.po index dfb765f2ca77..5bb2a8f7deeb 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: 2014-05-19 15:17+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -176,19 +176,19 @@ msgstr "" msgid "Model: %(name)s" msgstr "" -#: contrib/admindocs/templates/admin_doc/model_detail.html:35 +#: contrib/admindocs/templates/admin_doc/model_detail.html:34 msgid "Field" msgstr "" -#: contrib/admindocs/templates/admin_doc/model_detail.html:36 +#: contrib/admindocs/templates/admin_doc/model_detail.html:35 msgid "Type" msgstr "" -#: contrib/admindocs/templates/admin_doc/model_detail.html:37 +#: contrib/admindocs/templates/admin_doc/model_detail.html:36 msgid "Description" msgstr "" -#: contrib/admindocs/templates/admin_doc/model_detail.html:52 +#: contrib/admindocs/templates/admin_doc/model_detail.html:51 msgid "Back to Model Documentation" msgstr "" @@ -322,64 +322,59 @@ msgstr "" msgid "Field of type: %(field_type)s" msgstr "" -#: contrib/admindocs/views.py:69 contrib/admindocs/views.py:71 -#: contrib/admindocs/views.py:73 +#: contrib/admindocs/views.py:71 contrib/admindocs/views.py:73 +#: contrib/admindocs/views.py:75 msgid "tag:" msgstr "" -#: contrib/admindocs/views.py:102 contrib/admindocs/views.py:104 -#: contrib/admindocs/views.py:106 +#: contrib/admindocs/views.py:104 contrib/admindocs/views.py:106 +#: contrib/admindocs/views.py:108 msgid "filter:" msgstr "" -#: contrib/admindocs/views.py:153 contrib/admindocs/views.py:155 -#: contrib/admindocs/views.py:157 +#: contrib/admindocs/views.py:156 contrib/admindocs/views.py:158 +#: contrib/admindocs/views.py:160 msgid "view:" msgstr "" -#: contrib/admindocs/views.py:184 +#: contrib/admindocs/views.py:188 #, python-format msgid "App %(app_label)r not found" msgstr "" -#: contrib/admindocs/views.py:188 +#: contrib/admindocs/views.py:192 #, python-format msgid "Model %(model_name)r not found in app %(app_label)r" msgstr "" -#: contrib/admindocs/views.py:201 -#, python-format -msgid "the related `%(app_label)s.%(data_type)s` object" +#: 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 +msgid "model:" msgstr "" -#: contrib/admindocs/views.py:205 contrib/admindocs/views.py:225 -#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:244 -#: contrib/admindocs/views.py:258 contrib/admindocs/views.py:263 -msgid "model:" +#: contrib/admindocs/views.py:211 +#, python-format +msgid "the related `%(app_label)s.%(data_type)s` object" msgstr "" -#: contrib/admindocs/views.py:221 contrib/admindocs/views.py:253 +#: contrib/admindocs/views.py:231 contrib/admindocs/views.py:266 #, python-format msgid "related `%(app_label)s.%(object_name)s` objects" msgstr "" -#: contrib/admindocs/views.py:225 contrib/admindocs/views.py:258 +#: contrib/admindocs/views.py:238 contrib/admindocs/views.py:274 #, python-format msgid "all %s" msgstr "" -#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:263 +#: contrib/admindocs/views.py:243 contrib/admindocs/views.py:279 #, python-format msgid "number of %s" msgstr "" -#. Translators: %s is an object type name -#: contrib/admindocs/views.py:268 -#, python-format -msgid "Attributes on %s objects" -msgstr "" - -#: contrib/admindocs/views.py:362 +#: contrib/admindocs/views.py:389 #, 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 d37c78776de1..03447e81b7eb 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: 2014-05-20 12:20+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -34,231 +34,224 @@ msgstr "" msgid "Change password: %s" msgstr "" -#: contrib/auth/apps.py:10 +#: contrib/auth/apps.py:12 msgid "Authentication and Authorization" msgstr "" -#: contrib/auth/forms.py:37 contrib/auth/tests/test_forms.py:316 -#: contrib/auth/tests/test_forms.py:321 contrib/auth/tests/test_forms.py:504 +#: contrib/auth/forms.py:38 contrib/auth/tests/test_forms.py:315 +#: contrib/auth/tests/test_forms.py:320 contrib/auth/tests/test_forms.py:530 msgid "No password set." msgstr "" -#: contrib/auth/forms.py:43 contrib/auth/tests/test_forms.py:326 -#: contrib/auth/tests/test_forms.py:332 +#: contrib/auth/forms.py:44 contrib/auth/tests/test_forms.py:325 +#: contrib/auth/tests/test_forms.py:331 msgid "Invalid password format or unknown hashing algorithm." msgstr "" -#: contrib/auth/forms.py:76 -msgid "A user with that username already exists." -msgstr "" - -#: contrib/auth/forms.py:77 contrib/auth/forms.py:285 -#: contrib/auth/forms.py:349 +#: contrib/auth/forms.py:77 contrib/auth/forms.py:273 +#: contrib/auth/forms.py:337 msgid "The two password fields didn't match." msgstr "" -#: contrib/auth/forms.py:79 contrib/auth/forms.py:129 -msgid "Username" -msgstr "" - -#: contrib/auth/forms.py:81 contrib/auth/forms.py:130 -#: contrib/auth/models.py:383 -msgid "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only." -msgstr "" - -#: contrib/auth/forms.py:84 contrib/auth/forms.py:133 -msgid "This value may contain only letters, numbers and @/./+/-/_ characters." -msgstr "" - -#: contrib/auth/forms.py:86 contrib/auth/forms.py:135 -#: contrib/auth/forms.py:163 contrib/auth/forms.py:352 +#: contrib/auth/forms.py:79 contrib/auth/forms.py:108 +#: contrib/auth/forms.py:136 contrib/auth/forms.py:341 msgid "Password" msgstr "" -#: contrib/auth/forms.py:88 +#: contrib/auth/forms.py:81 msgid "Password confirmation" msgstr "" -#: contrib/auth/forms.py:90 +#: contrib/auth/forms.py:83 contrib/auth/forms.py:347 msgid "Enter the same password as above, for verification." msgstr "" -#: contrib/auth/forms.py:136 +#: contrib/auth/forms.py:109 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." msgstr "" -#: contrib/auth/forms.py:166 +#: contrib/auth/forms.py:139 #, python-format msgid "" "Please enter a correct %(username)s and password. Note that both fields may " "be case-sensitive." msgstr "" -#: contrib/auth/forms.py:168 +#: contrib/auth/forms.py:141 msgid "This account is inactive." msgstr "" -#: contrib/auth/forms.py:231 +#: contrib/auth/forms.py:204 msgid "Email" msgstr "" -#: contrib/auth/forms.py:287 +#: contrib/auth/forms.py:275 msgid "New password" msgstr "" -#: contrib/auth/forms.py:289 +#: contrib/auth/forms.py:277 msgid "New password confirmation" msgstr "" -#: contrib/auth/forms.py:320 +#: contrib/auth/forms.py:308 msgid "Your old password was entered incorrectly. Please enter it again." msgstr "" -#: contrib/auth/forms.py:323 +#: contrib/auth/forms.py:311 msgid "Old password" msgstr "" -#: contrib/auth/forms.py:354 +#: contrib/auth/forms.py:345 msgid "Password (again)" msgstr "" -#: contrib/auth/hashers.py:252 contrib/auth/hashers.py:335 -#: contrib/auth/hashers.py:383 contrib/auth/hashers.py:411 -#: contrib/auth/hashers.py:444 contrib/auth/hashers.py:477 -#: contrib/auth/hashers.py:511 +#: 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 msgid "algorithm" msgstr "" -#: contrib/auth/hashers.py:253 +#: contrib/auth/hashers.py:252 msgid "iterations" msgstr "" -#: contrib/auth/hashers.py:254 contrib/auth/hashers.py:337 -#: contrib/auth/hashers.py:384 contrib/auth/hashers.py:412 -#: contrib/auth/hashers.py:512 +#: 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 msgid "salt" msgstr "" -#: contrib/auth/hashers.py:255 contrib/auth/hashers.py:385 -#: contrib/auth/hashers.py:413 contrib/auth/hashers.py:445 -#: contrib/auth/hashers.py:478 contrib/auth/hashers.py:513 +#: 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 msgid "hash" msgstr "" -#: contrib/auth/hashers.py:336 +#: contrib/auth/hashers.py:335 msgid "work factor" msgstr "" -#: contrib/auth/hashers.py:338 +#: contrib/auth/hashers.py:337 msgid "checksum" msgstr "" -#: contrib/auth/models.py:64 contrib/auth/models.py:113 +#: contrib/auth/models.py:65 contrib/auth/models.py:116 msgid "name" msgstr "" -#: contrib/auth/models.py:66 +#: 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:115 +#: contrib/auth/models.py:72 contrib/auth/models.py:118 msgid "permissions" msgstr "" -#: contrib/auth/models.py:120 +#: contrib/auth/models.py:123 msgid "group" msgstr "" -#: contrib/auth/models.py:121 contrib/auth/models.py:308 +#: contrib/auth/models.py:124 contrib/auth/models.py:312 msgid "groups" msgstr "" -#: contrib/auth/models.py:193 +#: contrib/auth/models.py:197 msgid "password" msgstr "" -#: contrib/auth/models.py:194 +#: contrib/auth/models.py:198 msgid "last login" msgstr "" -#: contrib/auth/models.py:305 +#: contrib/auth/models.py:309 msgid "superuser status" msgstr "" -#: contrib/auth/models.py:306 +#: contrib/auth/models.py:310 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." msgstr "" -#: contrib/auth/models.py:309 +#: contrib/auth/models.py:313 msgid "" "The groups this user belongs to. A user will get all permissions granted to " -"each of his/her group." +"each of their groups." msgstr "" -#: contrib/auth/models.py:314 +#: contrib/auth/models.py:318 msgid "user permissions" msgstr "" -#: contrib/auth/models.py:315 +#: contrib/auth/models.py:319 msgid "Specific permissions for this user." msgstr "" -#: contrib/auth/models.py:382 +#: contrib/auth/models.py:386 msgid "username" msgstr "" -#: contrib/auth/models.py:386 -msgid "Enter a valid username." +#: contrib/auth/models.py:387 +msgid "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only." +msgstr "" + +#: contrib/auth/models.py:391 +msgid "" +"Enter a valid username. This value may contain only letters, numbers and @/./" +"+/-/_ characters." +msgstr "" + +#: contrib/auth/models.py:396 +msgid "A user with that username already exists." msgstr "" -#: contrib/auth/models.py:388 +#: contrib/auth/models.py:398 msgid "first name" msgstr "" -#: contrib/auth/models.py:389 +#: contrib/auth/models.py:399 msgid "last name" msgstr "" -#: contrib/auth/models.py:390 +#: contrib/auth/models.py:400 msgid "email address" msgstr "" -#: contrib/auth/models.py:391 +#: contrib/auth/models.py:401 msgid "staff status" msgstr "" -#: contrib/auth/models.py:392 +#: contrib/auth/models.py:402 msgid "Designates whether the user can log into this admin site." msgstr "" -#: contrib/auth/models.py:394 +#: contrib/auth/models.py:404 msgid "active" msgstr "" -#: contrib/auth/models.py:395 +#: contrib/auth/models.py:405 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." msgstr "" -#: contrib/auth/models.py:397 +#: contrib/auth/models.py:407 msgid "date joined" msgstr "" -#: contrib/auth/models.py:405 +#: contrib/auth/models.py:415 msgid "user" msgstr "" -#: contrib/auth/models.py:406 +#: contrib/auth/models.py:416 msgid "users" msgstr "" @@ -267,38 +260,38 @@ msgstr "" msgid "Password reset on %(site_name)s" msgstr "" -#: contrib/auth/tests/test_forms.py:387 +#: contrib/auth/tests/test_forms.py:383 msgid "Enter a valid email address." msgstr "" -#: contrib/auth/views.py:92 +#: contrib/auth/views.py:98 msgid "Logged out" msgstr "" -#: contrib/auth/views.py:169 +#: contrib/auth/views.py:184 msgid "Password reset" msgstr "" -#: contrib/auth/views.py:181 -msgid "Password reset successful" +#: contrib/auth/views.py:199 +msgid "Password reset sent" msgstr "" -#: contrib/auth/views.py:216 +#: contrib/auth/views.py:237 msgid "Enter new password" msgstr "" -#: contrib/auth/views.py:227 +#: contrib/auth/views.py:248 msgid "Password reset unsuccessful" msgstr "" -#: contrib/auth/views.py:244 +#: contrib/auth/views.py:268 msgid "Password reset complete" msgstr "" -#: contrib/auth/views.py:278 +#: contrib/auth/views.py:305 msgid "Password change" msgstr "" -#: contrib/auth/views.py:291 +#: contrib/auth/views.py:321 msgid "Password change successful" msgstr "" diff --git a/django/contrib/contenttypes/locale/en/LC_MESSAGES/django.po b/django/contrib/contenttypes/locale/en/LC_MESSAGES/django.po index e337553ac823..7c42dd4eff7e 100644 --- a/django/contrib/contenttypes/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/contenttypes/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: 2014-05-19 15:17+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -13,19 +13,19 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: contrib/contenttypes/apps.py:9 +#: contrib/contenttypes/apps.py:12 msgid "Content Types" msgstr "" -#: contrib/contenttypes/models.py:134 +#: contrib/contenttypes/models.py:160 msgid "python model class name" msgstr "" -#: contrib/contenttypes/models.py:138 +#: contrib/contenttypes/models.py:164 msgid "content type" msgstr "" -#: contrib/contenttypes/models.py:139 +#: contrib/contenttypes/models.py:165 msgid "content types" msgstr "" diff --git a/django/contrib/flatpages/locale/en/LC_MESSAGES/django.po b/django/contrib/flatpages/locale/en/LC_MESSAGES/django.po index e2d832b5d594..10fa09aa9df6 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: 2014-05-19 15:17+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -30,21 +30,21 @@ msgid "" "Example: '/about/contact/'. Make sure to have leading and trailing slashes." msgstr "" -#: contrib/flatpages/forms.py:11 +#: contrib/flatpages/forms.py:12 msgid "" "This value must contain only letters, numbers, dots, underscores, dashes, " "slashes or tildes." msgstr "" -#: contrib/flatpages/forms.py:22 +#: contrib/flatpages/forms.py:25 msgid "URL is missing a leading slash." msgstr "" -#: contrib/flatpages/forms.py:29 +#: contrib/flatpages/forms.py:32 msgid "URL is missing a trailing slash." msgstr "" -#: contrib/flatpages/forms.py:46 +#: contrib/flatpages/forms.py:49 #, python-format msgid "Flatpage with url %(url)s already exists for site %(site)s" msgstr "" @@ -65,25 +65,25 @@ msgstr "" msgid "template name" msgstr "" -#: contrib/flatpages/models.py:17 +#: contrib/flatpages/models.py:18 msgid "" "Example: 'flatpages/contact_page.html'. If this isn't provided, the system " "will use 'flatpages/default.html'." msgstr "" -#: contrib/flatpages/models.py:18 +#: contrib/flatpages/models.py:22 msgid "registration required" msgstr "" -#: contrib/flatpages/models.py:19 +#: contrib/flatpages/models.py:23 msgid "If this is checked, only logged-in users will be able to view the page." msgstr "" -#: contrib/flatpages/models.py:25 +#: contrib/flatpages/models.py:29 msgid "flat page" msgstr "" -#: contrib/flatpages/models.py:26 +#: contrib/flatpages/models.py:30 msgid "flat pages" msgstr "" diff --git a/django/contrib/gis/locale/en/LC_MESSAGES/django.po b/django/contrib/gis/locale/en/LC_MESSAGES/django.po index be68c9f53f36..6dec22a1f989 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: 2014-05-19 15:17+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -13,42 +13,46 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: contrib/gis/apps.py:8 +#: contrib/gis/apps.py:9 msgid "GIS" msgstr "" -#: contrib/gis/db/models/fields.py:56 +#: contrib/gis/db/models/fields.py:78 msgid "The base GIS field -- maps to the OpenGIS Specification Geometry type." msgstr "" -#: contrib/gis/db/models/fields.py:298 +#: contrib/gis/db/models/fields.py:324 msgid "Point" msgstr "" -#: contrib/gis/db/models/fields.py:304 +#: contrib/gis/db/models/fields.py:330 msgid "Line string" msgstr "" -#: contrib/gis/db/models/fields.py:310 +#: contrib/gis/db/models/fields.py:336 msgid "Polygon" msgstr "" -#: contrib/gis/db/models/fields.py:316 +#: contrib/gis/db/models/fields.py:342 msgid "Multi-point" msgstr "" -#: contrib/gis/db/models/fields.py:322 +#: contrib/gis/db/models/fields.py:348 msgid "Multi-line string" msgstr "" -#: contrib/gis/db/models/fields.py:328 +#: contrib/gis/db/models/fields.py:354 msgid "Multi polygon" msgstr "" -#: contrib/gis/db/models/fields.py:334 +#: contrib/gis/db/models/fields.py:360 msgid "Geometry collection" msgstr "" +#: contrib/gis/db/models/fields.py:366 +msgid "Extent Aggregate Field" +msgstr "" + #: contrib/gis/forms/fields.py:22 msgid "No geometry value provided." msgstr "" diff --git a/django/contrib/humanize/locale/en/LC_MESSAGES/django.po b/django/contrib/humanize/locale/en/LC_MESSAGES/django.po index 0f0bdddb899e..c406066732ae 100644 --- a/django/contrib/humanize/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/humanize/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: 2014-05-19 15:17+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -207,48 +207,48 @@ msgstr "" msgid "five" msgstr "" -#: contrib/humanize/templatetags/humanize.py:153 +#: contrib/humanize/templatetags/humanize.py:154 msgid "six" msgstr "" -#: contrib/humanize/templatetags/humanize.py:153 +#: contrib/humanize/templatetags/humanize.py:154 msgid "seven" msgstr "" -#: contrib/humanize/templatetags/humanize.py:153 +#: contrib/humanize/templatetags/humanize.py:154 msgid "eight" msgstr "" -#: contrib/humanize/templatetags/humanize.py:153 +#: contrib/humanize/templatetags/humanize.py:154 msgid "nine" msgstr "" -#: contrib/humanize/templatetags/humanize.py:177 contrib/humanize/tests.py:145 +#: contrib/humanize/templatetags/humanize.py:178 contrib/humanize/tests.py:145 msgid "today" msgstr "" -#: contrib/humanize/templatetags/humanize.py:179 contrib/humanize/tests.py:145 +#: contrib/humanize/templatetags/humanize.py:180 contrib/humanize/tests.py:145 msgid "tomorrow" msgstr "" -#: contrib/humanize/templatetags/humanize.py:181 contrib/humanize/tests.py:145 +#: contrib/humanize/templatetags/humanize.py:182 contrib/humanize/tests.py:145 msgid "yesterday" msgstr "" -#: contrib/humanize/templatetags/humanize.py:201 +#: contrib/humanize/templatetags/humanize.py:202 #, python-format msgctxt "naturaltime" msgid "%(delta)s ago" msgstr "" -#: contrib/humanize/templatetags/humanize.py:204 -#: contrib/humanize/templatetags/humanize.py:232 +#: contrib/humanize/templatetags/humanize.py:205 +#: contrib/humanize/templatetags/humanize.py:233 msgid "now" msgstr "" #. Translators: please keep a non-breaking space (U+00A0) #. between count and time unit. -#: contrib/humanize/templatetags/humanize.py:209 +#: contrib/humanize/templatetags/humanize.py:210 #, python-format msgid "a second ago" msgid_plural "%(count)s seconds ago" @@ -257,7 +257,7 @@ msgstr[1] "" #. Translators: please keep a non-breaking space (U+00A0) #. between count and time unit. -#: contrib/humanize/templatetags/humanize.py:216 +#: contrib/humanize/templatetags/humanize.py:217 #, python-format msgid "a minute ago" msgid_plural "%(count)s minutes ago" @@ -266,14 +266,14 @@ msgstr[1] "" #. Translators: please keep a non-breaking space (U+00A0) #. between count and time unit. -#: contrib/humanize/templatetags/humanize.py:223 +#: contrib/humanize/templatetags/humanize.py:224 #, python-format msgid "an hour ago" msgid_plural "%(count)s hours ago" msgstr[0] "" msgstr[1] "" -#: contrib/humanize/templatetags/humanize.py:229 +#: contrib/humanize/templatetags/humanize.py:230 #, python-format msgctxt "naturaltime" msgid "%(delta)s from now" @@ -281,7 +281,7 @@ msgstr "" #. Translators: please keep a non-breaking space (U+00A0) #. between count and time unit. -#: contrib/humanize/templatetags/humanize.py:237 +#: contrib/humanize/templatetags/humanize.py:238 #, python-format msgid "a second from now" msgid_plural "%(count)s seconds from now" @@ -290,7 +290,7 @@ msgstr[1] "" #. Translators: please keep a non-breaking space (U+00A0) #. between count and time unit. -#: contrib/humanize/templatetags/humanize.py:244 +#: contrib/humanize/templatetags/humanize.py:245 #, python-format msgid "a minute from now" msgid_plural "%(count)s minutes from now" @@ -299,7 +299,7 @@ msgstr[1] "" #. Translators: please keep a non-breaking space (U+00A0) #. between count and time unit. -#: contrib/humanize/templatetags/humanize.py:251 +#: contrib/humanize/templatetags/humanize.py:252 #, python-format msgid "an hour from now" msgid_plural "%(count)s hours from now" diff --git a/django/contrib/messages/locale/en/LC_MESSAGES/django.po b/django/contrib/messages/locale/en/LC_MESSAGES/django.po index 998a504a3745..6ca468b88364 100644 --- a/django/contrib/messages/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/messages/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: 2014-05-19 15:17+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -17,6 +17,6 @@ msgstr "" msgid "Messages" msgstr "" -#: contrib/messages/tests/base.py:105 +#: contrib/messages/tests/base.py:113 msgid "lazy message" msgstr "" diff --git a/django/contrib/redirects/locale/en/LC_MESSAGES/django.po b/django/contrib/redirects/locale/en/LC_MESSAGES/django.po index 045e4cbec638..2b73f2d16183 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: 2014-05-19 15:17+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" diff --git a/django/contrib/sessions/locale/en/LC_MESSAGES/django.po b/django/contrib/sessions/locale/en/LC_MESSAGES/django.po index c4e7cb0ba7ef..2ce18723404f 100644 --- a/django/contrib/sessions/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/sessions/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: 2014-05-19 15:17+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -17,22 +17,22 @@ msgstr "" msgid "Sessions" msgstr "" -#: contrib/sessions/models.py:38 +#: contrib/sessions/models.py:44 msgid "session key" msgstr "" -#: contrib/sessions/models.py:40 +#: contrib/sessions/models.py:46 msgid "session data" msgstr "" -#: contrib/sessions/models.py:41 +#: contrib/sessions/models.py:47 msgid "expire date" msgstr "" -#: contrib/sessions/models.py:46 +#: contrib/sessions/models.py:52 msgid "session" msgstr "" -#: contrib/sessions/models.py:47 +#: contrib/sessions/models.py:53 msgid "sessions" msgstr "" diff --git a/django/contrib/sites/locale/en/LC_MESSAGES/django.po b/django/contrib/sites/locale/en/LC_MESSAGES/django.po index 86595beea4ae..3b1884c4af54 100644 --- a/django/contrib/sites/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/sites/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: 2014-05-19 15:17+0200\n" +"POT-Creation-Date: 2015-01-17 11:07+0100\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/sites/apps.py:8 +#: contrib/sites/apps.py:11 msgid "Sites" msgstr "" @@ -21,18 +21,18 @@ msgstr "" msgid "The domain name cannot contain any spaces or tabs." msgstr "" -#: contrib/sites/models.py:67 +#: contrib/sites/models.py:81 msgid "domain name" msgstr "" -#: contrib/sites/models.py:69 +#: contrib/sites/models.py:83 msgid "display name" msgstr "" -#: contrib/sites/models.py:74 +#: contrib/sites/models.py:88 msgid "site" msgstr "" -#: contrib/sites/models.py:75 +#: contrib/sites/models.py:89 msgid "sites" msgstr "" From b714316c0606110cd508863b6d12650004366aaf Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 17 Jan 2015 09:30:52 -0500 Subject: [PATCH 0007/1125] [1.8.x] Documented django.utils.timezone.FixedOffset; thanks Aymeric. Backport of 25264d4e2a4b3fd6a25e6b617388ea24f3d48d63 from master --- docs/ref/utils.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 55184c277b1f..c9a68b308ebb 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -155,7 +155,7 @@ The functions defined in this module share the following properties: Parses a string and returns a :class:`datetime.datetime`. UTC offsets are supported; if ``value`` describes one, the result's - ``tzinfo`` attribute is a :class:`~django.utils.tzinfo.FixedOffset` + ``tzinfo`` attribute is a :class:`~django.utils.timezone.FixedOffset` instance. .. function:: parse_duration(value) @@ -884,6 +884,13 @@ appropriate entities. :class:`~datetime.tzinfo` instance that represents UTC. +.. class:: FixedOffset(offset=None, name=None) + + .. versionadded:: 1.7 + + A :class:`~datetime.tzinfo` subclass modeling a fixed offset from UTC. + ``offset`` is an integer number of minutes east of UTC. + .. function:: get_fixed_timezone(offset) .. versionadded:: 1.7 From 6b1b7263f4b0065a8a514abfd7e74e17e742dbf6 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 17 Jan 2015 15:43:02 +0100 Subject: [PATCH 0008/1125] [1.8.x] Fixed PostGIS crosses lookup and added crosses test Backport of aff0e54d5 from master. --- django/contrib/gis/db/backends/base/features.py | 4 ++++ .../contrib/gis/db/backends/postgis/operations.py | 2 +- django/contrib/gis/tests/geoapp/tests.py | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/django/contrib/gis/db/backends/base/features.py b/django/contrib/gis/db/backends/base/features.py index f43724dcf75e..ecefde493bfd 100644 --- a/django/contrib/gis/db/backends/base/features.py +++ b/django/contrib/gis/db/backends/base/features.py @@ -41,6 +41,10 @@ def supports_bbcontains_lookup(self): def supports_contained_lookup(self): return 'contained' in self.connection.ops.gis_operators + @property + def supports_crosses_lookup(self): + return 'crosses' in self.connection.ops.gis_operators + @property def supports_dwithin_lookup(self): return 'dwithin' in self.connection.ops.gis_operators diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 44cab5d12aa9..2255897f2f29 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -71,7 +71,7 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): 'contains_properly': PostGISOperator(func='ST_ContainsProperly'), 'coveredby': PostGISOperator(func='ST_CoveredBy', geography=True), 'covers': PostGISOperator(func='ST_Covers', geography=True), - 'crosses': PostGISOperator(func='ST_Crosses)'), + 'crosses': PostGISOperator(func='ST_Crosses'), 'disjoint': PostGISOperator(func='ST_Disjoint'), 'equals': PostGISOperator(func='ST_Equals'), 'intersects': PostGISOperator(func='ST_Intersects', geography=True), diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index 8d20ef843ab4..0dc4ce269d7e 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -286,6 +286,21 @@ def test_contains_contained_lookups(self): self.assertEqual(1, len(qs)) self.assertEqual('Texas', qs[0].name) + @skipUnlessDBFeature("supports_crosses_lookup") + def test_crosses_lookup(self): + Track.objects.create( + name='Line1', + line=LineString([(-95, 29), (-60, 0)]) + ) + self.assertEqual( + Track.objects.filter(line__crosses=LineString([(-95, 0), (-60, 29)])).count(), + 1 + ) + self.assertEqual( + Track.objects.filter(line__crosses=LineString([(-95, 30), (0, 30)])).count(), + 0 + ) + @skipUnlessDBFeature("supports_left_right_lookups") def test_left_right_lookups(self): "Testing the 'left' and 'right' lookup types." From 9f86d86c62e5e805ba0191ccd546e174301998ea Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 17 Jan 2015 10:46:27 -0500 Subject: [PATCH 0009/1125] [1.8.x] Updated tutorial 1 with actual migrate output. --- docs/intro/tutorial01.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 8c9c4a30e7ee..5881878e68ee 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -537,18 +537,16 @@ Now, run :djadmin:`migrate` again to create those model tables in your database: .. code-block:: bash $ python manage.py migrate - Operations to perform: - Synchronize unmigrated apps: sessions, admin, messages, auth, staticfiles, contenttypes - Apply all migrations: polls + Synchronize unmigrated apps: staticfiles, messages + Apply all migrations: admin, contenttypes, polls, auth, sessions Synchronizing apps without migrations: Creating tables... + Running deferred SQL... Installing custom SQL... - Installing indexes... - Installed 0 object(s) from 0 fixture(s) Running migrations: - Applying polls.0001_initial... OK - + Rendering model states... DONE + Applying ... OK The :djadmin:`migrate` command takes all the migrations that haven't been applied (Django tracks which ones are applied using a special table in your From bfa34788507505584a9885c6856c6a6083670879 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 17 Jan 2015 12:49:16 -0500 Subject: [PATCH 0010/1125] [1.8.x] Replaced deprecated requires_model_validation in docs. Backport of 18192b9fa4387d5e6c677a7929d91ce04f92cda7 from master --- docs/howto/custom-management-commands.txt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index cab7ea47acf6..5d7ec69b1f56 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -346,17 +346,15 @@ the :meth:`~BaseCommand.handle` method must be implemented. .. method:: BaseCommand.get_version() - Return the Django version, which should be correct for all - built-in Django commands. User-supplied commands can - override this method to return their own version. + Returns the Django version, which should be correct for all built-in Django + commands. User-supplied commands can override this method to return their + own version. .. method:: BaseCommand.execute(*args, **options) - Try to execute this command, performing model validation if - needed (as controlled by the attribute - :attr:`requires_model_validation`). If the command raises a - :class:`CommandError`, intercept it and print it sensibly to - stderr. + Tries to execute this command, performing system checks if needed (as + controlled by the :attr:`requires_system_checks` attribute). If the command + raises a :class:`CommandError`, it's intercepted and printed to stderr. .. admonition:: Calling a management command in your code From a58a120021e4d63fbb176ecd88a7fefae54ff19c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 17 Jan 2015 13:27:59 -0500 Subject: [PATCH 0011/1125] [1.8.x] Standardized indentation in docs/howto/custom-management-commands.txt. --- docs/howto/custom-management-commands.txt | 171 +++++++++++----------- 1 file changed, 84 insertions(+), 87 deletions(-) diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index 5d7ec69b1f56..6547bf51823a 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -41,9 +41,9 @@ The ``closepoll.py`` module has only one requirement -- it must define a class .. admonition:: Standalone scripts - Custom management commands are especially useful for running standalone - scripts or for scripts that are periodically executed from the UNIX crontab - or from Windows scheduled tasks control panel. + Custom management commands are especially useful for running standalone + scripts or for scripts that are periodically executed from the UNIX crontab + or from Windows scheduled tasks control panel. To implement the command, edit ``polls/management/commands/closepoll.py`` to look like this:: @@ -69,13 +69,15 @@ look like this:: self.stdout.write('Successfully closed poll "%s"' % poll_id) -Before Django 1.8, management commands were based on the :py:mod:`optparse` -module, and positional arguments were passed in ``*args`` while optional -arguments were passed in ``**options``. Now that management commands use -:py:mod:`argparse` for argument parsing, all arguments are passed in -``**options`` by default, unless you name your positional arguments to ``args`` -(compatibility mode). You are encouraged to exclusively use ``**options`` for -new commands. +.. versionchanged:: 1.8 + + Before Django 1.8, management commands were based on the :py:mod:`optparse` + module, and positional arguments were passed in ``*args`` while optional + arguments were passed in ``**options``. Now that management commands use + :py:mod:`argparse` for argument parsing, all arguments are passed in + ``**options`` by default, unless you name your positional arguments to + ``args`` (compatibility mode). You are encouraged to exclusively use + ``**options`` for new commands. .. _management-commands-output: @@ -227,97 +229,96 @@ All attributes can be set in your derived class and can be used in .. attribute:: BaseCommand.args - A string listing the arguments accepted by the command, - suitable for use in help messages; e.g., a command which takes - a list of application names might set this to ''. + A string listing the arguments accepted by the command, + suitable for use in help messages; e.g., a command which takes + a list of application names might set this to ''. - .. deprecated:: 1.8 + .. deprecated:: 1.8 - This should be done now in the :meth:`~BaseCommand.add_arguments()` - method, by calling the ``parser.add_argument()`` method. See the - ``closepoll`` example above. + This should be done now in the :meth:`~BaseCommand.add_arguments()` + method, by calling the ``parser.add_argument()`` method. See the + ``closepoll`` example above. .. attribute:: BaseCommand.can_import_settings - A boolean indicating whether the command needs to be able to - import Django settings; if ``True``, ``execute()`` will verify - that this is possible before proceeding. Default value is - ``True``. + A boolean indicating whether the command needs to be able to + import Django settings; if ``True``, ``execute()`` will verify + that this is possible before proceeding. Default value is + ``True``. .. attribute:: BaseCommand.help - A short description of the command, which will be printed in the - help message when the user runs the command - ``python manage.py help ``. + A short description of the command, which will be printed in the + help message when the user runs the command + ``python manage.py help ``. .. attribute:: BaseCommand.missing_args_message -.. versionadded:: 1.8 + .. versionadded:: 1.8 - If your command defines mandatory positional arguments, you can customize - the message error returned in the case of missing arguments. The default is - output by :py:mod:`argparse` ("too few arguments"). + If your command defines mandatory positional arguments, you can customize + the message error returned in the case of missing arguments. The default is + output by :py:mod:`argparse` ("too few arguments"). .. attribute:: BaseCommand.option_list - This is the list of ``optparse`` options which will be fed - into the command's ``OptionParser`` for parsing arguments. + This is the list of ``optparse`` options which will be fed + into the command's ``OptionParser`` for parsing arguments. - .. deprecated:: 1.8 + .. deprecated:: 1.8 - You should now override the :meth:`~BaseCommand.add_arguments` method to - add custom arguments accepted by your command. - See :ref:`the example above `. + You should now override the :meth:`~BaseCommand.add_arguments` method + to add custom arguments accepted by your command. See :ref:`the example + above `. .. attribute:: BaseCommand.output_transaction - A boolean indicating whether the command outputs SQL - statements; if ``True``, the output will automatically be - wrapped with ``BEGIN;`` and ``COMMIT;``. Default value is - ``False``. + A boolean indicating whether the command outputs SQL statements; if + ``True``, the output will automatically be wrapped with ``BEGIN;`` and + ``COMMIT;``. Default value is ``False``. .. attribute:: BaseCommand.requires_system_checks -.. versionadded:: 1.7 + .. versionadded:: 1.7 - A boolean; if ``True``, the entire Django project will be checked for - potential problems prior to executing the command. If - ``requires_system_checks`` is missing, the value of - ``requires_model_validation`` is used. If the latter flag is missing - as well, the default value (``True``) is used. Defining both - ``requires_system_checks`` and ``requires_model_validation`` will result - in an error. + A boolean; if ``True``, the entire Django project will be checked for + potential problems prior to executing the command. If + ``requires_system_checks`` is missing, the value of + ``requires_model_validation`` is used. If the latter flag is missing + as well, the default value (``True``) is used. Defining both + ``requires_system_checks`` and ``requires_model_validation`` will result + in an error. .. attribute:: BaseCommand.requires_model_validation -.. deprecated:: 1.7 - Replaced by ``requires_system_checks`` + .. deprecated:: 1.7 + Replaced by ``requires_system_checks`` - A boolean; if ``True``, validation of installed models will be - performed prior to executing the command. Default value is - ``True``. To validate an individual application's models - rather than all applications' models, call - :meth:`~BaseCommand.validate` from :meth:`~BaseCommand.handle`. + A boolean; if ``True``, validation of installed models will be performed + prior to executing the command. Default value is ``True``. To validate an + individual application's models rather than all applications' models, call + :meth:`~BaseCommand.validate` from :meth:`~BaseCommand.handle`. .. attribute:: BaseCommand.leave_locale_alone - A boolean indicating whether the locale set in settings should be preserved - during the execution of the command instead of being forcibly set to 'en-us'. + A boolean indicating whether the locale set in settings should be preserved + during the execution of the command instead of being forcibly set to 'en-us'. - Default value is ``False``. + Default value is ``False``. - Make sure you know what you are doing if you decide to change the value of - this option in your custom command if it creates database content that - is locale-sensitive and such content shouldn't contain any translations (like - it happens e.g. with django.contrib.auth permissions) as making the locale - differ from the de facto default 'en-us' might cause unintended effects. See - the `Management commands and locales`_ section above for further details. + Make sure you know what you are doing if you decide to change the value of + this option in your custom command if it creates database content that + is locale-sensitive and such content shouldn't contain any translations + (like it happens e.g. with django.contrib.auth permissions) as making the + locale differ from the de facto default 'en-us' might cause unintended + effects. Seethe `Management commands and locales`_ section above for + further details. - This option can't be ``False`` when the - :data:`~BaseCommand.can_import_settings` option is set to ``False`` too - because attempting to set the locale needs access to settings. This condition - will generate a :class:`CommandError`. + This option can't be ``False`` when the + :data:`~BaseCommand.can_import_settings` option is set to ``False`` too + because attempting to set the locale needs access to settings. This + condition will generate a :class:`CommandError`. Methods ------- @@ -337,7 +338,7 @@ the :meth:`~BaseCommand.handle` method must be implemented. .. method:: BaseCommand.add_arguments(parser) -.. versionadded:: 1.8 + .. versionadded:: 1.8 Entry point to add parser arguments to handle command line arguments passed to the command. Custom commands should override this method to add both @@ -367,7 +368,7 @@ the :meth:`~BaseCommand.handle` method must be implemented. .. method:: BaseCommand.check(app_configs=None, tags=None, display_num_errors=False) -.. versionadded:: 1.7 + .. versionadded:: 1.7 Uses the system check framework to inspect the entire Django project for potential problems. Serious problems are raised as a :class:`CommandError`; @@ -379,8 +380,8 @@ the :meth:`~BaseCommand.handle` method must be implemented. .. method:: BaseCommand.validate(app=None, display_num_errors=False) -.. deprecated:: 1.7 - Replaced with the :djadmin:`check` command + .. deprecated:: 1.7 + Replaced with the :djadmin:`check` command If ``app`` is None, then all installed apps are checked for errors. @@ -422,17 +423,16 @@ each application. .. class:: LabelCommand -A management command which takes one or more arbitrary arguments -(labels) on the command line, and does something with each of -them. +A management command which takes one or more arbitrary arguments (labels) on +the command line, and does something with each of them. Rather than implementing :meth:`~BaseCommand.handle`, subclasses must implement :meth:`~LabelCommand.handle_label`, which will be called once for each label. .. method:: LabelCommand.handle_label(label, **options) - Perform the command's actions for ``label``, which will be the - string as given on the command line. + Perform the command's actions for ``label``, which will be the string as + given on the command line. .. class:: NoArgsCommand @@ -457,16 +457,13 @@ Command exceptions .. class:: CommandError -Exception class indicating a problem while executing a management -command. +Exception class indicating a problem while executing a management command. -If this exception is raised during the execution of a management -command from a command line console, it will be caught and turned into a -nicely-printed error message to the appropriate output stream (i.e., stderr); -as a result, raising this exception (with a sensible description of the -error) is the preferred way to indicate that something has gone -wrong in the execution of a command. +If this exception is raised during the execution of a management command from a +command line console, it will be caught and turned into a nicely-printed error +message to the appropriate output stream (i.e., stderr); as a result, raising +this exception (with a sensible description of the error) is the preferred way +to indicate that something has gone wrong in the execution of a command. -If a management command is called from code through -:ref:`call_command `, it's up to you to catch the exception -when needed. +If a management command is called from code through :ref:`call_command +`, it's up to you to catch the exception when needed. From 5512338d4f2d4193cf4d9fa3dd707b149b8b9450 Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Sat, 17 Jan 2015 00:51:31 +0100 Subject: [PATCH 0012/1125] [1.8.x] Cleaned up migration writer tests Backport of 65d55c409343aab7c2ae771c459720ef797b4cdb from master --- tests/migrations/test_writer.py | 130 +++++++++++++++----------------- 1 file changed, 61 insertions(+), 69 deletions(-) diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index 2519e43f4275..74c53b1a2d51 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -136,43 +136,49 @@ def assertSerializedFieldEqual(self, value): self.assertEqual(value.null, new_value.null) self.assertEqual(value.unique, new_value.unique) - def test_serialize(self): - """ - Tests various different forms of the serializer. - This does not care about formatting, just that the parsed result is - correct, so we always exec() the result and check that. - """ - # Basic values + def test_serialize_numbers(self): self.assertSerializedEqual(1) self.assertSerializedEqual(1.2) self.assertTrue(math.isinf(self.serialize_round_trip(float("inf")))) self.assertTrue(math.isinf(self.serialize_round_trip(float("-inf")))) self.assertTrue(math.isnan(self.serialize_round_trip(float("nan")))) + + def test_serialize_constants(self): self.assertSerializedEqual(None) + self.assertSerializedEqual(True) + self.assertSerializedEqual(False) + + def test_serialize_strings(self): self.assertSerializedEqual(b"foobar") string, imports = MigrationWriter.serialize(b"foobar") self.assertEqual(string, "b'foobar'") self.assertSerializedEqual("föobár") string, imports = MigrationWriter.serialize("foobar") self.assertEqual(string, "'foobar'") + + def test_serialize_collections(self): self.assertSerializedEqual({1: 2}) self.assertSerializedEqual(["a", 2, True, None]) self.assertSerializedEqual({2, 3, "eighty"}) self.assertSerializedEqual({"lalalala": ["yeah", "no", "maybe"]}) self.assertSerializedEqual(_('Hello')) - # Builtins - self.assertSerializedEqual([list, tuple, dict, set]) - string, imports = MigrationWriter.serialize([list, tuple, dict, set]) - self.assertEqual(string, "[list, tuple, dict, set]") - self.assertEqual(imports, set()) - # Functions + + def test_serialize_builtin_types(self): + self.assertSerializedEqual([list, tuple, dict, set, frozenset]) + self.assertSerializedResultEqual( + [list, tuple, dict, set, frozenset], + ("[list, tuple, dict, set, frozenset]", set()) + ) + + def test_serialize_functions(self): with six.assertRaisesRegex(self, ValueError, 'Cannot serialize function: lambda'): self.assertSerializedEqual(lambda x: 42) self.assertSerializedEqual(models.SET_NULL) string, imports = MigrationWriter.serialize(models.SET(42)) self.assertEqual(string, 'models.SET(42)') self.serialize_round_trip(models.SET(42)) - # Datetime stuff + + def test_serialize_datetime(self): self.assertSerializedEqual(datetime.datetime.utcnow()) self.assertSerializedEqual(datetime.datetime.utcnow) self.assertSerializedEqual(datetime.datetime.today()) @@ -181,41 +187,46 @@ def test_serialize(self): self.assertSerializedEqual(datetime.date.today) self.assertSerializedEqual(datetime.datetime.now().time()) self.assertSerializedEqual(datetime.datetime(2014, 1, 1, 1, 1, tzinfo=get_default_timezone())) - self.assertSerializedEqual(datetime.datetime(2014, 1, 1, 1, 1, tzinfo=FixedOffset(180))) - safe_date = datetime_safe.date(2014, 3, 31) - string, imports = MigrationWriter.serialize(safe_date) - self.assertEqual(string, repr(datetime.date(2014, 3, 31))) - self.assertEqual(imports, {'import datetime'}) - safe_time = datetime_safe.time(10, 25) - string, imports = MigrationWriter.serialize(safe_time) - self.assertEqual(string, repr(datetime.time(10, 25))) - self.assertEqual(imports, {'import datetime'}) - safe_datetime = datetime_safe.datetime(2014, 3, 31, 16, 4, 31) - string, imports = MigrationWriter.serialize(safe_datetime) - self.assertEqual(string, repr(datetime.datetime(2014, 3, 31, 16, 4, 31))) - self.assertEqual(imports, {'import datetime'}) - timezone_aware_datetime = datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc) - string, imports = MigrationWriter.serialize(timezone_aware_datetime) - self.assertEqual(string, "datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc)") - self.assertEqual(imports, {'import datetime', 'from django.utils.timezone import utc'}) - # Django fields + self.assertSerializedEqual(datetime.datetime(2013, 12, 31, 22, 1, tzinfo=FixedOffset(180))) + self.assertSerializedResultEqual( + datetime.datetime(2014, 1, 1, 1, 1), + ("datetime.datetime(2014, 1, 1, 1, 1)", {'import datetime'}) + ) + self.assertSerializedResultEqual( + datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc), + ( + "datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc)", + {'import datetime', 'from django.utils.timezone import utc'}, + ) + ) + + def test_serialize_datetime_safe(self): + self.assertSerializedResultEqual( + datetime_safe.date(2014, 3, 31), + ("datetime.date(2014, 3, 31)", {'import datetime'}) + ) + self.assertSerializedResultEqual( + datetime_safe.time(10, 25), + ("datetime.time(10, 25)", {'import datetime'}) + ) + self.assertSerializedResultEqual( + datetime_safe.datetime(2014, 3, 31, 16, 4, 31), + ("datetime.datetime(2014, 3, 31, 16, 4, 31)", {'import datetime'}) + ) + + def test_serialize_fields(self): self.assertSerializedFieldEqual(models.CharField(max_length=255)) self.assertSerializedFieldEqual(models.TextField(null=True, blank=True)) - # Setting references + + def test_serialize_settings(self): self.assertSerializedEqual(SettingsReference(settings.AUTH_USER_MODEL, "AUTH_USER_MODEL")) self.assertSerializedResultEqual( SettingsReference("someapp.model", "AUTH_USER_MODEL"), - ( - "settings.AUTH_USER_MODEL", - {"from django.conf import settings"}, - ) + ("settings.AUTH_USER_MODEL", {"from django.conf import settings"}) ) self.assertSerializedResultEqual( ((x, x * x) for x in range(3)), - ( - "((0, 0), (1, 1), (2, 4))", - set(), - ) + ("((0, 0), (1, 1), (2, 4))", set()) ) def test_serialize_compiled_regex(self): @@ -316,6 +327,15 @@ def upload_to(self): '^Could not find function upload_to in migrations.test_writer'): self.serialize_round_trip(TestModel2.thing) + def test_serialize_managers(self): + self.assertSerializedEqual(models.Manager()) + self.assertSerializedResultEqual( + FoodQuerySet.as_manager(), + ('migrations.models.FoodQuerySet.as_manager()', {'import migrations.models'}) + ) + self.assertSerializedEqual(FoodManager('a', 'b')) + self.assertSerializedEqual(FoodManager('x', 'y', c=3, d=4)) + def test_simple_migration(self): """ Tests serializing a simple migration. @@ -399,25 +419,6 @@ def test_custom_operation(self): result['custom_migration_operations'].more_operations.TestOperation ) - def test_serialize_datetime(self): - """ - #23365 -- Timezone-aware datetimes should be allowed. - """ - # naive datetime - naive_datetime = datetime.datetime(2014, 1, 1, 1, 1) - self.assertEqual(MigrationWriter.serialize_datetime(naive_datetime), - "datetime.datetime(2014, 1, 1, 1, 1)") - - # datetime with utc timezone - utc_datetime = datetime.datetime(2014, 1, 1, 1, 1, tzinfo=utc) - self.assertEqual(MigrationWriter.serialize_datetime(utc_datetime), - "datetime.datetime(2014, 1, 1, 1, 1, tzinfo=utc)") - - # datetime with FixedOffset tzinfo - fixed_offset_datetime = datetime.datetime(2014, 1, 1, 1, 1, tzinfo=FixedOffset(180)) - self.assertEqual(MigrationWriter.serialize_datetime(fixed_offset_datetime), - "datetime.datetime(2013, 12, 31, 22, 1, tzinfo=utc)") - def test_deconstruct_class_arguments(self): # Yes, it doesn't make sense to use a class as a default for a # CharField. It does make sense for custom fields though, for example @@ -428,12 +429,3 @@ def deconstruct(self): string = MigrationWriter.serialize(models.CharField(default=DeconstructableInstances))[0] self.assertEqual(string, "models.CharField(default=migrations.test_writer.DeconstructableInstances)") - - def test_serialize_managers(self): - self.assertSerializedEqual(models.Manager()) - self.assertSerializedResultEqual( - FoodQuerySet.as_manager(), - ('migrations.models.FoodQuerySet.as_manager()', {'import migrations.models'}) - ) - self.assertSerializedEqual(FoodManager('a', 'b')) - self.assertSerializedEqual(FoodManager('x', 'y', c=3, d=4)) From 7cfcdd98dcf0dbbde7cfd656e450c52342dbe6f3 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 17 Jan 2015 18:11:47 -0500 Subject: [PATCH 0013/1125] [1.8.x] Added versionadded to ModelAdmin.get_formsets_with_inlines(); refs #20702. --- docs/ref/contrib/admin/index.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index bb72f3fe557d..a39affd7479f 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1531,6 +1531,8 @@ templates used by the :class:`ModelAdmin` views: .. method:: ModelAdmin.get_formsets_with_inlines(request, obj=None) + .. versionadded:: 1.7 + Yields (``FormSet``, :class:`InlineModelAdmin`) pairs for use in admin add and change views. From a41d41046af60e585713b33ff2e6e039446ce389 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 17 Jan 2015 19:14:44 -0500 Subject: [PATCH 0014/1125] [1.8.x] Added removal of check management command to deprecation timeline. Backport of 20e4e8fc79af9a87fb48951d03d3747f1f550551 from master --- docs/internals/deprecation.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 2876bb883b70..45180a107e35 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -269,6 +269,8 @@ details on these changes. * ``django.db.backends.DatabaseValidation.validate_field`` will be removed in favor of the ``check_field`` method. +* The ``check`` management command will be removed. + * ``django.utils.module_loading.import_by_path`` will be removed in favor of ``django.utils.module_loading.import_string``. From 8822d0bf7d9aa8c494e98cdbb4a0586e7e950a44 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 17 Jan 2015 20:14:45 -0500 Subject: [PATCH 0015/1125] [1.8.x] Removed usage of deprecated dumpdata options in docs. Backport of 1d975ff44bc23efaf0ebf3e96cc35539d80bd244 from master --- docs/ref/contrib/contenttypes.txt | 3 ++- docs/ref/django-admin.txt | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index ef857984dfe2..b9610c3abcfa 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -334,7 +334,8 @@ model: generic relations, you should probably be using a natural key to uniquely identify related :class:`~django.contrib.contenttypes.models.ContentType` objects. See :ref:`natural keys` and - :djadminopt:`dumpdata --natural <--natural>` for more information. + :djadminopt:`dumpdata --natural-foreign <--natural-foreign>` for more + information. This will enable an API similar to the one used for a normal :class:`~django.db.models.ForeignKey`; diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 570766f18d47..c6031b7d63ea 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1925,14 +1925,14 @@ with ``True`` or ``False``, as you can see with the ``interactive`` option above Named arguments can be passed by using either one of the following syntaxes:: # Similar to the command line - management.call_command('dumpdata', '--natural') + management.call_command('dumpdata', '--natural-foreign') # Named argument similar to the command line minus the initial dashes and # with internal dashes replaced by underscores - management.call_command('dumpdata', natural=True) + management.call_command('dumpdata', natural_foreign=True) - # `use_natural_keys` is the option destination variable - management.call_command('dumpdata', use_natural_keys=True) + # `use_natural_foreign_keys` is the option destination variable + management.call_command('dumpdata', use_natural_foreign_keys=True) .. versionchanged:: 1.8 From 737cd4ff3decfd3e709885d219e70d8395305965 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 18 Jan 2015 13:40:21 -0500 Subject: [PATCH 0016/1125] [1.8.x] Clarified contrib.contenttypes.generic deprecation; refs #19774. --- docs/ref/contrib/contenttypes.txt | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index b9610c3abcfa..474d0a6ff16b 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -299,10 +299,11 @@ model: is ``True``. This mirrors the ``for_concrete_model`` argument to :meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_model`. - .. versionchanged:: 1.7 + .. deprecated:: 1.7 This class used to be defined in ``django.contrib.contenttypes.generic``. - + Support for importing from this old location will be removed in Django + 1.9. .. admonition:: Primary key type compatibility @@ -369,9 +370,11 @@ Reverse generic relations .. class:: GenericRelation - .. versionchanged:: 1.7 + .. deprecated:: 1.7 This class used to be defined in ``django.contrib.contenttypes.generic``. + Support for importing from this old location will be removed in Django + 1.9. .. attribute:: related_query_name @@ -496,9 +499,11 @@ The :mod:`django.contrib.contenttypes.forms` module provides: .. class:: BaseGenericInlineFormSet - .. versionchanged:: 1.7 + .. deprecated:: 1.7 This class used to be defined in ``django.contrib.contenttypes.generic``. + Support for importing from this old location will be removed in Django + 1.9. .. function:: generic_inlineformset_factory(model, form=ModelForm, formset=BaseGenericInlineFormSet, ct_field="content_type", fk_field="object_id", fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, validate_max=False, for_concrete_model=True, min_num=None, validate_min=False) @@ -515,9 +520,11 @@ The :mod:`django.contrib.contenttypes.forms` module provides: :class:`~django.contrib.contenttypes.fields.GenericForeignKey.for_concrete_model` argument on ``GenericForeignKey``. - .. versionchanged:: 1.7 + .. deprecated:: 1.7 This function used to be defined in ``django.contrib.contenttypes.generic``. + Support for importing from this old location will be removed in Django + 1.9. .. versionchanged:: 1.7 @@ -557,9 +564,11 @@ information. The name of the integer field that represents the ID of the related object. Defaults to ``object_id``. - .. versionchanged:: 1.7 + .. deprecated:: 1.7 This class used to be defined in ``django.contrib.contenttypes.generic``. + Support for importing from this old location will be removed in Django + 1.9. .. class:: GenericTabularInline .. class:: GenericStackedInline @@ -567,6 +576,8 @@ information. Subclasses of :class:`GenericInlineModelAdmin` with stacked and tabular layouts, respectively. - .. versionchanged:: 1.7 + .. deprecated:: 1.7 These classes used to be defined in ``django.contrib.contenttypes.generic``. + Support for importing from this old location will be removed in Django + 1.9. From 390559a75cf634becf05ae265169f519ea176c3e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 18 Jan 2015 13:29:33 -0500 Subject: [PATCH 0017/1125] [1.8.x] Clarified a contrib.sites deprecation and added to 1.7 release notes. Backport of ba27f895878bb155fefb8c1b9beee2c9f3d85b3f from master --- docs/internals/deprecation.txt | 4 ++-- docs/ref/contrib/sites.txt | 8 +++++--- docs/releases/1.7.txt | 11 +++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 45180a107e35..c587d3b98dd0 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -206,8 +206,8 @@ details on these changes. * ``AppCommand.handle_app()`` will no longer be supported. -* ``RequestSite`` will be located in ``django.contrib.sites.requests`` and - ``get_current_site`` in ``django.contrib.sites.shortcuts``. +* ``RequestSite`` and ``get_current_site()`` will no longer be importable from + ``django.contrib.sites.models``. * FastCGI support via the ``runfcgi`` management command will be removed. Please deploy your project using WSGI. diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index 91fbd2af85b3..b3dd2933a73d 100644 --- a/docs/ref/contrib/sites.txt +++ b/docs/ref/contrib/sites.txt @@ -472,9 +472,10 @@ a fallback when the database-backed sites framework is not available. Sets the ``name`` and ``domain`` attributes to the value of :meth:`~django.http.HttpRequest.get_host`. - .. versionchanged:: 1.7 + .. deprecated:: 1.7 - This class used to be defined in ``django.contrib.sites.models``. + This class used to be defined in ``django.contrib.sites.models``. The + old import location will work until Django 1.9. A :class:`~django.contrib.sites.requests.RequestSite` object has a similar interface to a normal :class:`~django.contrib.sites.models.Site` object, @@ -498,9 +499,10 @@ Finally, to avoid repetitive fallback code, the framework provides a object or a :class:`~django.contrib.sites.requests.RequestSite` object based on the request. - .. versionchanged:: 1.7 + .. deprecated:: 1.7 This function used to be defined in ``django.contrib.sites.models``. + The old import location will work until Django 1.9. .. versionchanged:: 1.8 diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index b5888bc52a4f..134d45c1de19 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -1757,6 +1757,17 @@ FastCGI support FastCGI support via the ``runfcgi`` management command will be removed in Django 1.9. Please deploy your project using WSGI. +Moved objects in ``contrib.sites`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Following the app-loading refactor, two objects in +``django.contrib.sites.models`` needed to be moved because they must be +available without importing ``django.contrib.sites.models`` when +``django.contrib.sites`` isn't installed. Import ``RequestSite`` from +``django.contrib.sites.requests`` and ``get_current_site()`` from +``django.contrib.sites.shortcuts``. The old import locations will work until +Django 1.9. + .. removed-features-1.7: Features removed in 1.7 From 4af4ccfb304b1c724f840df6ea3f367fcd5ed2d3 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 18 Jan 2015 14:17:31 -0500 Subject: [PATCH 0018/1125] [1.8.x] Clarified deprecation of test.utils.TestTemplateLoader. Backport of 7468c948b6e7830082c939e0f43e2bd780c90527 from master --- docs/internals/deprecation.txt | 2 ++ docs/releases/1.8.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index c587d3b98dd0..a1fd657b4a58 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -292,6 +292,8 @@ details on these changes. * ``django.contrib.sitemaps.FlatPageSitemap`` will be removed in favor of ``django.contrib.flatpages.sitemaps.FlatPageSitemap``. +* Private API ``django.test.utils.TestTemplateLoader`` will be removed. + .. _deprecation-removed-in-1.8: 1.8 diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 437f72edb674..bea98f6524c0 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -1472,7 +1472,7 @@ loader that inherits ``BaseLoader``, you must inherit ``Loader`` instead. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Private API ``django.test.utils.TestTemplateLoader`` is deprecated in favor of -``django.template.loaders.locmem.Loader``. +``django.template.loaders.locmem.Loader`` and will be removed in Django 1.9. .. _storage-max-length-update: From 8c8a1a0846cc96f8daae9c4c87307ba4b912fac2 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 18 Jan 2015 20:58:16 +0100 Subject: [PATCH 0019/1125] [1.8.x] Added contrib.postgres translation catalog --- .tx/config | 5 + django/conf/locale/en/LC_MESSAGES/django.po | 82 +-------------- .../postgres/locale/en/LC_MESSAGES/django.mo | Bin 0 -> 356 bytes .../postgres/locale/en/LC_MESSAGES/django.po | 98 ++++++++++++++++++ 4 files changed, 104 insertions(+), 81 deletions(-) create mode 100644 django/contrib/postgres/locale/en/LC_MESSAGES/django.mo create mode 100644 django/contrib/postgres/locale/en/LC_MESSAGES/django.po diff --git a/.tx/config b/.tx/config index eda0092a4f37..44f8a7ebe64e 100644 --- a/.tx/config +++ b/.tx/config @@ -52,6 +52,11 @@ file_filter = django/contrib/messages/locale//LC_MESSAGES/django.po source_file = django/contrib/messages/locale/en/LC_MESSAGES/django.po source_lang = en +[django.contrib-postgres] +file_filter = django/contrib/postgres/locale//LC_MESSAGES/django.po +source_file = django/contrib/postgres/locale/en/LC_MESSAGES/django.po +source_lang = en + [django.contrib-redirects] file_filter = django/contrib/redirects/locale//LC_MESSAGES/django.po source_file = django/contrib/redirects/locale/en/LC_MESSAGES/django.po diff --git a/django/conf/locale/en/LC_MESSAGES/django.po b/django/conf/locale/en/LC_MESSAGES/django.po index 267f765bdab4..29219941db10 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-01-17 11:07+0100\n" +"POT-Creation-Date: 2015-01-18 20:56+0100\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n" "Last-Translator: Django team\n" "Language-Team: English \n" @@ -349,86 +349,6 @@ msgstr "" msgid "Traditional Chinese" msgstr "" -#: contrib/postgres/apps.py:12 -msgid "PostgreSQL extensions" -msgstr "" - -#: contrib/postgres/fields/array.py:23 contrib/postgres/forms/array.py:13 -#: contrib/postgres/forms/array.py:143 -#, python-format -msgid "Item %(nth)s in the array did not validate: " -msgstr "" - -#: contrib/postgres/fields/array.py:24 -msgid "Nested arrays must have the same length." -msgstr "" - -#: contrib/postgres/fields/hstore.py:16 -msgid "Map of strings to strings" -msgstr "" - -#: contrib/postgres/fields/hstore.py:18 -#, python-format -msgid "The value of \"%(key)s\" is not a string." -msgstr "" - -#: contrib/postgres/forms/hstore.py:16 -msgid "Could not load JSON data." -msgstr "" - -#: contrib/postgres/forms/ranges.py:13 -msgid "Enter two valid values." -msgstr "" - -#: contrib/postgres/forms/ranges.py:14 -msgid "The start of the range must not exceed the end of the range." -msgstr "" - -#: contrib/postgres/validators.py:14 -#, python-format -msgid "" -"List contains %(show_value)d item, it should contain no more than " -"%(limit_value)d." -msgid_plural "" -"List contains %(show_value)d items, it should contain no more than " -"%(limit_value)d." -msgstr[0] "" -msgstr[1] "" - -#: contrib/postgres/validators.py:21 -#, python-format -msgid "" -"List contains %(show_value)d item, it should contain no fewer than " -"%(limit_value)d." -msgid_plural "" -"List contains %(show_value)d items, it should contain no fewer than " -"%(limit_value)d." -msgstr[0] "" -msgstr[1] "" - -#: contrib/postgres/validators.py:31 -#, python-format -msgid "Some keys were missing: %(keys)s" -msgstr "" - -#: contrib/postgres/validators.py:32 -#, python-format -msgid "Some unknown keys were provided: %(keys)s" -msgstr "" - -#: contrib/postgres/validators.py:73 -#, python-format -msgid "" -"Ensure that this range is completely less than or equal to %(limit_value)s." -msgstr "" - -#: contrib/postgres/validators.py:78 -#, python-format -msgid "" -"Ensure that this range is completely greater than or equal to " -"%(limit_value)s." -msgstr "" - #: contrib/sitemaps/apps.py:8 msgid "Site Maps" msgstr "" diff --git a/django/contrib/postgres/locale/en/LC_MESSAGES/django.mo b/django/contrib/postgres/locale/en/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..08a7b68596a8a494a33644935e4ca6d40be6447f GIT binary patch literal 356 zcmYL^T}#6-6ow~yl}oR^$c0zxnQ2R<#t+1;!eGTQs`s%yW*IdZNha=x`1kxRo+7R< zJcL6Ya^CzLAAOG~2gC_+N*odwM4y5ZWM(uUoJsz&^Zmzz!*d&8TR@&V)BOZ^1J;Pp-3(L-q)*BYxOFWyXKqhBsF BVW0p2 literal 0 HcmV?d00001 diff --git a/django/contrib/postgres/locale/en/LC_MESSAGES/django.po b/django/contrib/postgres/locale/en/LC_MESSAGES/django.po new file mode 100644 index 000000000000..ae05e4225e63 --- /dev/null +++ b/django/contrib/postgres/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,98 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-01-18 20:56+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/postgres/apps.py:12 +msgid "PostgreSQL extensions" +msgstr "" + +#: contrib/postgres/fields/array.py:23 contrib/postgres/forms/array.py:13 +#: contrib/postgres/forms/array.py:143 +#, python-format +msgid "Item %(nth)s in the array did not validate: " +msgstr "" + +#: contrib/postgres/fields/array.py:24 +msgid "Nested arrays must have the same length." +msgstr "" + +#: contrib/postgres/fields/hstore.py:16 +msgid "Map of strings to strings" +msgstr "" + +#: contrib/postgres/fields/hstore.py:18 +#, python-format +msgid "The value of \"%(key)s\" is not a string." +msgstr "" + +#: contrib/postgres/forms/hstore.py:16 +msgid "Could not load JSON data." +msgstr "" + +#: contrib/postgres/forms/ranges.py:13 +msgid "Enter two valid values." +msgstr "" + +#: contrib/postgres/forms/ranges.py:14 +msgid "The start of the range must not exceed the end of the range." +msgstr "" + +#: contrib/postgres/validators.py:14 +#, python-format +msgid "" +"List contains %(show_value)d item, it should contain no more than " +"%(limit_value)d." +msgid_plural "" +"List contains %(show_value)d items, it should contain no more than " +"%(limit_value)d." +msgstr[0] "" +msgstr[1] "" + +#: contrib/postgres/validators.py:21 +#, python-format +msgid "" +"List contains %(show_value)d item, it should contain no fewer than " +"%(limit_value)d." +msgid_plural "" +"List contains %(show_value)d items, it should contain no fewer than " +"%(limit_value)d." +msgstr[0] "" +msgstr[1] "" + +#: contrib/postgres/validators.py:31 +#, python-format +msgid "Some keys were missing: %(keys)s" +msgstr "" + +#: contrib/postgres/validators.py:32 +#, python-format +msgid "Some unknown keys were provided: %(keys)s" +msgstr "" + +#: contrib/postgres/validators.py:73 +#, python-format +msgid "" +"Ensure that this range is completely less than or equal to %(limit_value)s." +msgstr "" + +#: contrib/postgres/validators.py:78 +#, python-format +msgid "" +"Ensure that this range is completely greater than or equal to " +"%(limit_value)s." +msgstr "" From f31ed5c934e7e664b912c13bd854cc9445f34ec5 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 18 Jan 2015 16:06:56 -0500 Subject: [PATCH 0020/1125] [1.8.x] Clarified deprecation of forms.forms.get_declared_fields(); refs #19617. Backport of 89e9f81601f7a343690e1153e70fd56091246d0b from master --- docs/internals/deprecation.txt | 2 +- docs/releases/1.7.txt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index a1fd657b4a58..8ec6f16666c9 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -238,7 +238,7 @@ details on these changes. * The ``use_natural_keys`` argument for ``serializers.serialize()`` will be removed. Use ``use_natural_foreign_keys`` instead. -* ``django.forms.get_declared_fields`` will be removed. +* Private API ``django.forms.forms.get_declared_fields()`` will be removed. * The ability to use a ``SplitDateTimeWidget`` with ``DateTimeField`` will be removed. diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 134d45c1de19..4b89aebc3c77 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -1768,6 +1768,12 @@ available without importing ``django.contrib.sites.models`` when ``django.contrib.sites.shortcuts``. The old import locations will work until Django 1.9. +``django.forms.forms.get_declared_fields()`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Django no longer uses this functional internally. Even though it's a private +API, it'll go through the normal deprecation cycle. + .. removed-features-1.7: Features removed in 1.7 From 559e15a23a7e1f2437de001e7eda2e2fa0723fb3 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 18 Jan 2015 16:30:45 -0500 Subject: [PATCH 0021/1125] [1.8.x] Removed an obsolete comment in django/apps/config.py Backport of bd98926f0eb19d27821a8a7679b42ff46e53e4da from master --- django/apps/config.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/django/apps/config.py b/django/apps/config.py index 843013f30ed7..fd0e2fe78c47 100644 --- a/django/apps/config.py +++ b/django/apps/config.py @@ -110,9 +110,6 @@ def create(cls, entry): # If we're reaching this point, we must attempt to load the app config # class located at . - - # Avoid django.utils.module_loading.import_by_path because it - # masks errors -- it reraises ImportError as ImproperlyConfigured. mod = import_module(mod_path) try: cls = getattr(mod, cls_name) From 663db8ea2adfd47dda50726404401ff2d290db52 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 18 Jan 2015 18:32:47 -0500 Subject: [PATCH 0022/1125] [1.8.x] Removed usage of deprecated removetags in a template test. Backport of b84100e8e22b175b62ce849acbcf1fa9a1e0e5b8 from master --- .../template_tests/syntax_tests/test_filter_syntax.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/template_tests/syntax_tests/test_filter_syntax.py b/tests/template_tests/syntax_tests/test_filter_syntax.py index 58ee52775840..94e7fe287179 100644 --- a/tests/template_tests/syntax_tests/test_filter_syntax.py +++ b/tests/template_tests/syntax_tests/test_filter_syntax.py @@ -2,8 +2,7 @@ from __future__ import unicode_literals from django.template import TemplateSyntaxError -from django.test import SimpleTestCase, ignore_warnings -from django.utils.deprecation import RemovedInDjango20Warning +from django.test import SimpleTestCase from ..utils import setup, SomeClass, SomeOtherException, UTF8Class @@ -75,14 +74,13 @@ def test_filter_syntax08(self): with self.assertRaises(TemplateSyntaxError): self.engine.get_template('filter-syntax08') - @ignore_warnings(category=RemovedInDjango20Warning) - @setup({'filter-syntax09': '{{ var|removetags:"b i"|upper|lower }}'}) + @setup({'filter-syntax09': '{{ var|cut:"o"|upper|lower }}'}) def test_filter_syntax09(self): """ Chained filters, with an argument to the first one """ - output = self.engine.render_to_string('filter-syntax09', {'var': 'Yes'}) - self.assertEqual(output, 'yes') + output = self.engine.render_to_string('filter-syntax09', {'var': 'Foo'}) + self.assertEqual(output, 'f') @setup({'filter-syntax10': r'{{ var|default_if_none:" endquote\" hah" }}'}) def test_filter_syntax10(self): From 511ef69194e0bbbd3a917e9f544f250a50619ffb Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 18 Jan 2015 20:02:15 -0500 Subject: [PATCH 0023/1125] [1.8.x] Removed a flake8 exclude that caused code to be missed. Backport of 2fa2068406cbef08bac3e2da9b69e86c6b931f57 from master --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e362e06cb08f..58c9b2914c6a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ doc_files = docs extras AUTHORS INSTALL LICENSE README.rst install-script = scripts/rpm-install.sh [flake8] -exclude=build,.git,./django/utils/dictconfig.py,./django/utils/unittest.py,./django/utils/lru_cache.py,./django/utils/six.py,./django/conf/app_template/*,./django/dispatch/weakref_backports.py,./tests/.env,./xmlrunner,./tests/test_*.py +exclude=build,.git,./django/utils/dictconfig.py,./django/utils/unittest.py,./django/utils/lru_cache.py,./django/utils/six.py,./django/conf/app_template/*,./django/dispatch/weakref_backports.py,./tests/.env,./xmlrunner ignore=E123,E128,E265,E501,W601 max-line-length = 119 From 0c910823c166b827b090df5c5c8be9e502a14d8c Mon Sep 17 00:00:00 2001 From: Josh Smeaton Date: Mon, 19 Jan 2015 10:35:10 +1100 Subject: [PATCH 0024/1125] [1.8.x] Fixed #24174 -- Fixed extra order by descending Backport of 69c6a6868f0b4137bb293ff4326ecf4681506c37 from master --- django/db/models/sql/compiler.py | 6 ++++-- tests/ordering/tests.py | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index ebda3be96fd9..7944e35d8abf 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -261,8 +261,10 @@ def get_order_by(self): # on verbatim. table, col = col.split('.', 1) order_by.append(( - OrderBy(RawSQL('%s.%s' % (self.quote_name_unless_alias(table), col), [])), - False)) + OrderBy( + RawSQL('%s.%s' % (self.quote_name_unless_alias(table), col), []), + descending=descending + ), False)) continue if not self.query._extra or col not in self.query._extra: diff --git a/tests/ordering/tests.py b/tests/ordering/tests.py index abb30f43f162..60880b71bedf 100644 --- a/tests/ordering/tests.py +++ b/tests/ordering/tests.py @@ -166,6 +166,26 @@ def test_extra_ordering_quoting(self): attrgetter("headline") ) + def test_extra_ordering_with_table_name(self): + self.assertQuerysetEqual( + Article.objects.extra(order_by=['ordering_article.headline']), [ + "Article 1", + "Article 2", + "Article 3", + "Article 4", + ], + attrgetter("headline") + ) + self.assertQuerysetEqual( + Article.objects.extra(order_by=['-ordering_article.headline']), [ + "Article 4", + "Article 3", + "Article 2", + "Article 1", + ], + attrgetter("headline") + ) + def test_order_by_pk(self): """ Ensure that 'pk' works as an ordering option in Meta. From dd0707c3ca74fd4a019e0dd143cb8fec799f5186 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 18 Jan 2015 21:23:06 -0500 Subject: [PATCH 0025/1125] [1.8.x] Added missing items to deprecation timeline/1.7 release notes. Backport of ecf109f2159f4581adb354263406116c2bda11d7 from master --- docs/internals/deprecation.txt | 12 ++++++++---- docs/releases/1.7.txt | 12 +++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 8ec6f16666c9..d5856a2c68cc 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -228,9 +228,9 @@ details on these changes. * ``ModelAdmin.get_formsets`` will be removed. -* Remove the backward compatible shims introduced to rename the +* The backward compatibility shim introduced to rename the ``BaseMemcachedCache._get_memcache_timeout()`` method to - ``get_backend_timeout()``. + ``get_backend_timeout()`` will be removed. * The ``--natural`` and ``-n`` options for :djadmin:`dumpdata` will be removed. Use :djadminopt:`--natural-foreign` instead. @@ -263,8 +263,10 @@ details on these changes. ``requires_system_checks``. Admin validators will be replaced by admin checks. -* ``ModelAdmin.validator`` will be removed in favor of the new ``checks`` - attribute. +* The ``ModelAdmin.validator_class`` and ``default_validator_class`` attributes + will be removed. + +* ``ModelAdmin.validate()`` will be removed. * ``django.db.backends.DatabaseValidation.validate_field`` will be removed in favor of the ``check_field`` method. @@ -294,6 +296,8 @@ details on these changes. * Private API ``django.test.utils.TestTemplateLoader`` will be removed. +* The ``django.contrib.contenttypes.generic`` module will be removed. + .. _deprecation-removed-in-1.8: 1.8 diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 4b89aebc3c77..7277af52d2a4 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -1694,10 +1694,16 @@ value of the former flag is used. Defining both ``requires_system_checks`` and The ``check()`` method has replaced the old ``validate()`` method. -``ModelAdmin.validator`` -~~~~~~~~~~~~~~~~~~~~~~~~ +``ModelAdmin`` validators +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``ModelAdmin.validator_class`` and ``default_validator_class`` attributes +are deprecated in favor of the new ``checks_class`` attribute. + +The ``ModelAdmin.validate()`` method is deprecated in favor of +``ModelAdmin.check()``. -``ModelAdmin.validator`` is deprecated in favor of new ``checks`` attribute. +The ``django.contrib.admin.validation`` module is deprecated. ``django.db.backends.DatabaseValidation.validate_field`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 6eddaa42c3843e23a16442096c43664e21c92af7 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 19 Jan 2015 10:18:29 +0100 Subject: [PATCH 0026/1125] [1.8.x] Fixed header of contrib.postgres translation catalog Backport of eb6e12ca6f from master. --- .../postgres/locale/en/LC_MESSAGES/django.po | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/django/contrib/postgres/locale/en/LC_MESSAGES/django.po b/django/contrib/postgres/locale/en/LC_MESSAGES/django.po index ae05e4225e63..b53d02a49305 100644 --- a/django/contrib/postgres/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/postgres/locale/en/LC_MESSAGES/django.po @@ -1,21 +1,18 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. +# This file is distributed under the same license as the Django package. # -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-18 20:56+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" +"PO-Revision-Date: 2015-01-18 20:56+0100\n" +"Last-Translator: Django team\n" +"Language-Team: English \n" +"Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" #: contrib/postgres/apps.py:12 msgid "PostgreSQL extensions" From 0b3e3e21cfe7463245dc1dfc9258e18903bc1116 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 18 Jan 2015 14:30:07 -0500 Subject: [PATCH 0027/1125] [1.8.x] Added deprecation docs for legacy lookup support; refs #16187. Backport of 8e435a564034c59ac408ec71283d8ac6ede2ce1f from master --- docs/internals/deprecation.txt | 3 +++ docs/releases/1.7.txt | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index d5856a2c68cc..49a3982239a9 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -298,6 +298,9 @@ details on these changes. * The ``django.contrib.contenttypes.generic`` module will be removed. +* Private APIs ``django.db.models.sql.where.WhereNode.make_atom()`` and + ``django.db.models.sql.where.Constraint`` will be removed. + .. _deprecation-removed-in-1.8: 1.8 diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 7277af52d2a4..f4a2c5462bfa 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -1780,6 +1780,13 @@ Django 1.9. Django no longer uses this functional internally. Even though it's a private API, it'll go through the normal deprecation cycle. +Private Query Lookup APIs +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Private APIs ``django.db.models.sql.where.WhereNode.make_atom()`` and +``django.db.models.sql.where.Constraint`` are deprecated in favor of the new +:doc:`custom lookups API `. + .. removed-features-1.7: Features removed in 1.7 From e55cb91bd47d054d502c03cdbe74ac98986af3fb Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Mon, 19 Jan 2015 15:31:23 +0100 Subject: [PATCH 0028/1125] [1.8.x] Fixed #24163 -- Removed unique constraint after index on MySQL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks Łukasz Harasimowicz for the report. Backport of 5792e6a88c1444d4ec84abe62077338ad3765b80 from master --- django/db/backends/base/schema.py | 24 +++---- docs/releases/1.7.4.txt | 3 + tests/schema/models.py | 10 +++ tests/schema/tests.py | 106 +++++++++++++++++++++++++++++- 4 files changed, 128 insertions(+), 15 deletions(-) diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 4a63088d30fd..467d5fb3978a 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -488,18 +488,6 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type, old_db_params, new_db_params, strict=False): """Actually perform a "physical" (non-ManyToMany) field update.""" - # Has unique been removed? - if old_field.unique and (not new_field.unique or (not old_field.primary_key and new_field.primary_key)): - # Find the unique constraint for this field - constraint_names = self._constraint_names(model, [old_field.column], unique=True) - if strict and len(constraint_names) != 1: - raise ValueError("Found wrong number (%s) of unique constraints for %s.%s" % ( - len(constraint_names), - model._meta.db_table, - old_field.column, - )) - for constraint_name in constraint_names: - self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_name)) # Drop any FK constraints, we'll remake them later fks_dropped = set() if old_field.rel and old_field.db_constraint: @@ -513,6 +501,18 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type, for fk_name in fk_names: fks_dropped.add((old_field.column,)) self.execute(self._delete_constraint_sql(self.sql_delete_fk, model, fk_name)) + # Has unique been removed? + if old_field.unique and (not new_field.unique or (not old_field.primary_key and new_field.primary_key)): + # Find the unique constraint for this field + constraint_names = self._constraint_names(model, [old_field.column], unique=True) + if strict and len(constraint_names) != 1: + raise ValueError("Found wrong number (%s) of unique constraints for %s.%s" % ( + len(constraint_names), + model._meta.db_table, + old_field.column, + )) + for constraint_name in constraint_names: + self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_name)) # Drop incoming FK constraints if we're a primary key and things are going # to change. if old_field.primary_key and new_field.primary_key and old_type != new_type: diff --git a/docs/releases/1.7.4.txt b/docs/releases/1.7.4.txt index 8fbac815bf8a..5f10e5e8c60e 100644 --- a/docs/releases/1.7.4.txt +++ b/docs/releases/1.7.4.txt @@ -14,3 +14,6 @@ Bugfixes * Made the migration's ``RenameModel`` operation rename ``ManyToManyField`` tables (:ticket:`24135`). + +* Fixed a migration crash on MySQL when migrating from a ``OneToOneField`` to a + ``ForeignKey`` (:ticket:`24163`). diff --git a/tests/schema/models.py b/tests/schema/models.py index df8a1c5ce4dd..6eba7ccae11b 100644 --- a/tests/schema/models.py +++ b/tests/schema/models.py @@ -67,6 +67,16 @@ class Meta: apps = new_apps +class BookWithO2O(models.Model): + author = models.OneToOneField(Author) + title = models.CharField(max_length=100, db_index=True) + pub_date = models.DateTimeField() + + class Meta: + apps = new_apps + db_table = "schema_book" + + class BookWithM2M(models.Model): author = models.ForeignKey(Author) title = models.CharField(max_length=100, db_index=True) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index b10ad07251b0..c62cf7c773d2 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -5,12 +5,12 @@ from django.db import connection, DatabaseError, IntegrityError, OperationalError from django.db.models.fields import (BinaryField, BooleanField, CharField, IntegerField, PositiveIntegerField, SlugField, TextField) -from django.db.models.fields.related import ManyToManyField, ForeignKey +from django.db.models.fields.related import ForeignKey, ManyToManyField, OneToOneField from django.db.transaction import atomic from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough, - AuthorWithEvenLongerName, BookWeak, Note) + AuthorWithEvenLongerName, BookWeak, Note, BookWithO2O) class SchemaTests(TransactionTestCase): @@ -28,7 +28,7 @@ class SchemaTests(TransactionTestCase): Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName, - BookWeak, + BookWeak, BookWithO2O, ] # Utility functions @@ -528,6 +528,106 @@ def test_alter_fk(self): else: self.fail("No FK constraint for author_id found") + @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support") + def test_alter_o2o_to_fk(self): + """ + #24163 - Tests altering of OneToOne to FK + """ + # Create the table + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.create_model(BookWithO2O) + # Ensure the field is right to begin with + columns = self.column_classes(BookWithO2O) + self.assertEqual(columns['author_id'][0], "IntegerField") + # Make sure the FK and unique constraints are present + constraints = self.get_constraints(BookWithO2O._meta.db_table) + author_is_fk = False + author_is_unique = False + for name, details in constraints.items(): + if details['columns'] == ['author_id']: + if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'): + author_is_fk = True + if details['unique']: + author_is_unique = True + self.assertTrue(author_is_fk, "No FK constraint for author_id found") + self.assertTrue(author_is_unique, "No unique constraint for author_id found") + # Alter the O2O to FK + new_field = ForeignKey(Author) + new_field.set_attributes_from_name("author") + with connection.schema_editor() as editor: + editor.alter_field( + BookWithO2O, + BookWithO2O._meta.get_field("author"), + new_field, + strict=True, + ) + # Ensure the field is right afterwards + columns = self.column_classes(Book) + self.assertEqual(columns['author_id'][0], "IntegerField") + # Make sure the FK constraint is present and unique constraint is absent + constraints = self.get_constraints(Book._meta.db_table) + author_is_fk = False + author_is_unique = True + for name, details in constraints.items(): + if details['columns'] == ['author_id']: + if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'): + author_is_fk = True + if not details['unique']: + author_is_unique = False + self.assertTrue(author_is_fk, "No FK constraint for author_id found") + self.assertFalse(author_is_unique, "Unique constraint for author_id found") + + @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support") + def test_alter_fk_to_o2o(self): + """ + #24163 - Tests altering of FK to OneToOne + """ + # Create the table + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.create_model(Book) + # Ensure the field is right to begin with + columns = self.column_classes(Book) + self.assertEqual(columns['author_id'][0], "IntegerField") + # Make sure the FK constraint is present and unique constraint is absent + constraints = self.get_constraints(Book._meta.db_table) + author_is_fk = False + author_is_unique = True + for name, details in constraints.items(): + if details['columns'] == ['author_id']: + if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'): + author_is_fk = True + if not details['unique']: + author_is_unique = False + self.assertTrue(author_is_fk, "No FK constraint for author_id found") + self.assertFalse(author_is_unique, "Unique constraint for author_id found") + # Alter the O2O to FK + new_field = OneToOneField(Author) + new_field.set_attributes_from_name("author") + with connection.schema_editor() as editor: + editor.alter_field( + Book, + Book._meta.get_field("author"), + new_field, + strict=True, + ) + # Ensure the field is right afterwards + columns = self.column_classes(BookWithO2O) + self.assertEqual(columns['author_id'][0], "IntegerField") + # Make sure the FK and unique constraints are present + constraints = self.get_constraints(BookWithO2O._meta.db_table) + author_is_fk = False + author_is_unique = False + for name, details in constraints.items(): + if details['columns'] == ['author_id']: + if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'): + author_is_fk = True + if details['unique']: + author_is_unique = True + self.assertTrue(author_is_fk, "No FK constraint for author_id found") + self.assertTrue(author_is_unique, "No unique constraint for author_id found") + def test_alter_implicit_id_to_explicit(self): """ Should be able to convert an implicit "id" field to an explicit "id" From 06fa019c1bac6af934c0c5b7f93e5d837d96aefa Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 19 Jan 2015 15:12:57 -0500 Subject: [PATCH 0029/1125] [1.8.x] Fixed #24153 -- Fixed cookie test compatibility with Python 3.4.3+ Backport of b19b81b3960ec2090d40be65547502a3386a769b from master --- django/contrib/sessions/tests.py | 5 +++-- tests/requests/tests.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 097cded2f0c4..24d7ac7367c9 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -24,6 +24,7 @@ from django.utils import six from django.utils import timezone from django.utils.encoding import force_text +from django.utils.six.moves import http_cookies from django.contrib.sessions.exceptions import InvalidSessionKey @@ -543,7 +544,7 @@ def test_httponly_session_cookie(self): response = middleware.process_response(request, response) self.assertTrue( response.cookies[settings.SESSION_COOKIE_NAME]['httponly']) - self.assertIn('httponly', + self.assertIn(http_cookies.Morsel._reserved['httponly'], str(response.cookies[settings.SESSION_COOKIE_NAME])) @override_settings(SESSION_COOKIE_HTTPONLY=False) @@ -560,7 +561,7 @@ def test_no_httponly_session_cookie(self): response = middleware.process_response(request, response) self.assertFalse(response.cookies[settings.SESSION_COOKIE_NAME]['httponly']) - self.assertNotIn('httponly', + self.assertNotIn(http_cookies.Morsel._reserved['httponly'], str(response.cookies[settings.SESSION_COOKIE_NAME])) def test_session_save_on_500(self): diff --git a/tests/requests/tests.py b/tests/requests/tests.py index bb36af37477f..306e5e76bbbf 100644 --- a/tests/requests/tests.py +++ b/tests/requests/tests.py @@ -16,6 +16,7 @@ from django.utils import six from django.utils.encoding import force_str from django.utils.http import cookie_date, urlencode +from django.utils.six.moves import http_cookies from django.utils.six.moves.urllib.parse import urlencode as original_urlencode from django.utils.timezone import utc @@ -221,7 +222,7 @@ def test_httponly_cookie(self): example_cookie = response.cookies['example'] # A compat cookie may be in use -- check that it has worked # both as an output string, and using the cookie attributes - self.assertIn('; httponly', str(example_cookie)) + self.assertIn('; %s' % http_cookies.Morsel._reserved['httponly'], str(example_cookie)) self.assertTrue(example_cookie['httponly']) def test_unicode_cookie(self): From e69eea73d94c058728143416058a6257c765a9b8 Mon Sep 17 00:00:00 2001 From: "Fabio C. Barrionuevo da Luz" Date: Sun, 18 Jan 2015 18:53:18 -0300 Subject: [PATCH 0030/1125] [1.8.x] Fixed #24177 -- Added documentation about database view support in inspectdb Backport of bd691f4586c8ad45bd059ff9d3621cbf8afdcdce from master --- docs/ref/django-admin.txt | 15 ++++++++++----- docs/releases/1.8.txt | 3 +++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index c6031b7d63ea..48e699b0d90e 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -352,17 +352,17 @@ inspectdb .. django-admin:: inspectdb -Introspects the database tables in the database pointed-to by the +Introspects the database tables and views in the database pointed-to by the :setting:`NAME` setting and outputs a Django model module (a ``models.py`` file) to standard output. Use this if you have a legacy database with which you'd like to use Django. -The script will inspect the database and create a model for each table within -it. +The script will inspect the database and create a model for each table or view +within it. As you might expect, the created models will have an attribute for every field -in the table. Note that ``inspectdb`` has a few special cases in its field-name -output: +in the table or view. Note that ``inspectdb`` has a few special cases in its +field-name output: * If ``inspectdb`` cannot map a column's type to a model field type, it'll use ``TextField`` and will insert the Python comment @@ -405,6 +405,11 @@ it because ``True`` is its default value). The :djadminopt:`--database` option may be used to specify the database to introspect. +.. versionadded:: 1.8 + + A feature to inspect database views was added. In previous versions, only + tables (not views) were inspected. + loaddata ------------------------------ diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index bea98f6524c0..c89c65de5c2a 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -415,6 +415,9 @@ Management Commands introspect :class:`~django.db.models.AutoField` for MySQL and PostgreSQL databases. +* :djadmin:`inspectdb` now introspects database views on all database backends. + In previous versions, only tables (not views) were inspected. + * When calling management commands from code through :ref:`call_command ` and passing options, the option name can match the command line option name (without the initial dashes) or the final option destination From 504cd5d3be450376f462b66e36df6edc923e9496 Mon Sep 17 00:00:00 2001 From: Josh Smeaton Date: Tue, 20 Jan 2015 11:47:13 +1100 Subject: [PATCH 0031/1125] [1.8.x] Fixed #24183 -- Fixed wrong comparisons in Substr Backport of 61c102d010ef480cebe576cc1576d1101975925c from master --- django/db/models/functions.py | 4 ++-- tests/db_functions/tests.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/django/db/models/functions.py b/django/db/models/functions.py index f16cf10b152f..610ecb69859b 100644 --- a/django/db/models/functions.py +++ b/django/db/models/functions.py @@ -110,13 +110,13 @@ def __init__(self, expression, pos, length=None, **extra): pos: an integer > 0, or an expression returning an integer length: an optional number of characters to return """ - if not hasattr('pos', 'resolve_expression'): + if not hasattr(pos, 'resolve_expression'): if pos < 1: raise ValueError("'pos' must be greater than 0") pos = Value(pos) expressions = [expression, pos] if length is not None: - if not hasattr('length', 'resolve_expression'): + if not hasattr(length, 'resolve_expression'): length = Value(length) expressions.append(length) super(Substr, self).__init__(*expressions, **extra) diff --git a/tests/db_functions/tests.py b/tests/db_functions/tests.py index 00a5e258de7a..97d32d86e7f1 100644 --- a/tests/db_functions/tests.py +++ b/tests/db_functions/tests.py @@ -278,6 +278,18 @@ def test_substr_start(self): with six.assertRaisesRegex(self, ValueError, "'pos' must be greater than 0"): Author.objects.annotate(raises=Substr('name', 0)) + def test_substr_with_expressions(self): + Author.objects.create(name='John Smith', alias='smithj') + Author.objects.create(name='Rhonda') + authors = Author.objects.annotate(name_part=Substr('name', V(5), V(3))) + self.assertQuerysetEqual( + authors.order_by('name'), [ + ' Sm', + 'da', + ], + lambda a: a.name_part + ) + def test_nested_function_ordering(self): Author.objects.create(name='John Smith') Author.objects.create(name='Rhonda Simpson', alias='ronny') From c80b2144d22d29725cf4d584bfa9079dd35af064 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Tue, 20 Jan 2015 09:52:23 +0000 Subject: [PATCH 0032/1125] [1.8.x] Fixes #24169 -- More arrayfield specific lookups. varchar()[] cannot compare itself to text[] Thanks to joelburton for the patch. Backport of 0ae94d0d31 from master --- django/contrib/postgres/fields/array.py | 16 ++++++++++++++-- tests/postgres_tests/test_array.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/django/contrib/postgres/fields/array.py b/django/contrib/postgres/fields/array.py index b0850b92e7f2..af575c6b30d6 100644 --- a/django/contrib/postgres/fields/array.py +++ b/django/contrib/postgres/fields/array.py @@ -158,8 +158,20 @@ def as_sql(self, qn, connection): return sql, params -ArrayField.register_lookup(lookups.ContainedBy) -ArrayField.register_lookup(lookups.Overlap) +@ArrayField.register_lookup +class ArrayContainedBy(lookups.ContainedBy): + def as_sql(self, qn, connection): + sql, params = super(ArrayContainedBy, self).as_sql(qn, connection) + sql += '::%s' % self.lhs.output_field.db_type(connection) + return sql, params + + +@ArrayField.register_lookup +class ArrayOverlap(lookups.Overlap): + def as_sql(self, qn, connection): + sql, params = super(ArrayOverlap, self).as_sql(qn, connection) + sql += '::%s' % self.lhs.output_field.db_type(connection) + return sql, params @ArrayField.register_lookup diff --git a/tests/postgres_tests/test_array.py b/tests/postgres_tests/test_array.py index 5c300f7ea3b6..020b31658f1c 100644 --- a/tests/postgres_tests/test_array.py +++ b/tests/postgres_tests/test_array.py @@ -156,6 +156,18 @@ def test_contains_charfield(self): [] ) + def test_contained_by_charfield(self): + self.assertSequenceEqual( + CharArrayModel.objects.filter(field__contained_by=['text']), + [] + ) + + def test_overlap_charfield(self): + self.assertSequenceEqual( + CharArrayModel.objects.filter(field__overlap=['text']), + [] + ) + def test_index(self): self.assertSequenceEqual( NullableIntegerArrayModel.objects.filter(field__0=2), From cb90d489da4247c1c7ced86a42a2d89488fec67c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 19 Jan 2015 20:54:57 -0500 Subject: [PATCH 0033/1125] [1.8.x] Fixed a query failure on Python 3.5; refs #23763. The failure was introduced in Django by c7fd9b242d2d63406f1de6cc3204e35aaa025233 and the change in Python 3.5 is https://hg.python.org/cpython/rev/a3c345ba3563. Backport of be1357e70983d4ad029a1ecdd05292f8be917a80 from master --- django/db/models/sql/query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index c5e7eab28c4d..9693206b67aa 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -402,7 +402,7 @@ def get_aggregation(self, using, added_aggregate_names): # Remove any aggregates marked for reduction from the subquery # and move them to the outer AggregateQuery. col_cnt = 0 - for alias, expression in inner_query.annotation_select.items(): + for alias, expression in list(inner_query.annotation_select.items()): if expression.is_summary: expression, col_cnt = inner_query.rewrite_cols(expression, col_cnt) outer_query.annotations[alias] = expression.relabeled_clone(relabels) From 20f1aafa14798d8caac74319a335668ffe86c224 Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Tue, 20 Jan 2015 11:39:23 +0100 Subject: [PATCH 0034/1125] [1.8.x] Refs #24163 -- Fixed failing Oracle test when migrating from ForeignKey to OneToOneField Thanks Tim Graham for review Backport of 64ecb3f07db4be5eef4d9eb7687f783ee446c82f from master --- tests/schema/tests.py | 50 ++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index c62cf7c773d2..65b4ab5c25bb 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -531,7 +531,7 @@ def test_alter_fk(self): @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support") def test_alter_o2o_to_fk(self): """ - #24163 - Tests altering of OneToOne to FK + #24163 - Tests altering of OneToOneField to ForeignKey """ # Create the table with connection.schema_editor() as editor: @@ -540,19 +540,21 @@ def test_alter_o2o_to_fk(self): # Ensure the field is right to begin with columns = self.column_classes(BookWithO2O) self.assertEqual(columns['author_id'][0], "IntegerField") - # Make sure the FK and unique constraints are present + # Ensure the field is unique + author = Author.objects.create(name="Joe") + BookWithO2O.objects.create(author=author, title="Django 1", pub_date=datetime.datetime.now()) + with self.assertRaises(IntegrityError): + BookWithO2O.objects.create(author=author, title="Django 2", pub_date=datetime.datetime.now()) + BookWithO2O.objects.all().delete() + # Make sure the FK constraint is present constraints = self.get_constraints(BookWithO2O._meta.db_table) author_is_fk = False - author_is_unique = False for name, details in constraints.items(): if details['columns'] == ['author_id']: if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'): author_is_fk = True - if details['unique']: - author_is_unique = True self.assertTrue(author_is_fk, "No FK constraint for author_id found") - self.assertTrue(author_is_unique, "No unique constraint for author_id found") - # Alter the O2O to FK + # Alter the OneToOneField to ForeignKey new_field = ForeignKey(Author) new_field.set_attributes_from_name("author") with connection.schema_editor() as editor: @@ -565,23 +567,22 @@ def test_alter_o2o_to_fk(self): # Ensure the field is right afterwards columns = self.column_classes(Book) self.assertEqual(columns['author_id'][0], "IntegerField") - # Make sure the FK constraint is present and unique constraint is absent + # Ensure the field is not unique anymore + Book.objects.create(author=author, title="Django 1", pub_date=datetime.datetime.now()) + Book.objects.create(author=author, title="Django 2", pub_date=datetime.datetime.now()) + # Make sure the FK constraint is still present constraints = self.get_constraints(Book._meta.db_table) author_is_fk = False - author_is_unique = True for name, details in constraints.items(): if details['columns'] == ['author_id']: if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'): author_is_fk = True - if not details['unique']: - author_is_unique = False self.assertTrue(author_is_fk, "No FK constraint for author_id found") - self.assertFalse(author_is_unique, "Unique constraint for author_id found") @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support") def test_alter_fk_to_o2o(self): """ - #24163 - Tests altering of FK to OneToOne + #24163 - Tests altering of ForeignKey to OneToOneField """ # Create the table with connection.schema_editor() as editor: @@ -590,19 +591,20 @@ def test_alter_fk_to_o2o(self): # Ensure the field is right to begin with columns = self.column_classes(Book) self.assertEqual(columns['author_id'][0], "IntegerField") - # Make sure the FK constraint is present and unique constraint is absent + # Ensure the field is not unique + author = Author.objects.create(name="Joe") + Book.objects.create(author=author, title="Django 1", pub_date=datetime.datetime.now()) + Book.objects.create(author=author, title="Django 2", pub_date=datetime.datetime.now()) + Book.objects.all().delete() + # Make sure the FK constraint is present constraints = self.get_constraints(Book._meta.db_table) author_is_fk = False - author_is_unique = True for name, details in constraints.items(): if details['columns'] == ['author_id']: if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'): author_is_fk = True - if not details['unique']: - author_is_unique = False self.assertTrue(author_is_fk, "No FK constraint for author_id found") - self.assertFalse(author_is_unique, "Unique constraint for author_id found") - # Alter the O2O to FK + # Alter the ForeignKey to OneToOneField new_field = OneToOneField(Author) new_field.set_attributes_from_name("author") with connection.schema_editor() as editor: @@ -615,18 +617,18 @@ def test_alter_fk_to_o2o(self): # Ensure the field is right afterwards columns = self.column_classes(BookWithO2O) self.assertEqual(columns['author_id'][0], "IntegerField") - # Make sure the FK and unique constraints are present + # Ensure the field is unique now + BookWithO2O.objects.create(author=author, title="Django 1", pub_date=datetime.datetime.now()) + with self.assertRaises(IntegrityError): + BookWithO2O.objects.create(author=author, title="Django 2", pub_date=datetime.datetime.now()) + # Make sure the FK constraint is present constraints = self.get_constraints(BookWithO2O._meta.db_table) author_is_fk = False - author_is_unique = False for name, details in constraints.items(): if details['columns'] == ['author_id']: if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'): author_is_fk = True - if details['unique']: - author_is_unique = True self.assertTrue(author_is_fk, "No FK constraint for author_id found") - self.assertTrue(author_is_unique, "No unique constraint for author_id found") def test_alter_implicit_id_to_explicit(self): """ From 90d9bf5ba46f806e7d4a7d19023f8d19028c4a27 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Tue, 20 Jan 2015 11:44:41 -0500 Subject: [PATCH 0035/1125] [1.8.x] Fixed #24186 -- Fixed a typo in the admin docs. Thanks to Keryn Knight for the report. Backport of dccf41cff0f46a94aa1d853d1bad9052079bb454 from master --- docs/ref/contrib/admin/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index a39affd7479f..adfc9f489ddb 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -663,7 +663,7 @@ subclass:: .. versionadded:: 1.7 - To indicate descending order with ``admin_model_field`` you can use a + To indicate descending order with ``admin_order_field`` you can use a hyphen prefix on the field name. Using the above example, this would look like:: From 45aaced91e4789c3a08b17f0908010923361dbec Mon Sep 17 00:00:00 2001 From: Adam Taylor Date: Tue, 20 Jan 2015 07:54:12 -0700 Subject: [PATCH 0036/1125] [1.8.x] Fixed typos in code comments. Backport of 039465a6a7a18f48ea77ceadb6949990c0ec92e1 from master --- django/contrib/auth/tests/test_hashers.py | 4 ++-- django/contrib/gis/geos/tests/test_geos.py | 2 +- django/contrib/gis/tests/geoapp/test_regress.py | 2 +- django/contrib/gis/tests/geoapp/tests.py | 4 ++-- django/db/backends/base/creation.py | 2 +- django/db/migrations/loader.py | 2 +- django/db/migrations/optimizer.py | 2 +- django/forms/models.py | 2 +- django/utils/regex_helper.py | 2 +- tests/admin_custom_urls/tests.py | 2 +- tests/admin_views/tests.py | 2 +- tests/backends/tests.py | 2 +- tests/basic/tests.py | 2 +- tests/delete/tests.py | 2 +- tests/file_uploads/tests.py | 2 +- tests/forms_tests/tests/test_extra.py | 2 +- tests/forms_tests/tests/test_fields.py | 2 +- tests/forms_tests/tests/test_formsets.py | 2 +- tests/i18n/test_extraction.py | 2 +- tests/m2m_signals/tests.py | 2 +- tests/migrations/test_state.py | 2 +- tests/model_fields/tests.py | 2 +- tests/model_formsets/tests.py | 2 +- tests/queries/tests.py | 2 +- tests/requests/tests.py | 2 +- tests/settings_tests/tests.py | 2 +- 26 files changed, 28 insertions(+), 28 deletions(-) diff --git a/django/contrib/auth/tests/test_hashers.py b/django/contrib/auth/tests/test_hashers.py index 10ab657ae55f..0c280ae158fd 100644 --- a/django/contrib/auth/tests/test_hashers.py +++ b/django/contrib/auth/tests/test_hashers.py @@ -269,7 +269,7 @@ def test_pbkdf2_upgrade(self): def setter(password): state['upgraded'] = True - # Check that no upgrade is triggerd + # Check that no upgrade is triggered self.assertTrue(check_password('letmein', encoded, setter)) self.assertFalse(state['upgraded']) @@ -298,7 +298,7 @@ def setter(password): algo, iterations, salt, hash = encoded.split('$', 3) self.assertEqual(iterations, '1') - # Check that no upgrade is triggerd + # Check that no upgrade is triggered self.assertTrue(check_password('letmein', encoded, setter)) self.assertFalse(state['upgraded']) diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index 9d093bfa3285..48d4aabf1365 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -63,7 +63,7 @@ class FakeGeom2(GEOSBase): # Anything that is either not None or the acceptable pointer type will # result in a TypeError when trying to assign it to the `ptr` property. - # Thus, memmory addresses (integers) and pointers of the incorrect type + # Thus, memory addresses (integers) and pointers of the incorrect type # (in `bad_ptrs`) will not be allowed. bad_ptrs = (5, ctypes.c_char_p(b'foobar')) for bad_ptr in bad_ptrs: diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py index e5cefb0752eb..85dc1c139a30 100644 --- a/django/contrib/gis/tests/geoapp/test_regress.py +++ b/django/contrib/gis/tests/geoapp/test_regress.py @@ -83,7 +83,7 @@ def test_boolean_conversion(self): val1 = Truth.objects.get(pk=t1.pk).val val2 = Truth.objects.get(pk=t2.pk).val - # verify types -- should't be 0/1 + # verify types -- shouldn't be 0/1 self.assertIsInstance(val1, bool) self.assertIsInstance(val2, bool) # verify values diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index 0dc4ce269d7e..31a269442178 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -456,7 +456,7 @@ def test_diff_intersection_union(self): geom = Point(5, 23) qs = Country.objects.all().difference(geom).sym_difference(geom).union(geom) - # XXX For some reason SpatiaLite does something screwey with the Texas geometry here. Also, + # XXX For some reason SpatiaLite does something screwy with the Texas geometry here. Also, # XXX it doesn't like the null intersection. if spatialite: qs = qs.exclude(name='Texas') @@ -599,7 +599,7 @@ def test_geojson(self): @skipUnlessDBFeature("has_gml_method") def test_gml(self): "Testing GML output from the database using GeoQuerySet.gml()." - # 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() self.assertRaises(TypeError, qs.gml, field_name='name') diff --git a/django/db/backends/base/creation.py b/django/db/backends/base/creation.py index 5044fcfb6c84..35cabba0cf62 100644 --- a/django/db/backends/base/creation.py +++ b/django/db/backends/base/creation.py @@ -409,7 +409,7 @@ def get_objects(): queryset = model._default_manager.using(self.connection.alias).order_by(model._meta.pk.name) for obj in queryset.iterator(): yield obj - # Serialise to a string + # Serialize to a string out = StringIO() serializers.serialize("json", get_objects(), indent=None, stream=out) return out.getvalue() diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py index 51fb2783ebbd..d7ee805bb1c3 100644 --- a/django/db/migrations/loader.py +++ b/django/db/migrations/loader.py @@ -195,7 +195,7 @@ def build_graph(self): for key, migration in normal.items(): for parent in migration.dependencies: reverse_dependencies.setdefault(parent, set()).add(key) - # Remeber the possible replacements to generate more meaningful error + # Remember the possible replacements to generate more meaningful error # messages reverse_replacements = {} for key, migration in replacing.items(): diff --git a/django/db/migrations/optimizer.py b/django/db/migrations/optimizer.py index c39bf15453a7..8c5ee718194f 100644 --- a/django/db/migrations/optimizer.py +++ b/django/db/migrations/optimizer.py @@ -229,7 +229,7 @@ def reduce_model_rename_self(self, operation, other, in_between): def reduce_create_model_add_field(self, operation, other, in_between): if operation.name_lower == other.model_name_lower: - # Don't allow optimisations of FKs through models they reference + # Don't allow optimizations of FKs through models they reference if hasattr(other.field, "rel") and other.field.rel: for between in in_between: # Check that it doesn't point to the model diff --git a/django/forms/models.py b/django/forms/models.py index 1ec13981472c..b7068e8c7508 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -540,7 +540,7 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None, "'exclude' explicitly is prohibited." ) - # Instatiate type(form) in order to use the same metaclass as form. + # Instantiate type(form) in order to use the same metaclass as form. return type(form)(class_name, (form,), form_class_attrs) diff --git a/django/utils/regex_helper.py b/django/utils/regex_helper.py index a43234ad05f1..53736dffa77a 100644 --- a/django/utils/regex_helper.py +++ b/django/utils/regex_helper.py @@ -165,7 +165,7 @@ def normalize(pattern): else: result.append(Group((("%%(%s)s" % param), None))) elif ch in "*?+{": - # Quanitifers affect the previous item in the result list. + # Quantifiers affect the previous item in the result list. count, ch = get_quantifier(ch, pattern_iter) if ch: # We had to look ahead, but it wasn't need to compute the diff --git a/tests/admin_custom_urls/tests.py b/tests/admin_custom_urls/tests.py index f3182cd24645..ea3c0704b3c6 100644 --- a/tests/admin_custom_urls/tests.py +++ b/tests/admin_custom_urls/tests.py @@ -71,7 +71,7 @@ def test_admin_URLs_no_clash(self): self.assertContains(response, 'Change action') # Should correctly get the change_view for the model instance with the - # funny-looking PK (the one wth a 'path/to/html/document.html' value) + # funny-looking PK (the one with a 'path/to/html/document.html' value) url = reverse('admin:%s_action_change' % Action._meta.app_label, args=(quote("path/to/html/document.html"),)) response = self.client.get(url) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 0c28ecc02991..a58474575e77 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -1783,7 +1783,7 @@ def test_no_standard_modeladmin_urls(self): self.client.get('/test_admin/admin/') r = self.client.post(reverse('admin:login'), self.changeuser_login) r = self.client.get('/test_admin/admin/') - # we shouldn' get an 500 error caused by a NoReverseMatch + # we shouldn't get a 500 error caused by a NoReverseMatch self.assertEqual(r.status_code, 200) self.client.get('/test_admin/admin/logout/') diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 3d4185e7628f..4a07cd5f2f39 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -772,7 +772,7 @@ def test_integrity_checks_on_update(self): models.Article.objects.create(headline='Another article', pub_date=datetime.datetime(1988, 5, 15), reporter=self.r, reporter_proxy=r_proxy) - # Retreive the second article from the DB + # Retrieve the second article from the DB a2 = models.Article.objects.get(headline='Another article') a2.reporter_proxy_id = 30 self.assertRaises(IntegrityError, a2.save) diff --git a/tests/basic/tests.py b/tests/basic/tests.py index 3ba94d058d14..72fdbaa8d828 100644 --- a/tests/basic/tests.py +++ b/tests/basic/tests.py @@ -357,7 +357,7 @@ def test_create_relation_with_ugettext_lazy(self): Article.objects.create(headline=lazy, pub_date=datetime.now()) article = Article.objects.get() self.assertEqual(article.headline, notlazy) - # test that assign + save works with Promise objecs + # test that assign + save works with Promise objects article.headline = lazy article.save() self.assertEqual(article.headline, notlazy) diff --git a/tests/delete/tests.py b/tests/delete/tests.py index 2d6c02b9a96f..c04284e06908 100644 --- a/tests/delete/tests.py +++ b/tests/delete/tests.py @@ -324,7 +324,7 @@ def test_large_delete(self): # One query for Avatar.objects.all() and then one related fast delete for # each batch. fetches_to_mem = 1 + batches - # The Avatar objecs are going to be deleted in batches of GET_ITERATOR_CHUNK_SIZE + # The Avatar objects are going to be deleted in batches of GET_ITERATOR_CHUNK_SIZE queries = fetches_to_mem + TEST_SIZE // GET_ITERATOR_CHUNK_SIZE self.assertNumQueries(queries, Avatar.objects.all().delete) self.assertFalse(Avatar.objects.exists()) diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py index 130a3c7649e5..34681122e4c5 100644 --- a/tests/file_uploads/tests.py +++ b/tests/file_uploads/tests.py @@ -187,7 +187,7 @@ def test_dangerous_file_names(self): # trying such an attack. scary_file_names = [ "/tmp/hax0rd.txt", # Absolute path, *nix-style. - "C:\\Windows\\hax0rd.txt", # Absolute path, win-syle. + "C:\\Windows\\hax0rd.txt", # Absolute path, win-style. "C:/Windows/hax0rd.txt", # Absolute path, broken-style. "\\tmp\\hax0rd.txt", # Absolute path, broken in a different way. "/tmp\\hax0rd.txt", # Absolute path, broken by mixing. diff --git a/tests/forms_tests/tests/test_extra.py b/tests/forms_tests/tests/test_extra.py index d172e17cd94d..32f7e8cc0486 100644 --- a/tests/forms_tests/tests/test_extra.py +++ b/tests/forms_tests/tests/test_extra.py @@ -390,7 +390,7 @@ def test_multiwidget(self): # MultiWidget and MultiValueField ############################################# # MultiWidgets are widgets composed of other widgets. They are usually # combined with MultiValueFields - a field that is composed of other fields. - # MulitWidgets can themselved be composed of other MultiWidgets. + # MulitWidgets can themselves be composed of other MultiWidgets. # SplitDateTimeWidget is one example of a MultiWidget. class ComplexMultiWidget(MultiWidget): diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py index ea6e84247519..c711360a504d 100644 --- a/tests/forms_tests/tests/test_fields.py +++ b/tests/forms_tests/tests/test_fields.py @@ -1026,7 +1026,7 @@ def test_typedchoicefield_3(self): def test_typedchoicefield_4(self): # Even more weirdness: if you have a valid choice but your coercion function - # can't coerce, yo'll still get a validation error. Don't do this! + # can't coerce, you'll still get a validation error. Don't do this! f = TypedChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int) self.assertRaisesMessage(ValidationError, "'Select a valid choice. B is not one of the available choices.'", f.clean, 'B') # Required fields require values diff --git a/tests/forms_tests/tests/test_formsets.py b/tests/forms_tests/tests/test_formsets.py index 94e27044ffe4..276bf1cb228b 100644 --- a/tests/forms_tests/tests/test_formsets.py +++ b/tests/forms_tests/tests/test_formsets.py @@ -893,7 +893,7 @@ def test_formset_iteration(self): except IndexError: pass - # Formets can override the default iteration order + # Formsets can override the default iteration order class BaseReverseFormSet(BaseFormSet): def __iter__(self): return reversed(self.forms) diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py index ad96f978bd9e..94948b203ebb 100644 --- a/tests/i18n/test_extraction.py +++ b/tests/i18n/test_extraction.py @@ -510,7 +510,7 @@ def test_symlink(self): else: # On Python >= 3.2) os.symlink() exists always but then can # fail at runtime when user hasn't the needed permissions on - # WIndows versions that support symbolink links (>= 6/Vista). + # Windows versions that support symbolink links (>= 6/Vista). # See Python issue 9333 (http://bugs.python.org/issue9333). # Skip the test in that case try: diff --git a/tests/m2m_signals/tests.py b/tests/m2m_signals/tests.py index 31a83212f5de..ea4649551def 100644 --- a/tests/m2m_signals/tests.py +++ b/tests/m2m_signals/tests.py @@ -90,7 +90,7 @@ def test_m2m_relations_add_remove_clear(self): }) self.assertEqual(self.m2m_changed_messages, expected_messages) - # give the BMW and Toyata some doors as well + # give the BMW and Toyota some doors as well self.doors.car_set.add(self.bmw, self.toyota) expected_messages.append({ 'instance': self.doors, diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py index 6410477b3614..a7dbdbe11c3a 100644 --- a/tests/migrations/test_state.py +++ b/tests/migrations/test_state.py @@ -86,7 +86,7 @@ class Meta: mgr2 = FoodManager('x', 'y', c=3, d=4) class FoodOrderedManagers(models.Model): - # The managers on this model should be orderd by their creation + # The managers on this model should be ordered by their creation # counter and not by the order in model body food_no_mgr = NoMigrationFoodManager('x', 'y') diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index a9ce43cae627..1e30297f6664 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -333,7 +333,7 @@ def test_select_related(self): # Test select_related('fk_field_name') ma = FksToBooleans.objects.select_related('bf').get(pk=m1.id) - # verify types -- should't be 0/1 + # verify types -- shouldn't be 0/1 self.assertIsInstance(ma.bf.bfield, bool) self.assertIsInstance(ma.nbf.nbfield, bool) # verify values diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py index 80c5c6fa0906..acd05c9bf2d0 100644 --- a/tests/model_formsets/tests.py +++ b/tests/model_formsets/tests.py @@ -1067,7 +1067,7 @@ def test_unique_together_with_inlineformset_factory(self): self.assertEqual(revision1.repository, repository) self.assertEqual(revision1.revision, '146239817507f148d448db38840db7c3cbf47c76') - # attempt to save the same revision against against the same repo. + # attempt to save the same revision against the same repo. data = { 'revision_set-TOTAL_FORMS': '1', 'revision_set-INITIAL_FORMS': '0', diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 92e5982e10ed..c8d8532df31d 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -1771,7 +1771,7 @@ def test_ticket7872(self): # Another variation on the disjunctive filtering theme. # For the purposes of this regression test, it's important that there is no - # Join object releated to the LeafA we create. + # Join object related to the LeafA we create. LeafA.objects.create(data='first') self.assertQuerysetEqual(LeafA.objects.all(), ['']) self.assertQuerysetEqual( diff --git a/tests/requests/tests.py b/tests/requests/tests.py index 306e5e76bbbf..e9fdefb7ea85 100644 --- a/tests/requests/tests.py +++ b/tests/requests/tests.py @@ -352,7 +352,7 @@ def test_body_after_POST_multipart_form_data(self): """ Reading body after parsing multipart/form-data is not allowed """ - # Because multipart is used for large amounts fo data i.e. file uploads, + # Because multipart is used for large amounts of data i.e. file uploads, # we don't want the data held in memory twice, and we don't want to # silence the error by setting body = '' either. payload = FakePayload("\r\n".join([ diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py index f1a43763b66a..0befd18e293d 100644 --- a/tests/settings_tests/tests.py +++ b/tests/settings_tests/tests.py @@ -117,7 +117,7 @@ def test_override(self): self.assertEqual(settings.TEST, 'override') def test_setupclass_override(self): - """Test that settings are overriden within setUpClass -- refs #21281""" + """Test that settings are overridden within setUpClass -- refs #21281""" self.assertEqual(self.foo, 'override') @override_settings(TEST='override2') From 0580133971e42c322a9c0b451d3bbf72eff2ca24 Mon Sep 17 00:00:00 2001 From: Loic Bistuer Date: Wed, 21 Jan 2015 12:47:49 +0700 Subject: [PATCH 0037/1125] [1.8.x] Fixed small inconsistency when handling aggregate's default_alias. Refs #14030. Backport of d450af8a26 from master --- django/db/models/query.py | 16 ++++++++++++---- tests/aggregation/tests.py | 4 +++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 6774feef0cf2..2a1dbf94724c 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -288,7 +288,13 @@ def aggregate(self, *args, **kwargs): if self.query.distinct_fields: raise NotImplementedError("aggregate() + distinct(fields) not implemented.") for arg in args: - if not hasattr(arg, 'default_alias'): + # The default_alias property may raise a TypeError, so we use + # a try/except construct rather than hasattr in order to remain + # consistent between PY2 and PY3 (hasattr would swallow + # the TypeError on PY2). + try: + arg.default_alias + except (AttributeError, TypeError): raise TypeError("Complex aggregates require an alias") kwargs[arg.default_alias] = arg @@ -759,14 +765,16 @@ def annotate(self, *args, **kwargs): """ annotations = OrderedDict() # To preserve ordering of args for arg in args: + # The default_alias property may raise a TypeError, so we use + # a try/except construct rather than hasattr in order to remain + # consistent between PY2 and PY3 (hasattr would swallow + # the TypeError on PY2). try: - # we can't do an hasattr here because py2 returns False - # if default_alias exists but throws a TypeError if arg.default_alias in kwargs: raise ValueError("The named annotation '%s' conflicts with the " "default name for another annotation." % arg.default_alias) - except AttributeError: # default_alias + except (AttributeError, TypeError): raise TypeError("Complex annotations require an alias") annotations[arg.default_alias] = arg annotations.update(kwargs) diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py index ec19fcfd5337..7e500dfb0e60 100644 --- a/tests/aggregation/tests.py +++ b/tests/aggregation/tests.py @@ -768,10 +768,12 @@ def test_combine_different_types(self): self.assertEqual(b3.sums, Approximate(Decimal("383.69"), places=2)) def test_complex_aggregations_require_kwarg(self): - with six.assertRaisesRegex(self, TypeError, 'Complex expressions require an alias'): + with six.assertRaisesRegex(self, TypeError, 'Complex annotations require an alias'): Author.objects.annotate(Sum(F('age') + F('friends__age'))) with six.assertRaisesRegex(self, TypeError, 'Complex aggregates require an alias'): Author.objects.aggregate(Sum('age') / Count('age')) + with six.assertRaisesRegex(self, TypeError, 'Complex aggregates require an alias'): + Author.objects.aggregate(Sum(Value(1))) def test_aggregate_over_complex_annotation(self): qs = Author.objects.annotate( From 11a5e45b96c3a15826927f5d0e50472767b937f1 Mon Sep 17 00:00:00 2001 From: Andriy Sokolovskiy Date: Fri, 9 Jan 2015 00:51:00 +0200 Subject: [PATCH 0038/1125] [1.8.x] Fixed #24104 -- Fixed check to look on field.many_to_many instead of class instance Backport of 38c17871bb6dafd489367f6fe8bc56199223adb8 from master --- django/db/backends/base/schema.py | 5 ++- django/db/backends/sqlite3/schema.py | 9 +++-- tests/schema/fields.py | 54 ++++++++++++++++++++++++++++ tests/schema/tests.py | 45 +++++++++++++++++++++++ 4 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 tests/schema/fields.py diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 467d5fb3978a..00306d6803d4 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -1,7 +1,6 @@ import hashlib from django.db.backends.utils import truncate_name -from django.db.models.fields.related import ManyToManyField from django.db.transaction import atomic from django.utils import six from django.utils.encoding import force_bytes @@ -380,7 +379,7 @@ def add_field(self, model, field): table instead (for M2M fields) """ # Special-case implicit M2M tables - if isinstance(field, ManyToManyField) and field.rel.through._meta.auto_created: + if field.many_to_many and field.rel.through._meta.auto_created: return self.create_model(field.rel.through) # Get the column's definition definition, params = self.column_sql(model, field, include_default=True) @@ -424,7 +423,7 @@ def remove_field(self, model, field): but for M2Ms may involve deleting a table. """ # Special-case implicit M2M tables - if isinstance(field, ManyToManyField) and field.rel.through._meta.auto_created: + if field.many_to_many and field.rel.through._meta.auto_created: return self.delete_model(field.rel.through) # It might not actually have a column behind it if field.db_parameters(connection=self.connection)['type'] is None: diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py index 814b94ffee02..031bf7784064 100644 --- a/django/db/backends/sqlite3/schema.py +++ b/django/db/backends/sqlite3/schema.py @@ -4,7 +4,6 @@ from django.apps.registry import Apps from django.db.backends.base.schema import BaseDatabaseSchemaEditor -from django.db.models.fields.related import ManyToManyField from django.utils import six import _sqlite3 @@ -71,7 +70,7 @@ def _remake_table(self, model, create_fields=[], delete_fields=[], alter_fields= for field in create_fields: body[field.name] = field # Choose a default and insert it into the copy map - if not isinstance(field, ManyToManyField): + if not field.many_to_many: mapping[field.column] = self.quote_value( self.effective_default(field) ) @@ -94,7 +93,7 @@ def _remake_table(self, model, create_fields=[], delete_fields=[], alter_fields= del body[field.name] del mapping[field.column] # Remove any implicit M2M tables - if isinstance(field, ManyToManyField) and field.rel.through._meta.auto_created: + if field.many_to_many and field.rel.through._meta.auto_created: return self.delete_model(field.rel.through) # Work inside a new app registry apps = Apps() @@ -173,7 +172,7 @@ def add_field(self, model, field): table instead (for M2M fields) """ # Special-case implicit M2M tables - if isinstance(field, ManyToManyField) and field.rel.through._meta.auto_created: + if field.many_to_many and field.rel.through._meta.auto_created: return self.create_model(field.rel.through) self._remake_table(model, create_fields=[field]) @@ -183,7 +182,7 @@ def remove_field(self, model, field): but for M2Ms may involve deleting a table. """ # M2M fields are a special case - if isinstance(field, ManyToManyField): + if field.many_to_many: # For implicit M2M tables, delete the auto-created table if field.rel.through._meta.auto_created: self.delete_model(field.rel.through) diff --git a/tests/schema/fields.py b/tests/schema/fields.py new file mode 100644 index 000000000000..4f70c96b0bee --- /dev/null +++ b/tests/schema/fields.py @@ -0,0 +1,54 @@ +from django.db.models.fields.related import ( + create_many_to_many_intermediary_model, + ManyToManyField, ManyToManyRel, RelatedField, + RECURSIVE_RELATIONSHIP_CONSTANT, ReverseManyRelatedObjectsDescriptor, +) + +from django.utils.functional import curry + + +class CustomManyToManyField(RelatedField): + """ + Ticket #24104 - Need to have a custom ManyToManyField, + which is not an inheritor of ManyToManyField. + """ + many_to_many = True + + def __init__(self, to, db_constraint=True, swappable=True, **kwargs): + try: + to._meta + except AttributeError: + to = str(to) + kwargs['rel'] = ManyToManyRel( + self, to, + related_name=kwargs.pop('related_name', None), + related_query_name=kwargs.pop('related_query_name', None), + limit_choices_to=kwargs.pop('limit_choices_to', None), + symmetrical=kwargs.pop('symmetrical', to == RECURSIVE_RELATIONSHIP_CONSTANT), + through=kwargs.pop('through', None), + through_fields=kwargs.pop('through_fields', None), + db_constraint=db_constraint, + ) + self.swappable = swappable + self.db_table = kwargs.pop('db_table', None) + if kwargs['rel'].through is not None: + assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." + super(CustomManyToManyField, self).__init__(**kwargs) + + def contribute_to_class(self, cls, name, **kwargs): + if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name): + self.rel.related_name = "%s_rel_+" % name + super(CustomManyToManyField, self).contribute_to_class(cls, name, **kwargs) + if not self.rel.through and not cls._meta.abstract and not cls._meta.swapped: + self.rel.through = create_many_to_many_intermediary_model(self, cls) + setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) + self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) + + def get_internal_type(self): + return 'ManyToManyField' + + # Copy those methods from ManyToManyField because they don't call super() internally + contribute_to_related_class = ManyToManyField.__dict__['contribute_to_related_class'] + _get_m2m_attr = ManyToManyField.__dict__['_get_m2m_attr'] + _get_m2m_reverse_attr = ManyToManyField.__dict__['_get_m2m_reverse_attr'] + _get_m2m_db_table = ManyToManyField.__dict__['_get_m2m_db_table'] diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 65b4ab5c25bb..2dd56d7248e5 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -7,6 +7,7 @@ PositiveIntegerField, SlugField, TextField) from django.db.models.fields.related import ForeignKey, ManyToManyField, OneToOneField from django.db.transaction import atomic +from .fields import CustomManyToManyField from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough, @@ -1303,3 +1304,47 @@ def test_add_field_use_effective_default(self): cursor.execute("SELECT surname FROM schema_author;") item = cursor.fetchall()[0] self.assertEqual(item[0], None if connection.features.interprets_empty_strings_as_nulls else '') + + def test_custom_manytomanyfield(self): + """ + #24104 - Schema editors should look for many_to_many + """ + # Create the tables + with connection.schema_editor() as editor: + editor.create_model(AuthorWithM2M) + editor.create_model(TagM2MTest) + # Create an M2M field + new_field = CustomManyToManyField("schema.TagM2MTest", related_name="authors") + new_field.contribute_to_class(AuthorWithM2M, "tags") + # Ensure there's no m2m table there + self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through) + try: + # Add the field + with connection.schema_editor() as editor: + editor.add_field( + AuthorWithM2M, + new_field, + ) + # Ensure there is now an m2m table there + columns = self.column_classes(new_field.rel.through) + self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField") + + # "Alter" the field. This should not rename the DB table to itself. + with connection.schema_editor() as editor: + editor.alter_field( + AuthorWithM2M, + new_field, + new_field, + ) + + # Remove the M2M table again + with connection.schema_editor() as editor: + editor.remove_field( + AuthorWithM2M, + new_field, + ) + # Ensure there's no m2m table there + self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through) + finally: + # Cleanup model states + AuthorWithM2M._meta.local_many_to_many.remove(new_field) From 1806e059f6127e3e4572db09f297984c96ea9d02 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 22 Jan 2015 15:23:45 -0500 Subject: [PATCH 0039/1125] [1.8.x] Isolated a flatpages test; refs #11505. Backport of 4135d837027eac43ec416856d9476c478167d8a6 from master --- django/contrib/flatpages/tests/test_sitemaps.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/django/contrib/flatpages/tests/test_sitemaps.py b/django/contrib/flatpages/tests/test_sitemaps.py index 3b8e57d1746a..dcfbef4e29e8 100644 --- a/django/contrib/flatpages/tests/test_sitemaps.py +++ b/django/contrib/flatpages/tests/test_sitemaps.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from django.apps import apps +from django.contrib.sites.models import Site from django.test import TestCase from django.test.utils import modify_settings, override_settings @@ -12,6 +13,13 @@ @modify_settings(INSTALLED_APPS={'append': ['django.contrib.sitemaps']},) class FlatpagesSitemapTests(TestCase): + @classmethod + def setUpClass(cls): + super(FlatpagesSitemapTests, cls).setUpClass() + # This cleanup is necessary because contrib.sites cache + # makes tests interfere with each other, see #11505 + Site.objects.clear_cache() + @classmethod def setUpTestData(cls): Site = apps.get_model('sites.Site') From bc93568500303b4fa00c89c7f1607fab455b6c32 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 23 Jan 2015 09:05:46 +0100 Subject: [PATCH 0040/1125] [1.8.x] Fixed warning leak in static.serve() test Partial forward port of b1bf8d64fb from 1.7.x. Refs #24193. --- docs/releases/1.7.4.txt | 3 +++ tests/view_tests/tests/test_static.py | 1 + 2 files changed, 4 insertions(+) diff --git a/docs/releases/1.7.4.txt b/docs/releases/1.7.4.txt index 5f10e5e8c60e..0d53459068e9 100644 --- a/docs/releases/1.7.4.txt +++ b/docs/releases/1.7.4.txt @@ -17,3 +17,6 @@ Bugfixes * Fixed a migration crash on MySQL when migrating from a ``OneToOneField`` to a ``ForeignKey`` (:ticket:`24163`). + +* Prevented the ``static.serve`` view from producing ``ResourceWarning``\s in + certain circumstances (security fix regression, :ticket:`24193`). diff --git a/tests/view_tests/tests/test_static.py b/tests/view_tests/tests/test_static.py index 808c07d60517..5a353806e113 100644 --- a/tests/view_tests/tests/test_static.py +++ b/tests/view_tests/tests/test_static.py @@ -38,6 +38,7 @@ def test_chunked(self): first_chunk = next(response.streaming_content) self.assertEqual(len(first_chunk), FileResponse.block_size) second_chunk = next(response.streaming_content) + response.close() # strip() to prevent OS line endings from causing differences self.assertEqual(len(second_chunk.strip()), 1449) From 2ed1980e0f82c9a952bdecbeb6f5d36513d74cc8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 23 Jan 2015 12:58:57 -0500 Subject: [PATCH 0041/1125] [1.8.x] Clarified docstring in dispatch/dispatcher.py Backport of 851f5bd413a93708436a129442007448755b34f3 from master --- django/dispatch/dispatcher.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index fcf80f3facff..b02adb8e807b 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -64,9 +64,9 @@ def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): Receivers must be able to accept keyword arguments. - If receivers have a dispatch_uid attribute, the receiver will - not be added if another receiver already exists with that - dispatch_uid. + If a receiver is connected with a dispatch_uid argument, it + will not be added if another receiver was already connected + with that dispatch_uid. sender The sender to which the receiver should respond. Must either be From 56015c01c4725aeeb225a62f2702a4bbb3a3ce54 Mon Sep 17 00:00:00 2001 From: Ng Zhi An Date: Sun, 18 Jan 2015 02:20:42 +0000 Subject: [PATCH 0042/1125] [1.8.x] Fixed #24170 -- Implemented decompress for BaseRangeField widgets Backport of 4669b6a807811d6763b9fdc5df974cb67aa1fb56 from master --- django/contrib/postgres/forms/ranges.py | 15 ++++++++++++-- docs/ref/contrib/postgres/forms.txt | 26 ++++++++++++++++++++++++- tests/postgres_tests/test_ranges.py | 21 ++++++++++++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/django/contrib/postgres/forms/ranges.py b/django/contrib/postgres/forms/ranges.py index 4f8989a96d59..23db4fe51413 100644 --- a/django/contrib/postgres/forms/ranges.py +++ b/django/contrib/postgres/forms/ranges.py @@ -1,5 +1,6 @@ from django.core import exceptions from django import forms +from django.forms.widgets import MultiWidget from django.utils.translation import ugettext_lazy as _ from psycopg2.extras import NumericRange, DateRange, DateTimeTZRange @@ -15,8 +16,7 @@ class BaseRangeField(forms.MultiValueField): } def __init__(self, **kwargs): - widget = forms.MultiWidget([self.base_field.widget, self.base_field.widget]) - kwargs.setdefault('widget', widget) + kwargs.setdefault('widget', RangeWidget(self.base_field.widget)) kwargs.setdefault('fields', [self.base_field(required=False), self.base_field(required=False)]) kwargs.setdefault('required', False) kwargs.setdefault('require_all_fields', False) @@ -67,3 +67,14 @@ class DateTimeRangeField(BaseRangeField): class DateRangeField(BaseRangeField): base_field = forms.DateField range_type = DateRange + + +class RangeWidget(MultiWidget): + def __init__(self, base_widget, attrs=None): + widgets = (base_widget, base_widget) + super(RangeWidget, self).__init__(widgets, attrs) + + def decompress(self, value): + if value: + return (value.lower, value.upper) + return (None, None) diff --git a/docs/ref/contrib/postgres/forms.txt b/docs/ref/contrib/postgres/forms.txt index f6226c4b55bf..d1a15907ae1e 100644 --- a/docs/ref/contrib/postgres/forms.txt +++ b/docs/ref/contrib/postgres/forms.txt @@ -161,7 +161,8 @@ Range Fields This group of fields all share similar functionality for accepting range data. They are based on :class:`~django.forms.MultiValueField`. They treat one omitted value as an unbounded range. They also validate that the lower bound is -not greater than the upper bound. +not greater than the upper bound. All of these fields use +:class:`~django.contrib.postgres.forms.RangeWidget`. IntegerRangeField ~~~~~~~~~~~~~~~~~ @@ -199,3 +200,26 @@ DateRangeField Based on :class:`~django.forms.DateField` and translates its input into :class:`~psycopg2:psycopg2.extras.DateRange`. Default for :class:`~django.contrib.postgres.fields.DateRangeField`. + +Widgets +------- + +RangeWidget +~~~~~~~~~~~ + +.. class:: RangeWidget(base_widget, attrs=None) + + Widget used by all of the range fields. + Based on :class:`~django.forms.MultiWidget`. + + :class:`~RangeWidget` has one required argument: + + .. attribute:: base_widget + + A :class:`~RangeWidget` comprises a 2-tuple of ``base_widget``. + + .. method:: decompress(value) + + Takes a single "compressed" value of a field, for example a + :class:`~django.contrib.postgres.fields.DateRangeField`, + and returns a tuple representing and lower and upper bound. diff --git a/tests/postgres_tests/test_ranges.py b/tests/postgres_tests/test_ranges.py index 6d35e62cc50e..bc15987ba705 100644 --- a/tests/postgres_tests/test_ranges.py +++ b/tests/postgres_tests/test_ranges.py @@ -374,3 +374,24 @@ def test_model_field_formfield_datetime(self): model_field = pg_fields.DateTimeRangeField() form_field = model_field.formfield() self.assertIsInstance(form_field, pg_forms.DateTimeRangeField) + + +class TestWidget(TestCase): + def test_range_widget(self): + f = pg_forms.ranges.DateTimeRangeField() + self.assertHTMLEqual( + f.widget.render('datetimerange', ''), + '' + ) + self.assertHTMLEqual( + f.widget.render('datetimerange', None), + '' + ) + dt_range = DateTimeTZRange( + datetime.datetime(2006, 1, 10, 7, 30), + datetime.datetime(2006, 2, 12, 9, 50) + ) + self.assertHTMLEqual( + f.widget.render('datetimerange', dt_range), + '' + ) From 1ee18a60460d192953fbd1e8c9b7a75aac5d0bd0 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 23 Jan 2015 16:03:30 -0500 Subject: [PATCH 0043/1125] [1.8.x] Moved imports in GIS tests to avoid failure if dependencies aren't installed. Backport of 1e219ac62f3313f5a5f8eb188240a11bedd41aa1 from master --- django/contrib/gis/tests/geoapp/test_regress.py | 2 +- django/contrib/gis/tests/geoapp/tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py index 85dc1c139a30..60dbe5e56d3b 100644 --- a/django/contrib/gis/tests/geoapp/test_regress.py +++ b/django/contrib/gis/tests/geoapp/test_regress.py @@ -3,7 +3,6 @@ from datetime import datetime -from django.contrib.gis.db.models import Extent from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.shortcuts import render_to_kmz from django.contrib.gis.tests.utils import no_oracle @@ -11,6 +10,7 @@ from django.test import TestCase, skipUnlessDBFeature if HAS_GEOS: + from django.contrib.gis.db.models import Extent from .models import City, PennsylvaniaCity, State, Truth diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index 31a269442178..8a885b28276a 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -5,7 +5,6 @@ from django.db import connection from django.contrib.gis import gdal -from django.contrib.gis.db.models import Extent, MakeLine, Union from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.tests.utils import no_oracle, oracle, postgis, spatialite from django.core.management import call_command @@ -14,6 +13,7 @@ from django.utils.deprecation import RemovedInDjango20Warning if HAS_GEOS: + from django.contrib.gis.db.models import Extent, MakeLine, Union from django.contrib.gis.geos import (fromstr, GEOSGeometry, Point, LineString, LinearRing, Polygon, GeometryCollection) from .models import Country, City, PennsylvaniaCity, State, Track, NonConcreteModel, Feature, MinusOneSRID From 242c9538c8d252f27581e2b192237301f94a2928 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 23 Jan 2015 08:23:44 -0500 Subject: [PATCH 0044/1125] [1.8.x] Fixed test_runner test failure on Python 3.5; refs #23763. Python change is http://bugs.python.org/issue22032 Backport of 0386b97706052b88cd6fbbf777698810981cfeb6 from master --- tests/test_runner/test_debug_sql.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_runner/test_debug_sql.py b/tests/test_runner/test_debug_sql.py index 7bc0950d8434..cc583cbb377b 100644 --- a/tests/test_runner/test_debug_sql.py +++ b/tests/test_runner/test_debug_sql.py @@ -1,3 +1,4 @@ +import sys import unittest from django.db import connection @@ -82,9 +83,12 @@ def test_output_verbose(self): ] verbose_expected_outputs = [ - 'runTest (test_runner.test_debug_sql.FailingTest) ... FAIL', - 'runTest (test_runner.test_debug_sql.ErrorTest) ... ERROR', - 'runTest (test_runner.test_debug_sql.PassingTest) ... ok', + # Output format changed in Python 3.5+ + x.format('' if sys.version_info < (3, 5) else 'TestDebugSQL.') for x in [ + 'runTest (test_runner.test_debug_sql.{}FailingTest) ... FAIL', + 'runTest (test_runner.test_debug_sql.{}ErrorTest) ... ERROR', + 'runTest (test_runner.test_debug_sql.{}PassingTest) ... ok', + ] ] if six.PY3: verbose_expected_outputs += [ From 09e8985f25ddd901a3678d4b34878d6c631b0cea Mon Sep 17 00:00:00 2001 From: Collin Anderson Date: Tue, 20 Jan 2015 10:20:02 -0500 Subject: [PATCH 0045/1125] [1.8.x] Fixed #24190 -- Clarified len(queryset) Backport of ee23e03637aa8b82311f93b0a660574a0512891a from master --- docs/ref/models/querysets.txt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index e7d293fc89ac..6dca128cbdee 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -57,11 +57,10 @@ You can evaluate a ``QuerySet`` in the following ways: * **len().** A ``QuerySet`` is evaluated when you call ``len()`` on it. This, as you might expect, returns the length of the result list. - Note: *Don't* use ``len()`` on ``QuerySet``\s if all you want to do is - determine the number of records in the set. It's much more efficient to - handle a count at the database level, using SQL's ``SELECT COUNT(*)``, - and Django provides a ``count()`` method for precisely this reason. See - ``count()`` below. + Note: If you only need to determine the number of records in the set (and + don't need the actual objects), it's much more efficient to handle a count + at the database level using SQL's ``SELECT COUNT(*)``. Django provides a + :meth:`~QuerySet.count` method for precisely this reason. * **list().** Force evaluation of a ``QuerySet`` by calling ``list()`` on it. For example:: @@ -76,9 +75,8 @@ You can evaluate a ``QuerySet`` in the following ways: if Entry.objects.filter(headline="Test"): print("There is at least one Entry with the headline Test") - Note: *Don't* use this if all you want to do is determine if at least one - result exists, and don't need the actual objects. It's more efficient to - use :meth:`~QuerySet.exists` (see below). + Note: If you only want to determine if at least one result exists (and don't + need the actual objects), it's more efficient to use :meth:`~QuerySet.exists`. .. _pickling QuerySets: @@ -1805,6 +1803,11 @@ Depending on which database you're using (e.g. PostgreSQL vs. MySQL), is an underlying implementation quirk that shouldn't pose any real-world problems. +Note that if you want the number of items in a ``QuerySet`` and are also +retrieving model instances from it (for example, by iterating over it), it's +probably more efficient to use ``len(queryset)`` which won't cause an extra +database query like ``count()`` would. + in_bulk ~~~~~~~ From 2d990fb7fa6a04bb709aa9d7f687ec009c557c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Ehrlich?= Date: Mon, 26 Jan 2015 11:23:48 +0100 Subject: [PATCH 0046/1125] [1.8.x] Fixed #24221 - Used precompiled regexp for percent-placeholder matching. Backport of ea0ea7859a224225950a4df7c23eb3a7d823ddcd from master --- django/views/generic/edit.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/django/views/generic/edit.py b/django/views/generic/edit.py index ef70501bc2ca..6f514c3a703e 100644 --- a/django/views/generic/edit.py +++ b/django/views/generic/edit.py @@ -12,6 +12,8 @@ from django.views.generic.detail import (SingleObjectMixin, SingleObjectTemplateResponseMixin, BaseDetailView) +PERCENT_PLACEHOLDER_REGEX = re.compile(r'%\([^\)]+\)') # RemovedInDjango20Warning + class FormMixinBase(type): def __new__(cls, name, bases, attrs): @@ -163,7 +165,7 @@ def get_success_url(self): Returns the supplied URL. """ if self.success_url: - if re.search(r'%\([^\)]+\)', self.success_url): + if PERCENT_PLACEHOLDER_REGEX.search(self.success_url): warnings.warn( "%()s placeholder style in success_url is deprecated. " "Please replace them by the {} Python format syntax.", @@ -297,7 +299,7 @@ def post(self, request, *args, **kwargs): def get_success_url(self): if self.success_url: - if re.search(r'%\([^\)]+\)', self.success_url): + if PERCENT_PLACEHOLDER_REGEX.search(self.success_url): warnings.warn( "%()s placeholder style in success_url is deprecated. " "Please replace them by the {} Python format syntax.", From 5dff3513cc1bb998abe60f52269790268a74220c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Ehrlich?= Date: Mon, 26 Jan 2015 11:09:50 +0100 Subject: [PATCH 0047/1125] [1.8.x] Fixed #24220 - Allowed lazy objects for success_url Backport of 511be35779a98427387d9aa4abacce01dedd7272 from master --- django/views/generic/edit.py | 4 ++++ tests/generic_views/views.py | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/django/views/generic/edit.py b/django/views/generic/edit.py index 6f514c3a703e..a4329a25b04f 100644 --- a/django/views/generic/edit.py +++ b/django/views/generic/edit.py @@ -165,6 +165,8 @@ def get_success_url(self): Returns the supplied URL. """ if self.success_url: + # force_text can be removed with deprecation warning + self.success_url = force_text(self.success_url) if PERCENT_PLACEHOLDER_REGEX.search(self.success_url): warnings.warn( "%()s placeholder style in success_url is deprecated. " @@ -299,6 +301,8 @@ def post(self, request, *args, **kwargs): def get_success_url(self): if self.success_url: + # force_text can be removed with deprecation warning + self.success_url = force_text(self.success_url) if PERCENT_PLACEHOLDER_REGEX.search(self.success_url): warnings.warn( "%()s placeholder style in success_url is deprecated. " diff --git a/tests/generic_views/views.py b/tests/generic_views/views.py index 27ae5b2ca9de..fe7fb88876a0 100644 --- a/tests/generic_views/views.py +++ b/tests/generic_views/views.py @@ -172,9 +172,7 @@ class SpecializedAuthorDelete(generic.DeleteView): queryset = Author.objects.all() template_name = 'generic_views/confirm_delete.html' context_object_name = 'thingy' - - def get_success_url(self): - return reverse('authors_list') + success_url = reverse_lazy('authors_list') class BookConfig(object): From e56810e839db2beddc8a7b6e917158855ef381dc Mon Sep 17 00:00:00 2001 From: Josh Smeaton Date: Sat, 17 Jan 2015 16:03:46 +1100 Subject: [PATCH 0048/1125] [1.8.x] Fixed #24154 -- Backends can now check support for expressions Backport of 8196e4bdf498acb05e6680c81f9d7bf700f4295c from master --- .../gis/db/backends/base/operations.py | 8 +++---- django/contrib/gis/db/models/aggregates.py | 3 +++ django/db/backends/base/features.py | 8 +++---- django/db/backends/base/operations.py | 14 +++++++---- django/db/backends/sqlite3/features.py | 2 +- django/db/backends/sqlite3/operations.py | 24 ++++++++++++------- django/db/models/aggregates.py | 11 --------- django/db/models/expressions.py | 15 ++++++------ django/db/models/sql/query.py | 5 ---- django/db/models/sql/where.py | 11 --------- tests/backends/tests.py | 13 +++++++--- 11 files changed, 52 insertions(+), 62 deletions(-) diff --git a/django/contrib/gis/db/backends/base/operations.py b/django/contrib/gis/db/backends/base/operations.py index dc2fad025b21..4759c86b89a5 100644 --- a/django/contrib/gis/db/backends/base/operations.py +++ b/django/contrib/gis/db/backends/base/operations.py @@ -98,12 +98,12 @@ def get_geom_placeholder(self, f, value, compiler): """ raise NotImplementedError('subclasses of BaseSpatialOperations must provide a geo_db_placeholder() method') - def check_aggregate_support(self, aggregate): - if isinstance(aggregate, self.disallowed_aggregates): + def check_expression_support(self, expression): + if isinstance(expression, self.disallowed_aggregates): raise NotImplementedError( - "%s spatial aggregation is not supported by this database backend." % aggregate.name + "%s spatial aggregation is not supported by this database backend." % expression.name ) - super(BaseSpatialOperations, self).check_aggregate_support(aggregate) + super(BaseSpatialOperations, self).check_expression_support(expression) def spatial_aggregate_name(self, agg_name): raise NotImplementedError('Aggregate support not implemented for this spatial backend.') diff --git a/django/contrib/gis/db/models/aggregates.py b/django/contrib/gis/db/models/aggregates.py index 0ec842de0fd3..42198d9287c5 100644 --- a/django/contrib/gis/db/models/aggregates.py +++ b/django/contrib/gis/db/models/aggregates.py @@ -9,6 +9,9 @@ class GeoAggregate(Aggregate): is_extent = False def as_sql(self, compiler, connection): + # this will be called again in parent, but it's needed now - before + # we get the spatial_aggregate_name + connection.ops.check_expression_support(self) self.function = connection.ops.spatial_aggregate_name(self.name) return super(GeoAggregate, self).as_sql(compiler, connection) diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py index 0f6ee0efe36b..4b4d5c6d759e 100644 --- a/django/db/backends/base/features.py +++ b/django/db/backends/base/features.py @@ -1,3 +1,5 @@ +from django.db.models.aggregates import StdDev +from django.db.models.expressions import Value from django.db.utils import ProgrammingError from django.utils.functional import cached_property @@ -226,12 +228,8 @@ def supports_transactions(self): @cached_property def supports_stddev(self): """Confirm support for STDDEV and related stats functions.""" - class StdDevPop(object): - contains_aggregate = True - sql_function = 'STDDEV_POP' - try: - self.connection.ops.check_aggregate_support(StdDevPop()) + self.connection.ops.check_expression_support(StdDev(Value(1))) return True except NotImplementedError: return False diff --git a/django/db/backends/base/operations.py b/django/db/backends/base/operations.py index 24bcbb3d08eb..f535a8792dad 100644 --- a/django/db/backends/base/operations.py +++ b/django/db/backends/base/operations.py @@ -526,12 +526,16 @@ def convert_durationfield_value(self, value, expression, context): return value def check_aggregate_support(self, aggregate_func): - """Check that the backend supports the provided aggregate + return self.check_expression_support(aggregate_func) - This is used on specific backends to rule out known aggregates - that are known to have faulty implementations. If the named - aggregate function has a known problem, the backend should - raise NotImplementedError. + def check_expression_support(self, expression): + """ + Check that the backend supports the provided expression. + + This is used on specific backends to rule out known expressions + that have problematic or nonexistent implementations. If the + expression has a known problem, the backend should raise + NotImplementedError. """ pass diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py index fa5a002603ae..ee864691779f 100644 --- a/django/db/backends/sqlite3/features.py +++ b/django/db/backends/sqlite3/features.py @@ -60,7 +60,7 @@ def supports_stddev(self): """Confirm support for STDDEV and related stats functions SQLite supports STDDEV as an extension package; so - connection.ops.check_aggregate_support() can't unilaterally + connection.ops.check_expression_support() can't unilaterally rule out support for STDDEV. We need to manually check whether the call works. """ diff --git a/django/db/backends/sqlite3/operations.py b/django/db/backends/sqlite3/operations.py index ad05f88585f3..cd29092cd70e 100644 --- a/django/db/backends/sqlite3/operations.py +++ b/django/db/backends/sqlite3/operations.py @@ -4,7 +4,7 @@ import uuid from django.conf import settings -from django.core.exceptions import ImproperlyConfigured +from django.core.exceptions import ImproperlyConfigured, FieldError from django.db import utils from django.db.backends import utils as backend_utils from django.db.backends.base.operations import BaseDatabaseOperations @@ -33,15 +33,21 @@ def bulk_batch_size(self, fields, objs): limit = 999 if len(fields) > 1 else 500 return (limit // len(fields)) if len(fields) > 0 else len(objs) - def check_aggregate_support(self, aggregate): + def check_expression_support(self, expression): bad_fields = (fields.DateField, fields.DateTimeField, fields.TimeField) - bad_aggregates = (aggregates.Sum, aggregates.Avg, - aggregates.Variance, aggregates.StdDev) - if aggregate.refs_field(bad_aggregates, bad_fields): - raise NotImplementedError( - 'You cannot use Sum, Avg, StdDev and Variance aggregations ' - 'on date/time fields in sqlite3 ' - 'since date/time is saved as text.') + bad_aggregates = (aggregates.Sum, aggregates.Avg, aggregates.Variance, aggregates.StdDev) + if isinstance(expression, bad_aggregates): + try: + output_field = expression.input_field.output_field + if isinstance(output_field, bad_fields): + raise NotImplementedError( + 'You cannot use Sum, Avg, StdDev and Variance aggregations ' + 'on date/time fields in sqlite3 ' + 'since date/time is saved as text.') + except FieldError: + # not every sub-expression has an output_field which is fine to + # ignore + pass def date_extract_sql(self, lookup_type, field_name): # sqlite doesn't support extract, so we fake it with the user-defined diff --git a/django/db/models/aggregates.py b/django/db/models/aggregates.py index 06220123ca2e..668f79f622f6 100644 --- a/django/db/models/aggregates.py +++ b/django/db/models/aggregates.py @@ -25,17 +25,6 @@ def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize c._patch_aggregate(query) # backward-compatibility support return c - def refs_field(self, aggregate_types, field_types): - try: - return (isinstance(self, aggregate_types) and - isinstance(self.input_field._output_field_or_none, field_types)) - except FieldError: - # Sometimes we don't know the input_field's output type (for example, - # doing Sum(F('datetimefield') + F('datefield'), output_type=DateTimeField()) - # is OK, but the Expression(F('datetimefield') + F('datefield')) doesn't - # have any output field. - return False - @property def input_field(self): return self.source_expressions[0] diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 97a2a9071d05..fb094fd4ef7a 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -297,14 +297,6 @@ def refs_aggregate(self, existing_aggregates): return agg, lookup return False, () - def refs_field(self, aggregate_types, field_types): - """ - Helper method for check_aggregate_support on backends - """ - return any( - node.refs_field(aggregate_types, field_types) - for node in self.get_source_expressions()) - def prepare_database_save(self, field): return self @@ -401,6 +393,7 @@ def compile(self, side, compiler, connection): return compiler.compile(side) def as_sql(self, compiler, connection): + connection.ops.check_expression_support(self) expressions = [] expression_params = [] sql, params = self.compile(self.lhs, compiler, connection) @@ -473,6 +466,7 @@ def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize return c def as_sql(self, compiler, connection, function=None, template=None): + connection.ops.check_expression_support(self) sql_parts = [] params = [] for arg in self.source_expressions: @@ -511,6 +505,7 @@ def __init__(self, value, output_field=None): self.value = value def as_sql(self, compiler, connection): + connection.ops.check_expression_support(self) val = self.value # check _output_field to avoid triggering an exception if self._output_field is not None: @@ -536,6 +531,7 @@ def get_group_by_cols(self): class DurationValue(Value): def as_sql(self, compiler, connection): + connection.ops.check_expression_support(self) if (connection.features.has_native_duration_field and connection.features.driver_supports_timedelta_args): return super(DurationValue, self).as_sql(compiler, connection) @@ -650,6 +646,7 @@ def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize return c def as_sql(self, compiler, connection, template=None): + connection.ops.check_expression_support(self) template_params = {} sql_params = [] condition_sql, condition_params = compiler.compile(self.condition) @@ -715,6 +712,7 @@ def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize return c def as_sql(self, compiler, connection, template=None, extra=None): + connection.ops.check_expression_support(self) if not self.cases: return compiler.compile(self.default) template_params = dict(extra) if extra else {} @@ -851,6 +849,7 @@ def get_source_expressions(self): return [self.expression] def as_sql(self, compiler, connection): + connection.ops.check_expression_support(self) expression_sql, params = compiler.compile(self.expression) placeholders = {'expression': expression_sql} placeholders['ordering'] = 'DESC' if self.descending else 'ASC' diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 9693206b67aa..0d89de2458be 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -230,11 +230,6 @@ def get_compiler(self, using=None, connection=None): raise ValueError("Need either using or connection") if using: connection = connections[using] - - # Check that the compiler will be able to execute the query - for alias, annotation in self.annotation_select.items(): - connection.ops.check_aggregate_support(annotation) - return connection.ops.compiler(self.compiler)(self, connection, using) def get_meta(self): diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index e3766c51d625..6a03210a93e8 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -325,17 +325,6 @@ def _contains_aggregate(cls, obj): def contains_aggregate(self): return self._contains_aggregate(self) - @classmethod - def _refs_field(cls, obj, aggregate_types, field_types): - if not isinstance(obj, tree.Node): - if hasattr(obj.rhs, 'refs_field'): - return obj.rhs.refs_field(aggregate_types, field_types) - return False - return any(cls._refs_field(c, aggregate_types, field_types) for c in obj.children) - - def refs_field(self, aggregate_types, field_types): - return self._refs_field(self, aggregate_types, field_types) - class EmptyWhere(WhereNode): def add(self, data, connector): diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 4a07cd5f2f39..979ebefa01c7 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -128,12 +128,19 @@ def test_aggregation(self): #19360: Raise NotImplementedError when aggregating on date/time fields. """ for aggregate in (Sum, Avg, Variance, StdDev): - self.assertRaises(NotImplementedError, + self.assertRaises( + NotImplementedError, models.Item.objects.all().aggregate, aggregate('time')) - self.assertRaises(NotImplementedError, + self.assertRaises( + NotImplementedError, models.Item.objects.all().aggregate, aggregate('date')) - self.assertRaises(NotImplementedError, + self.assertRaises( + NotImplementedError, models.Item.objects.all().aggregate, aggregate('last_modified')) + self.assertRaises( + NotImplementedError, + models.Item.objects.all().aggregate, + **{'complex': aggregate('last_modified') + aggregate('last_modified')}) @unittest.skipUnless(connection.vendor == 'postgresql', "Test only for PostgreSQL") From 645fe136c4354ce313be5a150864ad046c227a22 Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Tue, 27 Jan 2015 14:39:22 +0100 Subject: [PATCH 0049/1125] [1.8.x] Refs #24104 -- Added missing release notes Forwardport of 3d4a826174b7a411a03be39725e60c940944a7fe from stable/1.7.x --- docs/releases/1.7.4.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/releases/1.7.4.txt b/docs/releases/1.7.4.txt index 0d53459068e9..3e81a924967f 100644 --- a/docs/releases/1.7.4.txt +++ b/docs/releases/1.7.4.txt @@ -20,3 +20,7 @@ Bugfixes * Prevented the ``static.serve`` view from producing ``ResourceWarning``\s in certain circumstances (security fix regression, :ticket:`24193`). + +* Fixed schema check for ManyToManyField to look for internal type instead + of checking class instance, so you can write custom m2m-like fields with the + same behavior. (:ticket:`24104`). From 617121cd4a5e0775b61eab630eacbbffc5e8dbe5 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 27 Jan 2015 11:48:04 -0500 Subject: [PATCH 0050/1125] [1.8.x] Added 1.4.19 release notes. Backport of 6f8418089c5e81d12718187da2140394ed30da43 from master --- docs/releases/1.4.19.txt | 16 ++++++++++++++++ docs/releases/1.7.4.txt | 2 +- docs/releases/index.txt | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 docs/releases/1.4.19.txt diff --git a/docs/releases/1.4.19.txt b/docs/releases/1.4.19.txt new file mode 100644 index 000000000000..992fc6c74021 --- /dev/null +++ b/docs/releases/1.4.19.txt @@ -0,0 +1,16 @@ +=========================== +Django 1.4.19 release notes +=========================== + +*January 27, 2015* + +Django 1.4.19 fixes a regression in the 1.4.18 security release. + +Bugfixes +======== + +* ``GZipMiddleware`` now supports streaming responses. As part of the 1.4.18 + security release, the ``django.views.static.serve()`` function was altered + to stream the files it serves. Unfortunately, the ``GZipMiddleware`` consumed + the stream prematurely and prevented files from being served properly + (`#24158 `_). diff --git a/docs/releases/1.7.4.txt b/docs/releases/1.7.4.txt index 3e81a924967f..6960a1df5272 100644 --- a/docs/releases/1.7.4.txt +++ b/docs/releases/1.7.4.txt @@ -2,7 +2,7 @@ Django 1.7.4 release notes ========================== -*Under development* +*January 27, 2015* Django 1.7.4 fixes several bugs in 1.7.3. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 29b34d508c65..4d2f2992625b 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -79,6 +79,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.4.19 1.4.18 1.4.17 1.4.16 From 29fa0e3c6663a45cc17163d7b48c3718e1e5a574 Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Tue, 27 Jan 2015 19:42:48 +0100 Subject: [PATCH 0051/1125] [1.8.x] Corrected naming of method and attribute Backport of 335df82a3f13877220712090eb455a32eea87421 from master --- docs/releases/1.8.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index c89c65de5c2a..e1db70164608 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -467,9 +467,9 @@ Migrations * A :ref:`generic mechanism to handle the deprecation of model fields ` was added. -* The :attr:`RunPython.noop ` - and :meth:`RunSQL.noop() ` class - attribute/method were added to ease in making ``RunPython`` and ``RunSQL`` +* The :meth:`RunPython.noop() ` + and :attr:`RunSQL.noop ` class + method/attribute were added to ease in making ``RunPython`` and ``RunSQL`` operations reversible. * The :class:`~django.db.migrations.operations.RunPython` and From 7cc1b4710ea3fbe3a076d14f9265f115a615964a Mon Sep 17 00:00:00 2001 From: Raul Cumplido Date: Sat, 24 Jan 2015 12:14:30 +0000 Subject: [PATCH 0052/1125] [1.8.x] Fixed #24209 -- Prevented crash when parsing malformed RFC 2231 headers Thanks Tom Christie for the report and review. Backport of ac650d02cb from master. --- django/http/multipartparser.py | 3 ++- tests/file_uploads/tests.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py index 07c874e08fed..e1de03f8b189 100644 --- a/django/http/multipartparser.py +++ b/django/http/multipartparser.py @@ -643,7 +643,8 @@ def parse_header(line): # Lang/encoding embedded in the value (like "filename*=UTF-8''file.ext") # http://tools.ietf.org/html/rfc2231#section-4 name = name[:-1] - has_encoding = True + if p.count(b"'") == 2: + has_encoding = True value = p[i + 1:].strip() if has_encoding: encoding, lang, value = value.split(b"'") diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py index 34681122e4c5..610cf1363073 100644 --- a/tests/file_uploads/tests.py +++ b/tests/file_uploads/tests.py @@ -584,3 +584,20 @@ def test_rfc2231_parsing(self): for raw_line, expected_title in test_data: parsed = parse_header(raw_line) self.assertEqual(parsed[1]['title'], expected_title) + + def test_rfc2231_wrong_title(self): + """ + Test wrongly formatted RFC 2231 headers (missing double single quotes). + Parsing should not crash (#24209). + """ + test_data = ( + (b"Content-Type: application/x-stuff; title*='This%20is%20%2A%2A%2Afun%2A%2A%2A", + b"'This%20is%20%2A%2A%2Afun%2A%2A%2A"), + (b"Content-Type: application/x-stuff; title*='foo.html", + b"'foo.html"), + (b"Content-Type: application/x-stuff; title*=bar.html", + b"bar.html"), + ) + for raw_line, expected_title in test_data: + parsed = parse_header(raw_line) + self.assertEqual(parsed[1]['title'], expected_title) From 405351ba2f639a1a0c092ecb6d93f9f7a646686a Mon Sep 17 00:00:00 2001 From: Josh Smeaton Date: Tue, 27 Jan 2015 13:37:43 +1100 Subject: [PATCH 0053/1125] [1.8.x] Refs #24060 -- Added a test demonstrating reverse order isn't mutable Backport of f218a2ff455b5f7391dd38038994f2c5f8b0eca1 from master --- tests/ordering/tests.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/ordering/tests.py b/tests/ordering/tests.py index 60880b71bedf..19d289a2d1f6 100644 --- a/tests/ordering/tests.py +++ b/tests/ordering/tests.py @@ -137,6 +137,28 @@ def test_reversed_ordering(self): attrgetter("headline") ) + def test_reverse_ordering_pure(self): + qs1 = Article.objects.order_by(F('headline').asc()) + qs2 = qs1.reverse() + self.assertQuerysetEqual( + qs1, [ + "Article 1", + "Article 2", + "Article 3", + "Article 4", + ], + attrgetter("headline") + ) + self.assertQuerysetEqual( + qs2, [ + "Article 4", + "Article 3", + "Article 2", + "Article 1", + ], + attrgetter("headline") + ) + def test_extra_ordering(self): """ Ordering can be based on fields included from an 'extra' clause From 6c68e40e6e7f3ba36fa0e629d5724c7f4b279bb8 Mon Sep 17 00:00:00 2001 From: Josh Smeaton Date: Tue, 27 Jan 2015 13:40:32 +1100 Subject: [PATCH 0054/1125] [1.8.x] Refs #14030 -- Added repr methods to all expressions Backport of 7171bf755b0c4be85ddbcc164eaf87164c131021 from master --- django/db/models/aggregates.py | 21 ++++++++++++++ django/db/models/expressions.py | 50 +++++++++++++++++++++++++++++---- tests/expressions/tests.py | 46 +++++++++++++++++++++++++++++- 3 files changed, 111 insertions(+), 6 deletions(-) diff --git a/django/db/models/aggregates.py b/django/db/models/aggregates.py index 668f79f622f6..c1ddc75d4c2e 100644 --- a/django/db/models/aggregates.py +++ b/django/db/models/aggregates.py @@ -94,6 +94,13 @@ def __init__(self, expression, distinct=False, **extra): super(Count, self).__init__( expression, distinct='DISTINCT ' if distinct else '', output_field=IntegerField(), **extra) + def __repr__(self): + return "{}({}, distinct={})".format( + self.__class__.__name__, + self.arg_joiner.join(str(arg) for arg in self.source_expressions), + 'False' if self.extra['distinct'] == '' else 'True', + ) + def convert_value(self, value, connection, context): if value is None: return 0 @@ -117,6 +124,13 @@ def __init__(self, expression, sample=False, **extra): self.function = 'STDDEV_SAMP' if sample else 'STDDEV_POP' super(StdDev, self).__init__(expression, output_field=FloatField(), **extra) + def __repr__(self): + return "{}({}, sample={})".format( + self.__class__.__name__, + self.arg_joiner.join(str(arg) for arg in self.source_expressions), + 'False' if self.function == 'STDDEV_POP' else 'True', + ) + def convert_value(self, value, connection, context): if value is None: return value @@ -135,6 +149,13 @@ def __init__(self, expression, sample=False, **extra): self.function = 'VAR_SAMP' if sample else 'VAR_POP' super(Variance, self).__init__(expression, output_field=FloatField(), **extra) + def __repr__(self): + return "{}({}, sample={})".format( + self.__class__.__name__, + self.arg_joiner.join(str(arg) for arg in self.source_expressions), + 'False' if self.function == 'VAR_POP' else 'True', + ) + def convert_value(self, value, connection, context): if value is None: return value diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index fb094fd4ef7a..9caa5dace438 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -340,6 +340,12 @@ def __init__(self, lhs, connector, rhs, output_field=None): self.lhs = lhs self.rhs = rhs + def __repr__(self): + return "<{}: {}>".format(self.__class__.__name__, self) + + def __str__(self): + return "{} {} {}".format(self.lhs, self.connector, self.rhs) + def get_source_expressions(self): return [self.lhs, self.rhs] @@ -408,7 +414,7 @@ def as_sql(self, compiler, connection): return expression_wrapper % sql, expression_params -class F(CombinableMixin): +class F(Combinable): """ An object capable of resolving references to existing query objects. """ @@ -419,6 +425,9 @@ def __init__(self, name): """ self.name = name + def __repr__(self): + return "{}({})".format(self.__class__.__name__, self.name) + def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False): return query.resolve_ref(self.name, allow_joins, reuse, summarize) @@ -446,6 +455,13 @@ def __init__(self, *expressions, **extra): self.source_expressions = self._parse_expressions(*expressions) self.extra = extra + def __repr__(self): + args = self.arg_joiner.join(str(arg) for arg in self.source_expressions) + extra = ', '.join(str(key) + '=' + str(val) for key, val in self.extra.items()) + if extra: + return "{}({}, {})".format(self.__class__.__name__, args, extra) + return "{}({})".format(self.__class__.__name__, args) + def get_source_expressions(self): return self.source_expressions @@ -504,6 +520,9 @@ def __init__(self, value, output_field=None): super(Value, self).__init__(output_field=output_field) self.value = value + def __repr__(self): + return "{}({})".format(self.__class__.__name__, self.value) + def as_sql(self, compiler, connection): connection.ops.check_expression_support(self) val = self.value @@ -545,6 +564,9 @@ def __init__(self, sql, params, output_field=None): self.sql, self.params = sql, params super(RawSQL, self).__init__(output_field=output_field) + def __repr__(self): + return "{}({}, {})".format(self.__class__.__name__, self.sql, self.params) + def as_sql(self, compiler, connection): return '(%s)' % self.sql, self.params @@ -556,6 +578,9 @@ class Random(ExpressionNode): def __init__(self): super(Random, self).__init__(output_field=fields.FloatField()) + def __repr__(self): + return "Random()" + def as_sql(self, compiler, connection): return connection.ops.random_function_sql(), [] @@ -567,6 +592,10 @@ def __init__(self, alias, target, source=None): super(Col, self).__init__(output_field=source) self.alias, self.target = alias, target + def __repr__(self): + return "{}({}, {})".format( + self.__class__.__name__, self.alias, self.target) + def as_sql(self, compiler, connection): qn = compiler.quote_name_unless_alias return "%s.%s" % (qn(self.alias), qn(self.target.column)), [] @@ -588,8 +617,10 @@ class Ref(ExpressionNode): """ def __init__(self, refs, source): super(Ref, self).__init__() - self.source = source - self.refs = refs + self.refs, self.source = refs, source + + def __repr__(self): + return "{}({}, {})".format(self.__class__.__name__, self.refs, self.source) def get_source_expressions(self): return [self.source] @@ -743,6 +774,9 @@ def __init__(self, lookup, lookup_type): self.col = None self.lookup_type = lookup_type + def __repr__(self): + return "{}({}, {})".format(self.__class__.__name__, self.lookup, self.lookup_type) + def get_source_expressions(self): return [self.col] @@ -792,6 +826,10 @@ def __init__(self, lookup, lookup_type, tzinfo): self.tzname = timezone._get_timezone_name(tzinfo) self.tzinfo = tzinfo + def __repr__(self): + return "{}({}, {}, {})".format( + self.__class__.__name__, self.lookup, self.lookup_type, self.tzinfo) + def get_source_expressions(self): return [self.col] @@ -833,8 +871,6 @@ def convert_value(self, value, connection, context): class OrderBy(BaseExpression): template = '%(expression)s %(ordering)s' - descending_template = 'DESC' - ascending_template = 'ASC' def __init__(self, expression, descending=False): self.descending = descending @@ -842,6 +878,10 @@ def __init__(self, expression, descending=False): raise ValueError('expression must be an expression type') self.expression = expression + def __repr__(self): + return "{}({}, descending={})".format( + self.__class__.__name__, self.expression, self.descending) + def set_source_expressions(self, exprs): self.expression = exprs[0] diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py index 3c508b852027..f7e8cae85644 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -6,10 +6,17 @@ from django.core.exceptions import FieldError from django.db import connection, transaction, DatabaseError -from django.db.models import F, Value, TimeField, UUIDField +from django.db.models import TimeField, UUIDField +from django.db.models.aggregates import Avg, Count, Max, Min, StdDev, Sum, Variance +from django.db.models.expressions import ( + Case, Col, Date, DateTime, F, Func, OrderBy, + Random, RawSQL, Ref, Value, When +) +from django.db.models.functions import Coalesce, Concat, Length, Lower, Substr, Upper from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import Approximate from django.utils import six +from django.utils.timezone import utc from .models import Company, Employee, Number, Experiment, Time, UUID @@ -812,3 +819,40 @@ def test_update_UUIDField_using_Value(self): UUID.objects.create() UUID.objects.update(uuid=Value(uuid.UUID('12345678901234567890123456789012'), output_field=UUIDField())) self.assertEqual(UUID.objects.get().uuid, uuid.UUID('12345678901234567890123456789012')) + + +class ReprTests(TestCase): + + def test_expressions(self): + self.assertEqual( + repr(Case(When(a=1))), + " THEN Value(None), ELSE Value(None)>" + ) + self.assertEqual(repr(Col('alias', 'field')), "Col(alias, field)") + self.assertEqual(repr(Date('published', 'exact')), "Date(published, exact)") + self.assertEqual(repr(DateTime('published', 'exact', utc)), "DateTime(published, exact, UTC)") + self.assertEqual(repr(F('published')), "F(published)") + self.assertEqual(repr(F('cost') + F('tax')), "") + self.assertEqual(repr(Func('published', function='TO_CHAR')), "Func(F(published), function=TO_CHAR)") + self.assertEqual(repr(OrderBy(Value(1))), 'OrderBy(Value(1), descending=False)') + self.assertEqual(repr(Random()), "Random()") + self.assertEqual(repr(RawSQL('table.col', [])), "RawSQL(table.col, [])") + self.assertEqual(repr(Ref('sum_cost', Sum('cost'))), "Ref(sum_cost, Sum(F(cost)))") + self.assertEqual(repr(Value(1)), "Value(1)") + + def test_functions(self): + self.assertEqual(repr(Coalesce('a', 'b')), "Coalesce(F(a), F(b))") + self.assertEqual(repr(Concat('a', 'b')), "Concat(ConcatPair(F(a), F(b)))") + self.assertEqual(repr(Length('a')), "Length(F(a))") + self.assertEqual(repr(Lower('a')), "Lower(F(a))") + self.assertEqual(repr(Substr('a', 1, 3)), "Substr(F(a), Value(1), Value(3))") + self.assertEqual(repr(Upper('a')), "Upper(F(a))") + + def test_aggregates(self): + self.assertEqual(repr(Avg('a')), "Avg(F(a))") + self.assertEqual(repr(Count('a')), "Count(F(a), distinct=False)") + self.assertEqual(repr(Max('a')), "Max(F(a))") + self.assertEqual(repr(Min('a')), "Min(F(a))") + self.assertEqual(repr(StdDev('a')), "StdDev(F(a), sample=False)") + self.assertEqual(repr(Sum('a')), "Sum(F(a))") + self.assertEqual(repr(Variance('a', sample=True)), "Variance(F(a), sample=True)") From f858b51ee31314225e82812f58f353721f06101a Mon Sep 17 00:00:00 2001 From: Josh Smeaton Date: Tue, 27 Jan 2015 13:42:48 +1100 Subject: [PATCH 0055/1125] [1.8.x] Refs #14030 -- Renamed CombinableMixin to Combinable Removed unused method and updated docstrings. Backport of 14d0bd67d4bcf55f8a0a2b01433571a8b714121f from master --- django/db/models/expressions.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 9caa5dace438..87a08ecc3ba6 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -11,7 +11,7 @@ from django.utils.functional import cached_property -class CombinableMixin(object): +class Combinable(object): """ Provides the ability to combine one or two objects with some connector. For example F('foo') + F('bar'). @@ -184,6 +184,7 @@ def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize in this query * reuse: a set of reusable joins for multijoins * summarize: a terminal aggregate clause + * for_save: whether this expression about to be used in a save or update Returns: an ExpressionNode to be added to the query. """ @@ -297,9 +298,6 @@ def refs_aggregate(self, existing_aggregates): return agg, lookup return False, () - def prepare_database_save(self, field): - return self - def get_group_by_cols(self): if not self.contains_aggregate: return [self] @@ -325,7 +323,7 @@ def reverse_ordering(self): return self -class ExpressionNode(BaseExpression, CombinableMixin): +class ExpressionNode(BaseExpression, Combinable): """ An expression that can be combined with other expressions. """ From df7cca6dff8c18cd891fdd73529a14aa2632e4e2 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 28 Jan 2015 06:38:31 -0500 Subject: [PATCH 0056/1125] [1.8.x] Added stub 1.7.5 release notes. Backport of ac6033d8835ac54c1222801f6aeb47f9997b517a from master --- docs/releases/1.7.5.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.7.5.txt diff --git a/docs/releases/1.7.5.txt b/docs/releases/1.7.5.txt new file mode 100644 index 000000000000..ba81a64b9a92 --- /dev/null +++ b/docs/releases/1.7.5.txt @@ -0,0 +1,12 @@ +========================== +Django 1.7.5 release notes +========================== + +*Under development* + +Django 1.7.5 fixes several bugs in 1.7.4. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 4d2f2992625b..c298c3ac2ea2 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.7.5 1.7.4 1.7.3 1.7.2 From 6002393a976c457ec1a08e59cc03ce732032371d Mon Sep 17 00:00:00 2001 From: Emin Mastizada Date: Mon, 26 Jan 2015 03:08:00 +0200 Subject: [PATCH 0057/1125] [1.8.x] Updated Azerbaijani language name. Backport of 0f3ea8c0bc9c7f7f5e448b0b2137bc6351f5eae3 from master --- django/conf/locale/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/conf/locale/__init__.py b/django/conf/locale/__init__.py index f2affb22482f..352797289c6c 100644 --- a/django/conf/locale/__init__.py +++ b/django/conf/locale/__init__.py @@ -33,7 +33,7 @@ 'bidi': True, 'code': 'az', 'name': 'Azerbaijani', - 'name_local': 'azərbaycan dili', + 'name_local': 'Azərbaycanca', }, 'be': { 'bidi': False, From 590ee3ed167ef7d1dad681efd6d9a4dc0767f02a Mon Sep 17 00:00:00 2001 From: Reza Mohammadi Date: Sat, 17 Jan 2015 17:23:02 +0330 Subject: [PATCH 0058/1125] [1.8.x] Fixed Persian locale FIRST_DAY_OF_WEEK & DECIMAL/THOUSAND_SEPARATORs. Reference: http://lh.2xlibre.net/locale/fa_IR/ Backport of f1ff9407c94c4574d100efc3d224c1f79e2fb53d from master --- django/conf/locale/fa/formats.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django/conf/locale/fa/formats.py b/django/conf/locale/fa/formats.py index f70cb8387afc..c1678b81cddf 100644 --- a/django/conf/locale/fa/formats.py +++ b/django/conf/locale/fa/formats.py @@ -12,13 +12,13 @@ MONTH_DAY_FORMAT = 'j F' SHORT_DATE_FORMAT = 'Y/n/j' SHORT_DATETIME_FORMAT = 'Y/n/j،‏ G:i' -# FIRST_DAY_OF_WEEK = +FIRST_DAY_OF_WEEK = 6 # The *_INPUT_FORMATS strings use the Python strftime format syntax, # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # DATE_INPUT_FORMATS = # TIME_INPUT_FORMATS = # DATETIME_INPUT_FORMATS = -DECIMAL_SEPARATOR = ',' -THOUSAND_SEPARATOR = '.' +DECIMAL_SEPARATOR = '.' +THOUSAND_SEPARATOR = ',' # NUMBER_GROUPING = From d0c343372f62bbcccba844350353220af114f10b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 29 Jan 2015 12:53:57 -0500 Subject: [PATCH 0059/1125] [1.8.x] Removed ForeignObjectRel.get_lookup_constraint() [unused]. Backport of f60973111806100d284d41c12206c04740063549 from master --- django/db/models/fields/related.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index e6ecd98537f1..bcf7dd7a17f0 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1373,11 +1373,6 @@ def set_field_name(self): # example custom multicolumn joins currently have no remote field). self.field_name = None - def get_lookup_constraint(self, constraint_class, alias, targets, sources, lookup_type, - raw_value): - return self.field.get_lookup_constraint(constraint_class, alias, targets, sources, - lookup_type, raw_value) - def get_accessor_name(self, model=None): # This method encapsulates the logic that decides what name to give an # accessor descriptor that retrieves related many-to-one or From 7b92acea7055c6b39207a813b52e683a3672e467 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 28 Jan 2015 14:05:47 +0000 Subject: [PATCH 0060/1125] [1.8.x] Fixed #24223 -- Prevented a session test from leaking. Backport of 55c76f4e3bab74c8544b72d11a99e94a1c2cfbce from master --- django/contrib/sessions/tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 24d7ac7367c9..f39d46a8f6d8 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -513,7 +513,7 @@ def test_non_default_cache(self): self.assertNotEqual(caches['sessions'].get(self.session.cache_key), None) -class SessionMiddlewareTests(unittest.TestCase): +class SessionMiddlewareTests(TestCase): @override_settings(SESSION_COOKIE_SECURE=True) def test_secure_session_cookie(self): @@ -605,7 +605,8 @@ def test_session_delete_on_end(self): ) -class CookieSessionTests(SessionTestsMixin, TestCase): +# Don't need DB flushing for these tests, so can use unittest.TestCase as base class +class CookieSessionTests(SessionTestsMixin, unittest.TestCase): backend = CookieSession From a301061f88268255fc2660ca993a84b8bf12f9d2 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Thu, 22 Jan 2015 01:43:49 -0500 Subject: [PATCH 0061/1125] [1.8.x] Fixed #23940 -- Allowed model fields to be named `exact`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An explicit `__exact` lookup in the related managers filters was interpreted as a reference to a foreign `exact` field. Thanks to Trac alias zhiyajun11 for the report, Josh for the investigation, Loïc for the test name and Tim for the review. Backport of eb4cdfbdd64a95b303eaaa40a070521aa58362fd from master --- django/db/models/fields/related.py | 2 +- tests/managers_regress/models.py | 5 +++-- tests/managers_regress/tests.py | 8 ++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index bcf7dd7a17f0..8acae08efb8f 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -681,7 +681,7 @@ class RelatedManager(superclass): def __init__(self, instance): super(RelatedManager, self).__init__() self.instance = instance - self.core_filters = {'%s__exact' % rel_field.name: instance} + self.core_filters = {rel_field.name: instance} self.model = rel_model def __call__(self, **kwargs): diff --git a/tests/managers_regress/models.py b/tests/managers_regress/models.py index c8869f1e267c..99494448bd5b 100644 --- a/tests/managers_regress/models.py +++ b/tests/managers_regress/models.py @@ -129,6 +129,7 @@ class Child7(Parent): @python_2_unicode_compatible class RelatedModel(models.Model): test_gfk = GenericRelation('RelationModel', content_type_field='gfk_ctype', object_id_field='gfk_id') + exact = models.NullBooleanField() def __str__(self): return force_text(self.pk) @@ -140,8 +141,8 @@ class RelationModel(models.Model): m2m = models.ManyToManyField(RelatedModel, related_name='test_m2m') - gfk_ctype = models.ForeignKey(ContentType) - gfk_id = models.IntegerField() + gfk_ctype = models.ForeignKey(ContentType, null=True) + gfk_id = models.IntegerField(null=True) gfk = GenericForeignKey(ct_field='gfk_ctype', fk_field='gfk_id') def __str__(self): diff --git a/tests/managers_regress/tests.py b/tests/managers_regress/tests.py index 0d24873a102c..af507f0b7573 100644 --- a/tests/managers_regress/tests.py +++ b/tests/managers_regress/tests.py @@ -201,3 +201,11 @@ def test_regress_3871(self): t.render(Context({'related': related})), ''.join([force_text(relation.pk)] * 3), ) + + def test_field_can_be_called_exact(self): + # Make sure related managers core filters don't include an + # explicit `__exact` lookup that could be interpreted as a + # reference to a foreign `exact` field. refs #23940. + related = RelatedModel.objects.create(exact=False) + relation = related.test_fk.create() + self.assertEqual(related.test_fk.get(), relation) From df68751134531462f005a75e7342d46540033b88 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 29 Jan 2015 07:20:56 -0500 Subject: [PATCH 0062/1125] [1.8.x] Fixed #24164 -- Fixed Oracle GIS limited aggregation test failure. Backport of 29c0073335c7f7cdc482866e093e5e42a42625e5 from master --- django/db/backends/oracle/compiler.py | 17 +++++++++++------ django/db/models/sql/compiler.py | 12 +++++++----- django/db/models/sql/subqueries.py | 5 ++++- docs/releases/1.8.txt | 3 +++ 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/django/db/backends/oracle/compiler.py b/django/db/backends/oracle/compiler.py index cfc8a08c1bf5..1044a5feb76c 100644 --- a/django/db/backends/oracle/compiler.py +++ b/django/db/backends/oracle/compiler.py @@ -2,7 +2,7 @@ class SQLCompiler(compiler.SQLCompiler): - def as_sql(self, with_limits=True, with_col_aliases=False): + def as_sql(self, with_limits=True, with_col_aliases=False, subquery=False): """ Creates the SQL for this query. Returns the SQL string and list of parameters. This is overridden from the original Query class @@ -20,12 +20,17 @@ def as_sql(self, with_limits=True, with_col_aliases=False): do_offset = with_limits and (self.query.high_mark is not None or self.query.low_mark) if not do_offset: - sql, params = super(SQLCompiler, self).as_sql(with_limits=False, - with_col_aliases=with_col_aliases) + sql, params = super(SQLCompiler, self).as_sql( + with_limits=False, + with_col_aliases=with_col_aliases, + subquery=subquery, + ) else: - sql, params = super(SQLCompiler, self).as_sql(with_limits=False, - with_col_aliases=True) - + sql, params = super(SQLCompiler, self).as_sql( + with_limits=False, + with_col_aliases=True, + subquery=subquery, + ) # Wrap the base query in an outer SELECT * with boundaries on # the "_RN" column. This is the canonical way to emulate LIMIT # and OFFSET on Oracle. diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 7944e35d8abf..5b647888d132 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -30,6 +30,7 @@ def __init__(self, query, connection, using): self.annotation_col_map = None self.klass_info = None self.ordering_parts = re.compile(r'(.*)\s(ASC|DESC)(.*)') + self.subquery = False def setup_query(self): if all(self.query.alias_refcount[a] == 0 for a in self.query.tables): @@ -342,11 +343,11 @@ def compile(self, node, select_format=False): sql, params = vendor_impl(self, self.connection) else: sql, params = node.as_sql(self, self.connection) - if select_format: + if select_format and not self.subquery: return node.output_field.select_format(self, sql, params) return sql, params - def as_sql(self, with_limits=True, with_col_aliases=False): + def as_sql(self, with_limits=True, with_col_aliases=False, subquery=False): """ Creates the SQL for this query. Returns the SQL string and list of parameters. @@ -359,6 +360,7 @@ def as_sql(self, with_limits=True, with_col_aliases=False): # However we do not want to get rid of stuff done in pre_sql_setup(), # as the pre_sql_setup will modify query state in a way that forbids # another run of it. + self.subquery = subquery refcounts_before = self.query.alias_refcount.copy() try: extra_select, order_by, group_by = self.pre_sql_setup() @@ -1115,9 +1117,9 @@ def as_sql(self): raise EmptyResultSet sql, params = [], [] for annotation in self.query.annotation_select.values(): - agg_sql, agg_params = self.compile(annotation) - sql.append(agg_sql) - params.extend(agg_params) + ann_sql, ann_params = self.compile(annotation, select_format=True) + sql.append(ann_sql) + params.extend(ann_params) self.col_count = len(self.query.annotation_select) sql = ', '.join(sql) params = tuple(params) diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 35ce71311dd5..be7438b2a73a 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -209,4 +209,7 @@ class AggregateQuery(Query): compiler = 'SQLAggregateCompiler' def add_subquery(self, query, using): - self.subquery, self.sub_params = query.get_compiler(using).as_sql(with_col_aliases=True) + self.subquery, self.sub_params = query.get_compiler(using).as_sql( + with_col_aliases=True, + subquery=True, + ) diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index e1db70164608..745409d52568 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -937,6 +937,9 @@ those writing third-party backends in updating their code: ``data_type_check_constraints`` attributes have moved from the ``DatabaseCreation`` class to ``DatabaseWrapper``. +* The ``SQLCompiler.as_sql()`` method now takes a ``subquery`` parameter + (:ticket:`24164`). + :mod:`django.contrib.admin` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 3d3c7a62c865b9a993e73a0a367134f0416283c4 Mon Sep 17 00:00:00 2001 From: Andrei Kulakov Date: Thu, 29 Jan 2015 16:57:03 -0500 Subject: [PATCH 0063/1125] [1.8.x] Updated recommendation for testing keyword arg in custom fields. Backport of dbabf43920bfd99f0e720c7c20228c17128a2af8 from master --- docs/topics/migrations.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/migrations.txt b/docs/topics/migrations.txt index 1220b4b41f47..b0bc13b32a1e 100644 --- a/docs/topics/migrations.txt +++ b/docs/topics/migrations.txt @@ -284,7 +284,7 @@ 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 modified ``__init__`` method with the old signature. So if you need a new argument, please create a keyword argument and add something like -``assert kwargs.get('argument_name') is not None`` in the constructor. +``assert 'argument_name' in kwargs`` in the constructor. .. _using-managers-in-migrations: From 7060ef71581c740bcc28ed405225537a411c36b5 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 30 Jan 2015 11:23:20 -0500 Subject: [PATCH 0064/1125] [1.8.x] Reverted "Fixed #6785 -- Made QuerySet.get() fetch a limited number of rows." This reverts commit da79ccca1d34f427952cce4555e598a700adb8de. This optimized the unsuccessful case at the expense of the successful one. Backport of 293fd5da5b8c7b79bd34ef793ab45c1bb8ac69ea from master --- django/db/models/query.py | 16 ++++------------ tests/basic/tests.py | 26 +------------------------- 2 files changed, 5 insertions(+), 37 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 2a1dbf94724c..28936949f01b 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -23,10 +23,6 @@ from django.utils import timezone from django.utils.version import get_version -# The maximum number (one less than the max to be precise) of results to fetch -# in a get() query -MAX_GET_RESULTS = 20 - # The maximum number of items to display in a QuerySet.__repr__ REPR_OUTPUT_SIZE = 20 @@ -326,21 +322,17 @@ def get(self, *args, **kwargs): clone = self.filter(*args, **kwargs) if self.query.can_filter(): clone = clone.order_by() - if (not clone.query.select_for_update or - connections[self.db].features.supports_select_for_update_with_limit): - clone = clone[:MAX_GET_RESULTS + 1] num = len(clone) if num == 1: return clone._result_cache[0] if not num: raise self.model.DoesNotExist( "%s matching query does not exist." % - self.model._meta.object_name) - raise self.model.MultipleObjectsReturned( - "get() returned more than one %s -- it returned %s!" % ( - self.model._meta.object_name, - num if num <= MAX_GET_RESULTS else 'more than %s' % MAX_GET_RESULTS + self.model._meta.object_name ) + raise self.model.MultipleObjectsReturned( + "get() returned more than one %s -- it returned %s!" % + (self.model._meta.object_name, num) ) def create(self, **kwargs): diff --git a/tests/basic/tests.py b/tests/basic/tests.py index 72fdbaa8d828..1320c22349e4 100644 --- a/tests/basic/tests.py +++ b/tests/basic/tests.py @@ -10,7 +10,7 @@ from django.db.models.fields import Field from django.db.models.fields.related import ForeignObjectRel from django.db.models.manager import BaseManager -from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet, MAX_GET_RESULTS +from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet from django.test import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature from django.utils import six from django.utils.translation import ugettext_lazy @@ -178,30 +178,6 @@ def test_not_equal_and_equal_operators_behave_as_expected_on_instances(self): self.assertNotEqual(Article.objects.get(id__exact=a1.id), Article.objects.get(id__exact=a2.id)) - def test_multiple_objects_max_num_fetched(self): - """ - #6785 - get() should fetch a limited number of results. - """ - Article.objects.bulk_create( - Article(headline='Area %s' % i, pub_date=datetime(2005, 7, 28)) - for i in range(MAX_GET_RESULTS) - ) - six.assertRaisesRegex( - self, - MultipleObjectsReturned, - "get\(\) returned more than one Article -- it returned %d!" % MAX_GET_RESULTS, - Article.objects.get, - headline__startswith='Area', - ) - Article.objects.create(headline='Area %s' % MAX_GET_RESULTS, pub_date=datetime(2005, 7, 28)) - six.assertRaisesRegex( - self, - MultipleObjectsReturned, - "get\(\) returned more than one Article -- it returned more than %d!" % MAX_GET_RESULTS, - Article.objects.get, - headline__startswith='Area', - ) - @skipUnlessDBFeature('supports_microsecond_precision') def test_microsecond_precision(self): # In PostgreSQL, microsecond-level precision is available. From c77dd64402e64e3e53b49634175f1e010a480b67 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 17 Jan 2015 00:17:54 +0100 Subject: [PATCH 0065/1125] [1.8.x] Fixed #14483 -- Allowed using subqueries with GIS lookups Backport of a0b5f15ea5f from master. --- django/contrib/gis/db/models/fields.py | 7 ++++--- django/contrib/gis/db/models/lookups.py | 3 +++ django/contrib/gis/tests/geoapp/tests.py | 15 +++++++++++++++ django/db/models/sql/compiler.py | 2 +- docs/releases/1.8.txt | 3 +++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py index 927118c0ac0b..5e70697a2b86 100644 --- a/django/contrib/gis/db/models/fields.py +++ b/django/contrib/gis/db/models/fields.py @@ -293,10 +293,11 @@ def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): (lookup_type, self.__class__.__name__)) def get_prep_lookup(self, lookup_type, value): - if lookup_type == 'isnull': - return bool(value) - else: + if lookup_type == 'contains': + # 'contains' name might conflict with the "normal" contains lookup, + # for which the value is not prepared, but left as-is. return self.get_prep_value(value) + return super(GeometryField, self).get_prep_lookup(lookup_type, value) def get_db_prep_save(self, value, connection): "Prepares the value for saving in the database." diff --git a/django/contrib/gis/db/models/lookups.py b/django/contrib/gis/db/models/lookups.py index 17ed1a95c8c4..eb64eff6c7ed 100644 --- a/django/contrib/gis/db/models/lookups.py +++ b/django/contrib/gis/db/models/lookups.py @@ -66,6 +66,9 @@ def get_db_prep_lookup(self, value, connection): def process_rhs(self, compiler, connection): rhs, rhs_params = super(GISLookup, self).process_rhs(compiler, connection) + if hasattr(self.rhs, '_as_sql'): + # If rhs is some QuerySet, don't touch it + return rhs, rhs_params geom = self.rhs if isinstance(self.rhs, Col): diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index 8a885b28276a..7c999a20d0aa 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -890,6 +890,21 @@ def test_unionagg(self): self.assertIsNone(qs.unionagg(field_name='point')) self.assertIsNone(qs.aggregate(Union('point'))['point__union']) + def test_within_subquery(self): + """ + Test that using a queryset inside a geo lookup is working (using a subquery) + (#14483). + """ + tex_cities = City.objects.filter( + point__within=Country.objects.filter(name='Texas').values('mpoly')).order_by('name') + expected = ['Dallas', 'Houston'] + if not connection.features.supports_real_shape_operations: + expected.append('Oklahoma City') + self.assertEqual( + list(tex_cities.values_list('name', flat=True)), + expected + ) + def test_non_concrete_field(self): NonConcreteModel.objects.create(point=Point(0, 0), name='name') list(NonConcreteModel.objects.all()) diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 5b647888d132..8f3af37031f4 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -466,7 +466,7 @@ def as_nested_sql(self): if obj.low_mark == 0 and obj.high_mark is None and not self.query.distinct_fields: # If there is no slicing in use, then we can safely drop all ordering obj.clear_ordering(True) - return obj.get_compiler(connection=self.connection).as_sql() + return obj.get_compiler(connection=self.connection).as_sql(subquery=True) def get_default_columns(self, start_alias=None, opts=None, from_parent=None): """ diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 745409d52568..44e27241f456 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -216,6 +216,9 @@ Minor features * A new :doc:`GeoJSON serializer ` is now available. +* It is now allowed to include a subquery as a geographic lookup argument, for + example ``City.objects.filter(point__within=Country.objects.filter(continent='Africa').values('mpoly'))``. + * The Spatialite backend now supports ``Collect`` and ``Extent`` aggregates when the database version is 3.0 or later. From 28bb0ad19947514cd275e5692a5424e45919b5be Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 30 Jan 2015 08:20:35 -0500 Subject: [PATCH 0066/1125] [1.8.x] Fixed #24208 -- Documented changes in private model relations. Backport of 888054bff7c5878e026bac2e712d5480f7e295c7 from master --- docs/releases/1.8.txt | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 44e27241f456..2806c21580ed 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -917,6 +917,44 @@ Also private APIs ``django.template.base.compile_string()``, ``django.template.loader.find_template()``, and ``django.template.loader.get_template_from_string()`` were removed. +``model`` attribute on private model relations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In earlier versions of Django, on a model with a reverse foreign key +relationship (for example), ``model._meta.get_all_related_objects()`` returned +the relationship as a ``django.db.models.related.RelatedObject`` with the +``model`` attribute set to the source of the relationship. Now, this method +returns the relationship as ``django.db.models.fields.related.ManyToOneRel`` +(private API ``RelatedObject`` has been removed), and the ``model`` attribute +is set to the target of the relationship instead of the source. The source +model is accessible on the ``related_model`` attribute instead. + +Consider this example from the tutorial in Django 1.8:: + + >>> p = Poll.objects.get(pk=1) + >>> p._meta.get_all_related_objects() + [] + >>> p._meta.get_all_related_objects()[0].model + + >>> p._meta.get_all_related_objects()[0].related_model + + +and compare it to the behavior on older versions:: + + >>> p._meta.get_all_related_objects() + [] + >>> p._meta.get_all_related_objects()[0].model + + +To access the source model, you can use a pattern like this to write code that +will work with both Django 1.8 and older versions:: + + for relation in opts.get_all_related_objects(): + to_model = getattr(relation, 'related_model', relation.model) + +Also note that ``get_all_related_objects()`` is deprecated in 1.8. See the +:ref:`upgrade guide ` for the new API. + Database backend API ~~~~~~~~~~~~~~~~~~~~ From 0fc2f94699a425fe963be255d0da5966e914b6b0 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 30 Jan 2015 21:38:29 -0500 Subject: [PATCH 0067/1125] [1.8.x] Removed PostgreSQL DatabaseWrapper._set_isolation_level(). This method is unused since 8717b0668caf00ec5e81ef5a1e31b4d7c64eee8a. Backport of 64a899dc815f1a070dc7a7c22276e8bb41e46ea6 from master --- django/db/backends/postgresql_psycopg2/base.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 37433c398712..a73d1f27bf06 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -195,13 +195,6 @@ def create_cursor(self): cursor.tzinfo_factory = utc_tzinfo_factory if settings.USE_TZ else None return cursor - def _set_isolation_level(self, isolation_level): - assert isolation_level in range(1, 5) # Use set_autocommit for level = 0 - if self.psycopg2_version >= (2, 4, 2): - self.connection.set_session(isolation_level=isolation_level) - else: - self.connection.set_isolation_level(isolation_level) - def _set_autocommit(self, autocommit): with self.wrap_database_errors: if self.psycopg2_version >= (2, 4, 2): From c9e538b174e777a1ab51c55913ab8c185414a1ef Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 28 Jan 2015 09:55:52 -0500 Subject: [PATCH 0068/1125] [1.8.x] Removed threading fallback imports. Django imports threading in many other places without fallback. Backport of 18f3e79b13947de0bda7c985916d5a04e28936dc from master --- django/db/backends/base/base.py | 5 +---- django/utils/autoreload.py | 5 +---- django/utils/synch.py | 5 +---- tests/file_storage/tests.py | 6 +----- tests/select_for_update/tests.py | 13 +------------ tests/transactions/tests.py | 5 +---- 6 files changed, 6 insertions(+), 33 deletions(-) diff --git a/django/db/backends/base/base.py b/django/db/backends/base/base.py index fb9260d8ca71..cbc49830463c 100644 --- a/django/db/backends/base/base.py +++ b/django/db/backends/base/base.py @@ -10,10 +10,7 @@ from django.db.transaction import TransactionManagementError from django.db.utils import DatabaseError, DatabaseErrorWrapper from django.utils.functional import cached_property -try: - from django.utils.six.moves import _thread as thread -except ImportError: - from django.utils.six.moves import _dummy_thread as thread +from django.utils.six.moves import _thread as thread NO_DB_ALIAS = '__no_db__' diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index e7df8c8938ed..0fa6ee2855e0 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -39,10 +39,7 @@ from django.apps import apps from django.conf import settings from django.core.signals import request_finished -try: - from django.utils.six.moves import _thread as thread -except ImportError: - from django.utils.six.moves import _dummy_thread as thread +from django.utils.six.moves import _thread as thread # This import does nothing, but it's necessary to avoid some race conditions # in the threading module. See http://code.djangoproject.com/ticket/2330 . diff --git a/django/utils/synch.py b/django/utils/synch.py index 8bb7293fd49d..c8ef2f07bdbe 100644 --- a/django/utils/synch.py +++ b/django/utils/synch.py @@ -7,10 +7,7 @@ """ import contextlib -try: - import threading -except ImportError: - import dummy_threading as threading +import threading class RWLock(object): diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py index 4edee3cb80af..2718f07cadb9 100644 --- a/tests/file_storage/tests.py +++ b/tests/file_storage/tests.py @@ -6,16 +6,12 @@ import shutil import sys import tempfile +import threading import time import unittest import warnings from datetime import datetime, timedelta -try: - import threading -except ImportError: - import dummy_threading as threading - from django.core.cache import cache from django.core.exceptions import SuspiciousOperation, SuspiciousFileOperation from django.core.files.base import File, ContentFile diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py index d3f71ab65cb0..abd7bd2ed413 100644 --- a/tests/select_for_update/tests.py +++ b/tests/select_for_update/tests.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals +import threading import time -import unittest from django.conf import settings from django.db import transaction, connection, router @@ -15,14 +15,6 @@ from .models import Person -# Some tests require threading, which might not be available. So create a -# skip-test decorator for those test functions. -try: - import threading -except ImportError: - threading = None -requires_threading = unittest.skipUnless(threading, 'requires threading') - # We need to set settings.DEBUG to True so we can capture the output SQL # to examine. @@ -92,7 +84,6 @@ def test_for_update_sql_generated_nowait(self): list(Person.objects.all().select_for_update(nowait=True)) self.assertTrue(self.has_for_update_sql(connection, nowait=True)) - @requires_threading @skipUnlessDBFeature('has_select_for_update_nowait') def test_nowait_raises_error_on_block(self): """ @@ -173,7 +164,6 @@ def run_select_for_update(self, status, nowait=False): # database connection. Close it without waiting for the GC. connection.close() - @requires_threading @skipUnlessDBFeature('has_select_for_update') @skipUnlessDBFeature('supports_transactions') def test_block(self): @@ -223,7 +213,6 @@ def test_block(self): p = Person.objects.get(pk=self.person.pk) self.assertEqual('Fred', p.name) - @requires_threading @skipUnlessDBFeature('has_select_for_update') def test_raw_lock_not_available(self): """ diff --git a/tests/transactions/tests.py b/tests/transactions/tests.py index 005df0ba91b1..bf4735e0d25d 100644 --- a/tests/transactions/tests.py +++ b/tests/transactions/tests.py @@ -1,10 +1,7 @@ from __future__ import unicode_literals import sys -try: - import threading -except ImportError: - threading = None +import threading import time from unittest import skipIf, skipUnless From fe770a6452919807bba59c125b02464290feeba1 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 31 Jan 2015 14:20:36 -0500 Subject: [PATCH 0069/1125] [1.8.x] Fixed expressions test on Python 3.5; refs #23763. Backport of 62df1834b8d60c106c8c16524b275b8a1f47ac3a from master --- tests/expressions/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py index f7e8cae85644..cce1253e3f9f 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -830,7 +830,7 @@ def test_expressions(self): ) self.assertEqual(repr(Col('alias', 'field')), "Col(alias, field)") self.assertEqual(repr(Date('published', 'exact')), "Date(published, exact)") - self.assertEqual(repr(DateTime('published', 'exact', utc)), "DateTime(published, exact, UTC)") + self.assertEqual(repr(DateTime('published', 'exact', utc)), "DateTime(published, exact, %s)" % utc) self.assertEqual(repr(F('published')), "F(published)") self.assertEqual(repr(F('cost') + F('tax')), "") self.assertEqual(repr(Func('published', function='TO_CHAR')), "Func(F(published), function=TO_CHAR)") From 26e07a996d9ca34b6a2582bf4112cd27fb414959 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 1 Feb 2015 19:33:44 -0500 Subject: [PATCH 0070/1125] [1.8.x] Removed InlineAdminForm.field_count() This method is unused since 337d102b8612503bb9dc712bfbf07432a9a96302 Backport of 327a00f48b71b5f82788f3c9ee3cdb25b8e1ef1a from master --- django/contrib/admin/helpers.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 0383f1c72ade..bd41063e43a8 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -310,18 +310,6 @@ def needs_explicit_pk_field(self): return True return False - def field_count(self): - # tabular.html uses this function for colspan value. - num_of_fields = 0 - if self.has_auto_field(): - num_of_fields += 1 - num_of_fields += len(self.fieldsets[0][1]["fields"]) - if self.formset.can_order: - num_of_fields += 1 - if self.formset.can_delete: - num_of_fields += 1 - return num_of_fields - def pk_field(self): return AdminField(self.form, self.formset._pk_field.name, False) From 7580876fbdb05a2cfe016f49a49dc55b45f11011 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 1 Feb 2015 19:54:20 -0500 Subject: [PATCH 0071/1125] [1.8.x] Removed Query.raise_field_error() This method was inadvertently reintroduced in f59fd15c4928caf3dfcbd50f6ab47be409a43b01 Backport of 99ca7c2bd3e04b343f4a0fe2d5add7c6d6f3a456 from master --- django/db/models/sql/query.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 0d89de2458be..6865624ec3c0 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1408,11 +1408,6 @@ def names_to_path(self, names, opts, allow_many=True, fail_on_missing=False): break return path, final_field, targets, names[pos + 1:] - def raise_field_error(self, opts, name): - available = list(get_field_names_from_opts(opts)) + list(self.annotation_select) - raise FieldError("Cannot resolve keyword %r into field. " - "Choices are: %s" % (name, ", ".join(available))) - def setup_joins(self, names, opts, alias, can_reuse=None, allow_many=True): """ Compute the necessary table joins for the passage through the fields From c9df163d0ceae254fd16aec895253443b8e6fcd8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 1 Feb 2015 20:33:22 -0500 Subject: [PATCH 0072/1125] [1.8.x] Removed UpdateCacheMiddleware._session_accessed() This method is unused since f567d04b249913db4a37adab8ba521cdc974d423 Backport of 0e6091249295b0e06aff2b1b4411819f94a1c529 from master --- django/middleware/cache.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/django/middleware/cache.py b/django/middleware/cache.py index df33dfce334f..7fab78f3dc3d 100644 --- a/django/middleware/cache.py +++ b/django/middleware/cache.py @@ -64,12 +64,6 @@ def __init__(self): self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS self.cache = caches[self.cache_alias] - def _session_accessed(self, request): - try: - return request.session.accessed - except AttributeError: - return False - def _should_update_cache(self, request, response): return hasattr(request, '_cache_update_cache') and request._cache_update_cache From 6d0538bd8c38e83a605c987a40081149b254b802 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 1 Feb 2015 21:06:27 -0500 Subject: [PATCH 0073/1125] [1.8.x] Simplified a versionchanged notes for LiveServerTestCase. --- docs/topics/testing/tools.txt | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index b9755305cbd9..38d3f957990b 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -847,23 +847,15 @@ out the `full reference`_ for more details. .. versionchanged:: 1.7 - Before Django 1.7 ``LiveServerTestCase`` used to rely on the - :doc:`staticfiles contrib app ` to get the - static assets of the application(s) under test transparently served at their - expected locations during the execution of these tests. - - In Django 1.7 this dependency of core functionality on a ``contrib`` - application has been removed, because of which ``LiveServerTestCase`` - ability in this respect has been retrofitted to simply publish the contents - of the file system under :setting:`STATIC_ROOT` at the :setting:`STATIC_URL` - URL. - - If you use the ``staticfiles`` app in your project and need to perform live - testing then you might want to consider using the - :class:`~django.contrib.staticfiles.testing.StaticLiveServerTestCase` - subclass shipped with it instead because it's the one that implements the - original behavior now. See :ref:`the relevant documentation - ` for more details. + In older versions, ``LiveServerTestCase`` relied on the :doc:`staticfiles + contrib app ` to transparently serve static + files during the execution of tests. This functionality has been moved to + the :class:`~django.contrib.staticfiles.testing.StaticLiveServerTestCase` + subclass, so use that subclass if you need :ref:`the original behavior + `. + + ``LiveServerTestCase`` now simply publishes the contents of the file system + under :setting:`STATIC_ROOT` at the :setting:`STATIC_URL`. .. note:: From 3cd8f51f21878d2fcd8361adcb0d88d152e778ef Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 1 Feb 2015 20:18:15 -0500 Subject: [PATCH 0074/1125] [1.8.x] Removed query.alias_diff() This function is unused since 6fe2b001dba45134d7c10729c57959995e241a88 Backport of f79ce63fdb6788c8b4857fece6c86de57fc129ee from master --- django/db/models/sql/query.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 6865624ec3c0..4fb22149d0f1 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -2050,17 +2050,6 @@ def is_reverse_o2o(field): return not hasattr(field, 'rel') and field.field.unique -def alias_diff(refcounts_before, refcounts_after): - """ - Given the before and after copies of refcounts works out which aliases - have been added to the after copy. - """ - # Use -1 as default value so that any join that is created, then trimmed - # is seen as added. - return set(t for t in refcounts_after - if refcounts_after[t] > refcounts_before.get(t, -1)) - - class JoinPromoter(object): """ A class to abstract away join promotion problems for complex filter From ef90ca5f42c81dcee39878c9da96af56eb5e29a4 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Fri, 30 Jan 2015 23:14:46 +0000 Subject: [PATCH 0075/1125] [1.8.x] Fixed #24255 -- Specifed 'fields' parameter in modelformset_factory / inlineformset_factory examples. Backport of 8d64aae883f7721c33f88276e7c999844085659f from master --- docs/topics/forms/modelforms.txt | 66 ++++++++++++++++---------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index bfc066d251f0..e2804cb9f56e 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -671,7 +671,7 @@ There are a couple of things to note, however. You can only use this technique to opt out from a field defined declaratively by a parent class; it won't prevent the ``ModelForm`` metaclass from generating a default field. To opt-out from default fields, see - :ref:`controlling-fields-with-fields-and-exclude`. + :ref:`modelforms-selecting-fields`. .. _modelforms-factory: @@ -716,7 +716,19 @@ reuse the ``Author`` model from above:: >>> from django.forms.models import modelformset_factory >>> from myapp.models import Author - >>> AuthorFormSet = modelformset_factory(Author) + >>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title')) + +Using ``fields`` restricts the formset to use only the given fields. +Alternatively, you can take an "opt-out" approach, specifying which fields to +exclude:: + + >>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',)) + +.. versionchanged:: 1.8 + + In older versions, omitting both ``fields`` and ``exclude`` resulted in + a formset with all the model's fields. Doing this now raises an + :exc:`~django.core.exceptions.ImproperlyConfigured` exception. This will create a formset that is capable of working with the data associated with the ``Author`` model. It works just like a regular formset:: @@ -730,8 +742,7 @@ with the ``Author`` model. It works just like a regular formset:: - - + .. note:: @@ -763,7 +774,8 @@ Alternatively, you can create a subclass that sets ``self.queryset`` in Then, pass your ``BaseAuthorFormSet`` class to the factory function:: - >>> AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet) + >>> AuthorFormSet = modelformset_factory( + ... Author, fields=('name', 'title'), formset=BaseAuthorFormSet) If you want to return a formset that doesn't include *any* pre-existing instances of the model, you can specify an empty QuerySet:: @@ -795,22 +807,6 @@ It is not always necessary to define a custom model form. The ``modelformset_factory`` function has several arguments which are passed through to ``modelform_factory``, which are described below. -.. _controlling-fields-with-fields-and-exclude: - -Controlling which fields are used with ``fields`` and ``exclude`` ------------------------------------------------------------------ - -By default, a model formset uses all fields in the model that are not marked -with ``editable=False``. However, this can be overridden at the formset level:: - - >>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title')) - -Using ``fields`` restricts the formset to use only the given fields. -Alternatively, you can take an "opt-out" approach, specifying which fields to -exclude:: - - >>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',)) - Specifying widgets to use in the form with ``widgets`` ------------------------------------------------------ @@ -820,7 +816,8 @@ works the same way as the ``widgets`` dictionary on the inner ``Meta`` class of a ``ModelForm`` works:: >>> AuthorFormSet = modelformset_factory( - ... Author, widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20}) + ... Author, fields=('name', 'title'), + ... widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})}) Enabling localization for fields with ``localized_fields`` ---------------------------------------------------------- @@ -829,7 +826,8 @@ Using the ``localized_fields`` parameter, you can enable localization for fields in the form. >>> AuthorFormSet = modelformset_factory( - ... Author, localized_fields=('value',)) + ... Author, fields=('name', 'title', 'birth_date'), + ... localized_fields=('birth_date',)) If ``localized_fields`` is set to the special value ``'__all__'``, all fields will be localized. @@ -906,7 +904,7 @@ extra forms displayed. >>> Author.objects.order_by('name') [, , ] - >>> AuthorFormSet = modelformset_factory(Author, max_num=1) + >>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=1) >>> formset = AuthorFormSet(queryset=Author.objects.order_by('name')) >>> [x.name for x in formset.get_queryset()] ['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman'] @@ -915,7 +913,7 @@ 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``:: - >>> AuthorFormSet = modelformset_factory(Author, max_num=4, extra=2) + >>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=4, extra=2) >>> formset = AuthorFormSet(queryset=Author.objects.order_by('name')) >>> for form in formset: ... print(form.as_table()) @@ -938,7 +936,7 @@ formset to edit ``Author`` model instances:: from myapp.models import Author def manage_authors(request): - AuthorFormSet = modelformset_factory(Author) + AuthorFormSet = modelformset_factory(Author, fields=('name', 'title')) if request.method == 'POST': formset = AuthorFormSet(request.POST, request.FILES) if formset.is_valid(): @@ -1006,7 +1004,7 @@ formset:: from myapp.models import Author def manage_authors(request): - AuthorFormSet = modelformset_factory(Author) + AuthorFormSet = modelformset_factory(Author, fields=('name', 'title')) if request.method == "POST": formset = AuthorFormSet(request.POST, request.FILES, queryset=Author.objects.filter(name__startswith='O')) @@ -1106,7 +1104,7 @@ If you want to create a formset that allows you to edit books belonging to a particular author, you could do this:: >>> from django.forms.models import inlineformset_factory - >>> BookFormSet = inlineformset_factory(Author, Book) + >>> BookFormSet = inlineformset_factory(Author, Book, fields=('title',)) >>> author = Author.objects.get(name='Mike Royko') >>> formset = BookFormSet(instance=author) @@ -1145,7 +1143,8 @@ Then when you create your inline formset, pass in the optional argument ``formset``:: >>> from django.forms.models import inlineformset_factory - >>> BookFormSet = inlineformset_factory(Author, Book, formset=CustomInlineFormSet) + >>> BookFormSet = inlineformset_factory(Author, Book, fields=('title',), + ... formset=CustomInlineFormSet) >>> author = Author.objects.get(name='Mike Royko') >>> formset = BookFormSet(instance=author) @@ -1157,14 +1156,15 @@ need to resolve the ambiguity manually using ``fk_name``. For example, consider the following model:: class Friendship(models.Model): - from_friend = models.ForeignKey(Friend) - to_friend = models.ForeignKey(Friend) + from_friend = models.ForeignKey(Friend, related_name='from_friends') + to_friend = models.ForeignKey(Friend, related_name='friends') length_in_months = models.IntegerField() To resolve this, you can use ``fk_name`` to :func:`~django.forms.models.inlineformset_factory`:: - >>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name="from_friend") + >>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name='from_friend', + ... fields=('to_friend', 'length_in_months')) Using an inline formset in a view --------------------------------- @@ -1174,7 +1174,7 @@ of a model. Here's how you can do that:: def manage_books(request, author_id): author = Author.objects.get(pk=author_id) - BookInlineFormSet = inlineformset_factory(Author, Book) + BookInlineFormSet = inlineformset_factory(Author, Book, fields=('title',)) if request.method == "POST": formset = BookInlineFormSet(request.POST, request.FILES, instance=author) if formset.is_valid(): From 3af1e7860e0160bd126bdafebf8511b1ea365af9 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 2 Feb 2015 11:06:27 -0500 Subject: [PATCH 0076/1125] [1.8.x] Removed contrib.auth.forms.mask_password() This function is unused since dce820ff70f00e974afd3e6e310aa825bc55319f after being introduced in 718a5ba1a1a77374c26b134ded46dab13776d1a1 Backport of a53541852d5601232899e54d66e623bc163c6dc2 from master --- django/contrib/auth/forms.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index d410bbb9bcfc..3d19b2247e24 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -20,15 +20,6 @@ from django.contrib.sites.shortcuts import get_current_site -UNMASKED_DIGITS_TO_SHOW = 6 - - -def mask_password(password): - shown = password[:UNMASKED_DIGITS_TO_SHOW] - masked = "*" * max(len(password) - UNMASKED_DIGITS_TO_SHOW, 0) - return shown + masked - - class ReadOnlyPasswordHashWidget(forms.Widget): def render(self, name, value, attrs): encoded = value From f87457a4604efc862381020b47f4e6fe8272ac0b Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Mon, 26 Jan 2015 21:57:10 +0100 Subject: [PATCH 0077/1125] [1.8.x] Fixed #24168 -- Allowed selecting a template engine in a few APIs. Specifically in rendering shortcuts, template responses, and class-based views that return template responses. Also added a test for render_to_response(status=...) which was missing from fdbfc980. Thanks Tim and Carl for the review. Backport of 2133f31 from master. Conflicts: docs/topics/http/shortcuts.txt tests/generic_views/test_base.py --- django/shortcuts.py | 16 ++++++++----- django/template/response.py | 13 ++++++---- django/test/utils.py | 24 ++++++++++++++++++- django/views/generic/base.py | 2 ++ .../ref/class-based-views/flattened-index.txt | 14 +++++++++++ docs/ref/class-based-views/mixins-simple.txt | 9 +++++++ docs/ref/template-response.txt | 20 +++++++++++----- docs/topics/http/shortcuts.txt | 18 +++++++++++--- .../jinja2/generic_views/using.html | 1 + .../templates/generic_views/using.html | 1 + tests/generic_views/test_base.py | 16 ++++++++++++- tests/shortcuts/jinja2/shortcuts/using.html | 1 + .../shortcuts/templates/shortcuts/using.html | 1 + tests/shortcuts/tests.py | 24 +++++++++++++++++++ tests/shortcuts/urls.py | 3 +++ tests/shortcuts/views.py | 17 +++++++++++++ .../jinja2/template_tests/using.html | 1 + .../templates/template_tests/using.html | 1 + tests/template_tests/test_response.py | 20 ++++++++++++++++ 19 files changed, 180 insertions(+), 22 deletions(-) create mode 100644 tests/generic_views/jinja2/generic_views/using.html create mode 100644 tests/generic_views/templates/generic_views/using.html create mode 100644 tests/shortcuts/jinja2/shortcuts/using.html create mode 100644 tests/shortcuts/templates/shortcuts/using.html create mode 100644 tests/template_tests/jinja2/template_tests/using.html create mode 100644 tests/template_tests/templates/template_tests/using.html diff --git a/django/shortcuts.py b/django/shortcuts.py index 3bb241252674..bbfc99299fd8 100644 --- a/django/shortcuts.py +++ b/django/shortcuts.py @@ -25,7 +25,7 @@ def render_to_response(template_name, context=None, context_instance=_context_instance_undefined, content_type=None, status=None, dirs=_dirs_undefined, - dictionary=_dictionary_undefined): + dictionary=_dictionary_undefined, using=None): """ Returns a HttpResponse whose content is filled with the result of calling django.template.loader.render_to_string() with the passed arguments. @@ -34,12 +34,13 @@ def render_to_response(template_name, context=None, and dirs is _dirs_undefined and dictionary is _dictionary_undefined): # No deprecated arguments were passed - use the new code path - content = loader.render_to_string(template_name, context) + content = loader.render_to_string(template_name, context, using=using) else: # Some deprecated arguments were passed - use the legacy code path content = loader.render_to_string( - template_name, context, context_instance, dirs, dictionary) + template_name, context, context_instance, dirs, dictionary, + using=using) return HttpResponse(content, content_type, status) @@ -47,7 +48,8 @@ def render_to_response(template_name, context=None, def render(request, template_name, context=None, context_instance=_context_instance_undefined, content_type=None, status=None, current_app=_current_app_undefined, - dirs=_dirs_undefined, dictionary=_dictionary_undefined): + dirs=_dirs_undefined, dictionary=_dictionary_undefined, + using=None): """ Returns a HttpResponse whose content is filled with the result of calling django.template.loader.render_to_string() with the passed arguments. @@ -59,7 +61,8 @@ def render(request, template_name, context=None, and dictionary is _dictionary_undefined): # No deprecated arguments were passed - use the new code path # In Django 2.0, request should become a positional argument. - content = loader.render_to_string(template_name, context, request=request) + content = loader.render_to_string( + template_name, context, request=request, using=using) else: # Some deprecated arguments were passed - use the legacy code path @@ -80,7 +83,8 @@ def render(request, template_name, context=None, context_instance._current_app = current_app content = loader.render_to_string( - template_name, context, context_instance, dirs, dictionary) + template_name, context, context_instance, dirs, dictionary, + using=using) return HttpResponse(content, content_type, status) diff --git a/django/template/response.py b/django/template/response.py index ab9559853afc..48295c5e0576 100644 --- a/django/template/response.py +++ b/django/template/response.py @@ -16,7 +16,7 @@ class SimpleTemplateResponse(HttpResponse): rendering_attrs = ['template_name', 'context_data', '_post_render_callbacks'] def __init__(self, template, context=None, content_type=None, status=None, - charset=None): + charset=None, using=None): if isinstance(template, Template): warnings.warn( "{}'s template argument cannot be a django.template.Template " @@ -31,6 +31,8 @@ def __init__(self, template, context=None, content_type=None, status=None, self.template_name = template self.context_data = context + self.using = using + self._post_render_callbacks = [] # _request stores the current request object in subclasses that know @@ -73,9 +75,9 @@ def __getstate__(self): def resolve_template(self, template): "Accepts a template object, path-to-template or list of paths" if isinstance(template, (list, tuple)): - return loader.select_template(template) + return loader.select_template(template, using=self.using) elif isinstance(template, six.string_types): - return loader.get_template(template) + return loader.get_template(template, using=self.using) else: return template @@ -189,7 +191,8 @@ class TemplateResponse(SimpleTemplateResponse): rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_request', '_current_app'] def __init__(self, request, template, context=None, content_type=None, - status=None, current_app=_current_app_undefined, charset=None): + status=None, current_app=_current_app_undefined, charset=None, + using=None): # As a convenience we'll allow callers to provide current_app without # having to avoid needing to create the RequestContext directly if current_app is not _current_app_undefined: @@ -199,5 +202,5 @@ def __init__(self, request, template, context=None, content_type=None, RemovedInDjango20Warning, stacklevel=2) request.current_app = current_app super(TemplateResponse, self).__init__( - template, context, content_type, status, charset) + template, context, content_type, status, charset, using) self._request = request diff --git a/django/test/utils.py b/django/test/utils.py index 2bc6cf3451a5..90397f929889 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -3,7 +3,7 @@ import re import sys import time -from unittest import skipUnless +from unittest import skipIf, skipUnless import warnings from functools import wraps from xml.dom.minidom import parseString, Node @@ -22,6 +22,11 @@ from django.utils.encoding import force_str from django.utils.translation import deactivate +try: + import jinja2 +except ImportError: + jinja2 = None + __all__ = ( 'Approximate', 'ContextList', 'get_runner', @@ -585,3 +590,20 @@ def freeze_time(t): yield finally: time.time = _real_time + + +def require_jinja2(test_func): + """ + Decorator to enable a Jinja2 template engine in addition to the regular + Django template engine for a test or skip it if Jinja2 isn't available. + """ + test_func = skipIf(jinja2 is None, "this test requires jinja2")(test_func) + test_func = override_settings(TEMPLATES=[{ + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'APP_DIRS': True, + }, { + 'BACKEND': 'django.template.backends.jinja2.Jinja2', + 'APP_DIRS': True, + 'OPTIONS': {'keep_trailing_newline': True}, + }])(test_func) + return test_func diff --git a/django/views/generic/base.py b/django/views/generic/base.py index 8a9b6baa4411..afbf9bc571f3 100644 --- a/django/views/generic/base.py +++ b/django/views/generic/base.py @@ -115,6 +115,7 @@ class TemplateResponseMixin(object): A mixin that can be used to render a template. """ template_name = None + template_engine = None response_class = TemplateResponse content_type = None @@ -131,6 +132,7 @@ def render_to_response(self, context, **response_kwargs): request=self.request, template=self.get_template_names(), context=context, + using=self.template_engine, **response_kwargs ) diff --git a/docs/ref/class-based-views/flattened-index.txt b/docs/ref/class-based-views/flattened-index.txt index 8f7c743dd48b..5af8a4e7f9d9 100644 --- a/docs/ref/class-based-views/flattened-index.txt +++ b/docs/ref/class-based-views/flattened-index.txt @@ -35,6 +35,7 @@ TemplateView * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] **Methods** @@ -89,6 +90,7 @@ DetailView * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`] * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field` * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` @@ -124,6 +126,7 @@ ListView * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` @@ -155,6 +158,7 @@ FormView * :attr:`~django.views.generic.edit.FormMixin.prefix` [:meth:`~django.views.generic.edit.FormMixin.get_prefix`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] * :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] **Methods** @@ -191,6 +195,7 @@ CreateView * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`] * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` * :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field` * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` @@ -232,6 +237,7 @@ UpdateView * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`] * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` * :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field` * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` @@ -269,6 +275,7 @@ DeleteView * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`] * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` * :attr:`~django.views.generic.edit.DeletionMixin.success_url` [:meth:`~django.views.generic.edit.DeletionMixin.get_success_url`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field` * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` @@ -308,6 +315,7 @@ ArchiveIndexView * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` @@ -346,6 +354,7 @@ YearArchiveView * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` * :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] @@ -387,6 +396,7 @@ MonthArchiveView * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` * :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] @@ -428,6 +438,7 @@ WeekArchiveView * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` * :attr:`~django.views.generic.dates.WeekMixin.week` [:meth:`~django.views.generic.dates.WeekMixin.get_week`] @@ -473,6 +484,7 @@ DayArchiveView * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` * :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] @@ -520,6 +532,7 @@ TodayArchiveView * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` * :attr:`~django.views.generic.dates.YearMixin.year` [:meth:`~django.views.generic.dates.YearMixin.get_year`] @@ -565,6 +578,7 @@ DateDetailView * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`] * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_url_kwarg` +* :attr:`~django.views.generic.base.TemplateResponseMixin.template_engine` * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_field` * :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` diff --git a/docs/ref/class-based-views/mixins-simple.txt b/docs/ref/class-based-views/mixins-simple.txt index ce2bd2f3aae3..ba18a1325ba4 100644 --- a/docs/ref/class-based-views/mixins-simple.txt +++ b/docs/ref/class-based-views/mixins-simple.txt @@ -49,6 +49,15 @@ TemplateResponseMixin a ``template_name`` will raise a :class:`django.core.exceptions.ImproperlyConfigured` exception. + .. attribute:: template_engine + + .. versionadded:: 1.8 + + The :setting:`NAME ` of a template engine to use for + loading the template. ``template_engine`` is passed as the ``using`` + keyword argument to ``response_class``. Default is ``None``, which + tells Django to search for the template in all configured engines. + .. attribute:: response_class The response class to be returned by ``render_to_response`` method. diff --git a/docs/ref/template-response.txt b/docs/ref/template-response.txt index c1ae0267a535..ecf984cdf3b5 100644 --- a/docs/ref/template-response.txt +++ b/docs/ref/template-response.txt @@ -65,7 +65,7 @@ Attributes Methods ------- -.. method:: SimpleTemplateResponse.__init__(template, context=None, content_type=None, status=None, charset=None) +.. method:: SimpleTemplateResponse.__init__(template, context=None, content_type=None, status=None, charset=None, using=None) Instantiates a :class:`~django.template.response.SimpleTemplateResponse` object with the given template, context, content type, HTTP status, and @@ -102,9 +102,13 @@ Methods be extracted from ``content_type``, and if that is unsuccessful, the :setting:`DEFAULT_CHARSET` setting will be used. - .. versionadded:: 1.8 + ``using`` + The :setting:`NAME ` of a template engine to use for + loading the template. - The ``charset`` parameter was added. + .. versionchanged:: 1.8 + + The ``charset`` and ``using`` parameters were added. .. method:: SimpleTemplateResponse.resolve_context(context) @@ -185,7 +189,7 @@ TemplateResponse objects Methods ------- -.. method:: TemplateResponse.__init__(request, template, context=None, content_type=None, status=None, current_app=None, charset=None) +.. method:: TemplateResponse.__init__(request, template, context=None, content_type=None, status=None, current_app=None, charset=None, using=None) Instantiates a :class:`~django.template.response.TemplateResponse` object with the given request, template, context, content type, HTTP status, and @@ -235,9 +239,13 @@ Methods be extracted from ``content_type``, and if that is unsuccessful, the :setting:`DEFAULT_CHARSET` setting will be used. - .. versionadded:: 1.8 + ``using`` + The :setting:`NAME ` of a template engine to use for + loading the template. + + .. versionchanged:: 1.8 - The ``charset`` parameter was added. + The ``charset`` and ``using`` parameters were added. The rendering process ===================== diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index f01578683818..780176c8244d 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -15,7 +15,7 @@ introduce controlled coupling for convenience's sake. ``render`` ========== -.. function:: render(request, template_name[, context][, context_instance][, content_type][, status][, current_app][, dirs]) +.. function:: render(request, template_name[, context][, context_instance][, content_type][, status][, current_app][, dirs][, using]) Combines a given template with a given context dictionary and returns an :class:`~django.http.HttpResponse` object with that rendered text. @@ -77,6 +77,14 @@ Optional arguments The ``current_app`` argument is deprecated. Instead you should set ``request.current_app``. +``using`` + The :setting:`NAME ` of a template engine to use for + loading the template. + +.. versionchanged:: 1.8 + + The ``using`` parameter was added. + .. versionchanged:: 1.7 The ``dirs`` parameter was added. @@ -113,7 +121,7 @@ This example is equivalent to:: ``render_to_response`` ====================== -.. function:: render_to_response(template_name[, context][, context_instance][, content_type][, status][, dirs]) +.. function:: render_to_response(template_name[, context][, context_instance][, content_type][, status][, dirs][, using]) Renders a given template with a given context dictionary and returns an :class:`~django.http.HttpResponse` object with that rendered text. @@ -163,9 +171,13 @@ Optional arguments ``status`` The status code for the response. Defaults to ``200``. +``using`` + The :setting:`NAME ` of a template engine to use for + loading the template. + .. versionchanged:: 1.8 - The ``status`` parameter was added. + The ``status`` and ``using`` parameters were added. .. versionchanged:: 1.7 diff --git a/tests/generic_views/jinja2/generic_views/using.html b/tests/generic_views/jinja2/generic_views/using.html new file mode 100644 index 000000000000..8ce973e958c3 --- /dev/null +++ b/tests/generic_views/jinja2/generic_views/using.html @@ -0,0 +1 @@ +Jinja2 diff --git a/tests/generic_views/templates/generic_views/using.html b/tests/generic_views/templates/generic_views/using.html new file mode 100644 index 000000000000..65bcbf65a431 --- /dev/null +++ b/tests/generic_views/templates/generic_views/using.html @@ -0,0 +1 @@ +DTL diff --git a/tests/generic_views/test_base.py b/tests/generic_views/test_base.py index d855a3829b43..5c6ac24283e6 100644 --- a/tests/generic_views/test_base.py +++ b/tests/generic_views/test_base.py @@ -9,6 +9,7 @@ from django.utils import six from django.utils.deprecation import RemovedInDjango19Warning from django.test import TestCase, RequestFactory, ignore_warnings, override_settings +from django.test.utils import require_jinja2 from django.views.generic import View, TemplateView, RedirectView from . import views @@ -280,10 +281,23 @@ def test_get_generic_template(self): def test_template_name_required(self): """ - A template view must provide a template name + A template view must provide a template name. """ self.assertRaises(ImproperlyConfigured, self.client.get, '/template/no_template/') + @require_jinja2 + def test_template_engine(self): + """ + A template view may provide a template engine. + """ + request = self.rf.get('/using/') + view = TemplateView.as_view(template_name='generic_views/using.html') + self.assertEqual(view(request).render().content, b'DTL\n') + view = TemplateView.as_view(template_name='generic_views/using.html', template_engine='django') + self.assertEqual(view(request).render().content, b'DTL\n') + view = TemplateView.as_view(template_name='generic_views/using.html', template_engine='jinja2') + self.assertEqual(view(request).render().content, b'Jinja2\n') + def test_template_params(self): """ A generic template view passes kwargs as context. diff --git a/tests/shortcuts/jinja2/shortcuts/using.html b/tests/shortcuts/jinja2/shortcuts/using.html new file mode 100644 index 000000000000..8ce973e958c3 --- /dev/null +++ b/tests/shortcuts/jinja2/shortcuts/using.html @@ -0,0 +1 @@ +Jinja2 diff --git a/tests/shortcuts/templates/shortcuts/using.html b/tests/shortcuts/templates/shortcuts/using.html new file mode 100644 index 000000000000..65bcbf65a431 --- /dev/null +++ b/tests/shortcuts/templates/shortcuts/using.html @@ -0,0 +1 @@ +DTL diff --git a/tests/shortcuts/tests.py b/tests/shortcuts/tests.py index e22cf4d38628..98251244c56e 100644 --- a/tests/shortcuts/tests.py +++ b/tests/shortcuts/tests.py @@ -1,5 +1,6 @@ from django.utils.deprecation import RemovedInDjango20Warning from django.test import TestCase, ignore_warnings, override_settings +from django.test.utils import require_jinja2 @override_settings( @@ -38,6 +39,20 @@ def test_render_to_response_with_dirs(self): self.assertEqual(response.content, b'spam eggs\n') self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') + def test_render_to_response_with_status(self): + response = self.client.get('/render_to_response/status/') + self.assertEqual(response.status_code, 403) + self.assertEqual(response.content, b'FOO.BAR..\n') + + @require_jinja2 + def test_render_to_response_with_using(self): + response = self.client.get('/render_to_response/using/') + self.assertEqual(response.content, b'DTL\n') + response = self.client.get('/render_to_response/using/?using=django') + self.assertEqual(response.content, b'DTL\n') + response = self.client.get('/render_to_response/using/?using=jinja2') + self.assertEqual(response.content, b'Jinja2\n') + @ignore_warnings(category=RemovedInDjango20Warning) def test_render_to_response_with_context_instance_misuse(self): """ @@ -78,6 +93,15 @@ def test_render_with_status(self): self.assertEqual(response.status_code, 403) self.assertEqual(response.content, b'FOO.BAR../render/status/\n') + @require_jinja2 + def test_render_with_using(self): + response = self.client.get('/render/using/') + self.assertEqual(response.content, b'DTL\n') + response = self.client.get('/render/using/?using=django') + self.assertEqual(response.content, b'DTL\n') + response = self.client.get('/render/using/?using=jinja2') + self.assertEqual(response.content, b'Jinja2\n') + @ignore_warnings(category=RemovedInDjango20Warning) def test_render_with_current_app(self): response = self.client.get('/render/current_app/') diff --git a/tests/shortcuts/urls.py b/tests/shortcuts/urls.py index cbb6c99c145a..449a840f7384 100644 --- a/tests/shortcuts/urls.py +++ b/tests/shortcuts/urls.py @@ -8,6 +8,8 @@ url(r'^render_to_response/request_context/$', views.render_to_response_view_with_request_context), url(r'^render_to_response/content_type/$', views.render_to_response_view_with_content_type), url(r'^render_to_response/dirs/$', views.render_to_response_view_with_dirs), + url(r'^render_to_response/status/$', views.render_to_response_view_with_status), + url(r'^render_to_response/using/$', views.render_to_response_view_with_using), url(r'^render_to_response/context_instance_misuse/$', views.render_to_response_with_context_instance_misuse), url(r'^render/$', views.render_view), url(r'^render/multiple_templates/$', views.render_view_with_multiple_templates), @@ -15,6 +17,7 @@ url(r'^render/content_type/$', views.render_view_with_content_type), url(r'^render/dirs/$', views.render_with_dirs), url(r'^render/status/$', views.render_view_with_status), + url(r'^render/using/$', views.render_view_with_using), url(r'^render/current_app/$', views.render_view_with_current_app), url(r'^render/current_app_conflict/$', views.render_view_with_current_app_conflict), ] diff --git a/tests/shortcuts/views.py b/tests/shortcuts/views.py index 42493f3e7b9f..85d1922d58e1 100644 --- a/tests/shortcuts/views.py +++ b/tests/shortcuts/views.py @@ -43,6 +43,18 @@ def render_to_response_view_with_dirs(request): return render_to_response('render_dirs_test.html', dirs=dirs) +def render_to_response_view_with_status(request): + return render_to_response('shortcuts/render_test.html', { + 'foo': 'FOO', + 'bar': 'BAR', + }, status=403) + + +def render_to_response_view_with_using(request): + using = request.GET.get('using') + return render_to_response('shortcuts/using.html', using=using) + + def context_processor(request): return {'bar': 'context processor output'} @@ -95,6 +107,11 @@ def render_view_with_status(request): }, status=403) +def render_view_with_using(request): + using = request.GET.get('using') + return render(request, 'shortcuts/using.html', using=using) + + def render_view_with_current_app(request): return render(request, 'shortcuts/render_test.html', { 'foo': 'FOO', diff --git a/tests/template_tests/jinja2/template_tests/using.html b/tests/template_tests/jinja2/template_tests/using.html new file mode 100644 index 000000000000..8ce973e958c3 --- /dev/null +++ b/tests/template_tests/jinja2/template_tests/using.html @@ -0,0 +1 @@ +Jinja2 diff --git a/tests/template_tests/templates/template_tests/using.html b/tests/template_tests/templates/template_tests/using.html new file mode 100644 index 000000000000..65bcbf65a431 --- /dev/null +++ b/tests/template_tests/templates/template_tests/using.html @@ -0,0 +1 @@ +DTL diff --git a/tests/template_tests/test_response.py b/tests/template_tests/test_response.py index 993b2002c86f..3807789e636a 100644 --- a/tests/template_tests/test_response.py +++ b/tests/template_tests/test_response.py @@ -11,6 +11,7 @@ from django.template.response import (TemplateResponse, SimpleTemplateResponse, ContentNotRenderedError) from django.test import ignore_warnings, override_settings +from django.test.utils import require_jinja2 from django.utils._os import upath from django.utils.deprecation import RemovedInDjango20Warning @@ -133,6 +134,15 @@ def test_args(self): self.assertEqual(response['content-type'], 'application/json') self.assertEqual(response.status_code, 504) + @require_jinja2 + def test_using(self): + response = SimpleTemplateResponse('template_tests/using.html').render() + self.assertEqual(response.content, b'DTL\n') + response = SimpleTemplateResponse('template_tests/using.html', using='django').render() + self.assertEqual(response.content, b'DTL\n') + response = SimpleTemplateResponse('template_tests/using.html', using='jinja2').render() + self.assertEqual(response.content, b'Jinja2\n') + def test_post_callbacks(self): "Rendering a template response triggers the post-render callbacks" post = [] @@ -260,6 +270,16 @@ def test_args(self): self.assertEqual(response['content-type'], 'application/json') self.assertEqual(response.status_code, 504) + @require_jinja2 + def test_using(self): + request = self.factory.get('/') + response = TemplateResponse(request, 'template_tests/using.html').render() + self.assertEqual(response.content, b'DTL\n') + response = TemplateResponse(request, 'template_tests/using.html', using='django').render() + self.assertEqual(response.content, b'DTL\n') + response = TemplateResponse(request, 'template_tests/using.html', using='jinja2').render() + self.assertEqual(response.content, b'Jinja2\n') + @ignore_warnings(category=RemovedInDjango20Warning) def test_custom_app(self): self._response('{{ foo }}', current_app="foobar") From c65c8f5696eeac056260a0850525d5266dcf8353 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 3 Feb 2015 08:15:24 -0500 Subject: [PATCH 0078/1125] [1.8.x] Skipped tests from refs #24168 on Python 3.2. --- django/test/utils.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/django/test/utils.py b/django/test/utils.py index 90397f929889..6b5b06cd7ca4 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -22,9 +22,13 @@ from django.utils.encoding import force_str from django.utils.translation import deactivate -try: - import jinja2 -except ImportError: +# Jinja2 doesn't run on Python 3.2 because it uses u-prefixed unicode strings. +if sys.version_info[:2] != (3, 2): + try: + import jinja2 + except ImportError: + jinja2 = None +else: jinja2 = None From 6454716264163b4693c03168ec56ea894ea511f2 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 3 Feb 2015 08:41:57 -0500 Subject: [PATCH 0079/1125] [1.8.x] Fixed #24250 -- Corrected mistakes in FormMixin docs. Backport of 5d193d042a3cc49033f0e8b5125913650d779496 from master --- docs/ref/class-based-views/mixins-editing.txt | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/docs/ref/class-based-views/mixins-editing.txt b/docs/ref/class-based-views/mixins-editing.txt index 0bfbcbd3f7af..024b89cd2b84 100644 --- a/docs/ref/class-based-views/mixins-editing.txt +++ b/docs/ref/class-based-views/mixins-editing.txt @@ -21,6 +21,10 @@ FormMixin A mixin class that provides facilities for creating and displaying forms. + **Mixins** + + * :class:`django.views.generic.base.ContextMixin` + **Methods and Attributes** .. attribute:: initial @@ -87,20 +91,6 @@ FormMixin Renders a response, providing the invalid form as context. - .. method:: get_context_data(**kwargs) - - Populates a context containing the contents of ``kwargs``. - - **Context** - - * ``form``: The form instance that was generated for the view. - - .. note:: - - Views mixing ``FormMixin`` must provide an implementation of - :meth:`form_valid` and :meth:`form_invalid`. - - ModelFormMixin -------------- From 5fdd74f4be4aa6ce54021492e1b05ebf3770463d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 3 Feb 2015 09:10:55 -0500 Subject: [PATCH 0080/1125] [1.8.x] Fixed broken links in class based views flattend index; refs #24250. Backport of 9a391fbd6102176d576aa96ed3aeee9e9661b455 from master --- docs/ref/class-based-views/flattened-index.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ref/class-based-views/flattened-index.txt b/docs/ref/class-based-views/flattened-index.txt index 5af8a4e7f9d9..edefbe81ccaa 100644 --- a/docs/ref/class-based-views/flattened-index.txt +++ b/docs/ref/class-based-views/flattened-index.txt @@ -168,7 +168,7 @@ FormView * :meth:`~django.views.generic.edit.FormMixin.form_invalid` * :meth:`~django.views.generic.edit.FormMixin.form_valid` * :meth:`~django.views.generic.edit.ProcessFormView.get` -* :meth:`~django.views.generic.edit.FormMixin.get_context_data` +* :meth:`~django.views.generic.base.ContextMixin.get_context_data` * :meth:`~django.views.generic.edit.FormMixin.get_form` * :meth:`~django.views.generic.edit.FormMixin.get_form_kwargs` * :meth:`~django.views.generic.base.View.http_method_not_allowed` @@ -207,7 +207,7 @@ CreateView * :meth:`~django.views.generic.edit.FormMixin.form_invalid` * :meth:`~django.views.generic.edit.FormMixin.form_valid` * :meth:`~django.views.generic.edit.ProcessFormView.get` -* :meth:`~django.views.generic.edit.FormMixin.get_context_data` +* :meth:`~django.views.generic.base.ContextMixin.get_context_data` * :meth:`~django.views.generic.edit.FormMixin.get_form` * :meth:`~django.views.generic.edit.FormMixin.get_form_kwargs` * :meth:`~django.views.generic.detail.SingleObjectMixin.get_object` @@ -249,7 +249,7 @@ UpdateView * :meth:`~django.views.generic.edit.FormMixin.form_invalid` * :meth:`~django.views.generic.edit.FormMixin.form_valid` * :meth:`~django.views.generic.edit.ProcessFormView.get` -* :meth:`~django.views.generic.edit.FormMixin.get_context_data` +* :meth:`~django.views.generic.base.ContextMixin.get_context_data` * :meth:`~django.views.generic.edit.FormMixin.get_form` * :meth:`~django.views.generic.edit.FormMixin.get_form_kwargs` * :meth:`~django.views.generic.detail.SingleObjectMixin.get_object` From 9ffe013caa9360183cdb948b8e03c9c2a0da5417 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 3 Feb 2015 08:46:17 -0500 Subject: [PATCH 0081/1125] [1.8.x] Fixed #24263 -- Prevented extra queries on BaseDateDetailView with a custom queryset. Thanks jekka-ua for the report and patch. Backport of 118b11221f7f632b4d0e6e976c87f563746ec211 from master --- django/views/generic/dates.py | 2 +- tests/generic_views/test_dates.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index 8df5c338e2f1..27f0ba106523 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -648,7 +648,7 @@ def get_object(self, queryset=None): day, self.get_day_format()) # Use a custom queryset if provided - qs = queryset or self.get_queryset() + qs = self.get_queryset() if queryset is None else queryset if not self.get_allow_future() and date > datetime.date.today(): raise Http404(_( diff --git a/tests/generic_views/test_dates.py b/tests/generic_views/test_dates.py index cbd9bd90a033..d354d421498c 100644 --- a/tests/generic_views/test_dates.py +++ b/tests/generic_views/test_dates.py @@ -638,6 +638,10 @@ def test_get_object_custom_queryset(self): '/dates/books/get_object_custom_queryset/2008/oct/01/1/') self.assertEqual(res.status_code, 404) + def test_get_object_custom_queryset_numqueries(self): + with self.assertNumQueries(1): + self.client.get('/dates/books/get_object_custom_queryset/2006/may/01/2/') + def test_datetime_date_detail(self): bs = BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0)) res = self.client.get('/dates/booksignings/2008/apr/2/%d/' % bs.pk) From 43b0131fb5724cb92716eedfe35a6b2b4d84e1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Wed, 28 Jan 2015 13:40:48 +0200 Subject: [PATCH 0082/1125] [1.8.x] Fixed #23617 -- Added get_pk_value_on_save() The method is mainly intended for use with UUIDField. For UUIDField we want to call the field's default even when primary key value is explicitly set to None to match the behavior of AutoField. Thanks to Marc Tamlyn and Tim Graham for review. Backport of 8adc59038cdc6ce4f9170e4de2d716d940e136b3 from master --- django/db/models/base.py | 3 +++ django/db/models/fields/__init__.py | 11 +++++++++++ django/db/models/query.py | 7 +++++++ docs/ref/models/fields.txt | 9 +++++++++ docs/releases/1.8.txt | 3 +++ tests/model_fields/test_uuid.py | 17 +++++++++++++++++ 6 files changed, 50 insertions(+) diff --git a/django/db/models/base.py b/django/db/models/base.py index 945cd0154b79..3d65cc3d9dc3 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -772,6 +772,9 @@ def _save_table(self, raw=False, cls=None, force_insert=False, if f.name in update_fields or f.attname in update_fields] pk_val = self._get_pk_val(meta) + if pk_val is None: + pk_val = meta.pk.get_pk_value_on_save(self) + setattr(self, meta.pk.attname, pk_val) pk_set = pk_val is not None if not pk_set and (force_update or update_fields): raise ValueError("Cannot force an update in save() with no primary key.") diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index d5dfac733f25..627af0c98571 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -506,6 +506,17 @@ def __reduce__(self): return _load_field, (self.model._meta.app_label, self.model._meta.object_name, self.name) + def get_pk_value_on_save(self, instance): + """ + Hook to generate new PK values on save. This method is called when + saving instances with no primary key value set. If this method returns + something else than None, then the returned value is used when saving + the new instance. + """ + if self.default: + return self.get_default() + return None + def to_python(self, value): """ Converts the input value into the expected Python data type, raising diff --git a/django/db/models/query.py b/django/db/models/query.py index 28936949f01b..2b7aab6ac41b 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -345,6 +345,11 @@ def create(self, **kwargs): obj.save(force_insert=True, using=self.db) return obj + def _populate_pk_values(self, objs): + for obj in objs: + if obj.pk is None: + obj.pk = obj._meta.pk.get_pk_value_on_save(obj) + def bulk_create(self, objs, batch_size=None): """ Inserts each of the instances into the database. This does *not* call @@ -369,6 +374,8 @@ def bulk_create(self, objs, batch_size=None): self._for_write = True connection = connections[self.db] fields = self.model._meta.local_concrete_fields + objs = list(objs) + self._populate_pk_values(objs) with transaction.atomic(using=self.db, savepoint=False): if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk and self.model._meta.has_auto_field): diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 8604137afe9d..f4fe52a7373a 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -220,6 +220,15 @@ Note that ``lambda``\s cannot be used for field options like ``default`` because they cannot be :ref:`serialized by migrations `. See that documentation for other caveats. +The default value is used when new model instances are created and a value +isn't provided for the field. When the field is a primary key, the default is +also used when the field is set to ``None``. + +.. versionchanged:: 1.8 + + The default wasn't used for ``None`` primary key values in previous + versions. + ``editable`` ------------ diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 2806c21580ed..ac1dd36374c5 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -525,6 +525,9 @@ Models * You can now get the set of deferred fields for a model using :meth:`Model.get_deferred_fields() `. +* Model field ``default``’s are now used when primary key field's are set to + ``None``. + Signals ^^^^^^^ diff --git a/tests/model_fields/test_uuid.py b/tests/model_fields/test_uuid.py index b5bc37a48df7..13fe245be312 100644 --- a/tests/model_fields/test_uuid.py +++ b/tests/model_fields/test_uuid.py @@ -87,3 +87,20 @@ def test_creation(self): PrimaryKeyUUIDModel.objects.create() loaded = PrimaryKeyUUIDModel.objects.get() self.assertIsInstance(loaded.pk, uuid.UUID) + + def test_uuid_pk_on_save(self): + saved = PrimaryKeyUUIDModel.objects.create(id=None) + loaded = PrimaryKeyUUIDModel.objects.get() + self.assertIsNotNone(loaded.id, None) + self.assertEqual(loaded.id, saved.id) + + def test_uuid_pk_on_bulk_create(self): + u1 = PrimaryKeyUUIDModel() + u2 = PrimaryKeyUUIDModel(id=None) + PrimaryKeyUUIDModel.objects.bulk_create([u1, u2]) + # Check that the two objects were correctly created. + u1_found = PrimaryKeyUUIDModel.objects.filter(id=u1.id).exists() + u2_found = PrimaryKeyUUIDModel.objects.exclude(id=u1.id).exists() + self.assertTrue(u1_found) + self.assertTrue(u2_found) + self.assertEqual(PrimaryKeyUUIDModel.objects.count(), 2) From d88c24f436ae23ff5aec4ea06c0c27499a71e074 Mon Sep 17 00:00:00 2001 From: Matthew Somerville Date: Wed, 28 Jan 2015 21:43:23 +0000 Subject: [PATCH 0083/1125] [1.8.x] Fixed #24240 -- Allowed GZipping a Unicode StreamingHttpResponse make_bytes() assumed that if the Content-Encoding header is set, then everything had already been dealt with bytes-wise, but in a streaming situation this was not necessarily the case. make_bytes() is only called when necessary when working with a StreamingHttpResponse iterable, but by that point the middleware has added the Content-Encoding header and thus make_bytes() tried to call bytes(value) (and dies). If it had been a normal HttpResponse, make_bytes() would have been called when the content was set, well before the middleware set the Content-Encoding header. This commit removes the special casing when Content-Encoding is set, allowing unicode strings to be encoded during the iteration before they are e.g. gzipped. This behaviour was added a long time ago for #4969 and it doesn't appear to be necessary any more, as everything is correctly made into bytes at the appropriate places. Two new tests, to show that supplying non-ASCII characters to a StreamingHttpResponse works fine normally, and when passed through the GZip middleware (the latter dies without the change to make_bytes()). Removes the test with a nonsense Content-Encoding and Unicode input - if this were to happen, it can still be encoded as bytes fine. Backport of 250aa7c39b from master. --- AUTHORS | 1 + django/http/response.py | 4 ---- tests/httpwrappers/tests.py | 16 ++++++++-------- tests/middleware/tests.py | 12 ++++++++++++ 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/AUTHORS b/AUTHORS index b4863849dccd..bfc0107f642e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -452,6 +452,7 @@ answer newbie questions, and generally made Django that much better: Matt Deacalion Stevens Matt Dennenbaum Matthew Flanagan + Matthew Somerville Matthew Tretter Matthias Kestenholz Matthias Pronk diff --git a/django/http/response.py b/django/http/response.py index 560e2ac7a57b..eedd6c2c721d 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -282,10 +282,6 @@ def make_bytes(self, value): # an instance of a subclass, this function returns `bytes(value)`. # This doesn't make a copy when `value` already contains bytes. - # If content is already encoded (eg. gzip), assume bytes. - if self.has_header('Content-Encoding'): - return bytes(value) - # Handle string types -- we can't rely on force_bytes here because: # - under Python 3 it attempts str conversion first # - when self._charset != 'utf-8' it re-encodes the content diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py index cc324b76cd87..14ad306c6572 100644 --- a/tests/httpwrappers/tests.py +++ b/tests/httpwrappers/tests.py @@ -348,14 +348,6 @@ def test_iter_content(self): #'\xde\x9e' == unichr(1950).encode('utf-8') self.assertEqual(r.content, b'123\xde\x9e') - # with Content-Encoding header - r = HttpResponse() - r['Content-Encoding'] = 'winning' - r.content = [b'abc', b'def'] - self.assertEqual(r.content, b'abcdef') - self.assertRaises(TypeError if six.PY3 else UnicodeEncodeError, - setattr, r, 'content', ['\u079e']) - # .content can safely be accessed multiple times. r = HttpResponse(iter(['hello', 'world'])) self.assertEqual(r.content, r.content) @@ -512,6 +504,14 @@ def test_streaming_response(self): self.assertEqual(list(r), [b'abc', b'def']) self.assertEqual(list(r), []) + # iterating over Unicode strings still yields bytestring chunks. + r.streaming_content = iter(['hello', 'café']) + chunks = list(r) + # '\xc3\xa9' == unichr(233).encode('utf-8') + self.assertEqual(chunks, [b'hello', b'caf\xc3\xa9']) + for chunk in chunks: + self.assertIsInstance(chunk, six.binary_type) + # streaming responses don't have a `content` attribute. self.assertFalse(hasattr(r, 'content')) diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index 2b5b50f9e5ba..492f7a055a21 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -590,6 +590,7 @@ class GZipMiddlewareTest(TestCase): compressible_string = b'a' * 500 uncompressible_string = b''.join(six.int2byte(random.randint(0, 255)) for _ in range(500)) sequence = [b'a' * 500, b'b' * 200, b'a' * 300] + sequence_unicode = ['a' * 500, 'é' * 200, 'a' * 300] def setUp(self): self.req = RequestFactory().get('/') @@ -601,6 +602,8 @@ def setUp(self): self.resp['Content-Type'] = 'text/html; charset=UTF-8' self.stream_resp = StreamingHttpResponse(self.sequence) self.stream_resp['Content-Type'] = 'text/html; charset=UTF-8' + self.stream_resp_unicode = StreamingHttpResponse(self.sequence_unicode) + self.stream_resp_unicode['Content-Type'] = 'text/html; charset=UTF-8' @staticmethod def decompress(gzipped_string): @@ -624,6 +627,15 @@ def test_compress_streaming_response(self): self.assertEqual(r.get('Content-Encoding'), 'gzip') self.assertFalse(r.has_header('Content-Length')) + def test_compress_streaming_response_unicode(self): + """ + Tests that compression is performed on responses with streaming Unicode content. + """ + r = GZipMiddleware().process_response(self.req, self.stream_resp_unicode) + self.assertEqual(self.decompress(b''.join(r)), b''.join(x.encode('utf-8') for x in self.sequence_unicode)) + self.assertEqual(r.get('Content-Encoding'), 'gzip') + self.assertFalse(r.has_header('Content-Length')) + def test_compress_file_response(self): """ Tests that compression is performed on FileResponse. From ea3e40c278eb27fb3c8362d9e8cd67f957bf4f57 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 31 Jan 2015 16:05:16 +0100 Subject: [PATCH 0084/1125] [1.8.x] Fixed #24252 -- Forced lazy __str__ to utf-8 on Python 2 Thanks Stanislas Guerra for the report and Tomas Ehrlich for the review. Backport of cd0ceaa102 from master. --- django/utils/functional.py | 4 ++++ tests/urlpatterns_reverse/tests.py | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/django/utils/functional.py b/django/utils/functional.py index aa0d6f850a51..37cc7c7f0f29 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -120,6 +120,7 @@ def __prepare_class__(cls): cls.__str__ = cls.__text_cast else: cls.__unicode__ = cls.__text_cast + cls.__str__ = cls.__bytes_cast_encoded elif cls._delegate_bytes: if six.PY3: cls.__bytes__ = cls.__bytes_cast @@ -142,6 +143,9 @@ def __text_cast(self): def __bytes_cast(self): return bytes(func(*self.__args, **self.__kw)) + def __bytes_cast_encoded(self): + return func(*self.__args, **self.__kw).encode('utf-8') + def __cast(self): if self._delegate_bytes: return self.__bytes_cast() diff --git a/tests/urlpatterns_reverse/tests.py b/tests/urlpatterns_reverse/tests.py index bb732d2fec3c..d2fa1a982571 100644 --- a/tests/urlpatterns_reverse/tests.py +++ b/tests/urlpatterns_reverse/tests.py @@ -318,6 +318,17 @@ def test_user_permission_with_lazy_reverse(self): response = self.client.get('/login_required_view/') self.assertEqual(response.status_code, 200) + def test_inserting_reverse_lazy_into_string(self): + self.assertEqual( + 'Some URL: %s' % reverse_lazy('some-login-page'), + 'Some URL: /login/' + ) + if six.PY2: + self.assertEqual( + b'Some URL: %s' % reverse_lazy('some-login-page'), + 'Some URL: /login/' + ) + class ReverseLazySettingsTest(AdminScriptTestCase): """ From b35c2261543be619f7fef0d74154611ce2924f26 Mon Sep 17 00:00:00 2001 From: minusf Date: Tue, 3 Feb 2015 14:03:05 +0100 Subject: [PATCH 0085/1125] [1.8.x] Fixed typos in docs/ref/forms/validation.txt. Backport of 66f5aa9fa5d53ddd7fbdb7ddac39c429f0c1b4fd from master --- docs/ref/forms/validation.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index 5f57e1b8f01a..e45a0e6d6743 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -36,7 +36,7 @@ overridden: ``ValidationError``. * The ``validate()`` method on a Field handles field-specific validation - that is not suitable for a validator, It takes a value that has been + that is not suitable for a validator. It takes a value that has been coerced to correct datatype and raises ``ValidationError`` on any error. This method does not return anything and shouldn't alter the value. You should override it to handle validation logic that you can't or don't @@ -259,7 +259,7 @@ available and for an example of how to write a validator. Form field default cleaning ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Let's firstly create a custom form field that validates its input is a string +Let's first create a custom form field that validates its input is a string containing comma-separated email addresses. The full class looks like this:: from django import forms From ff39de1e1e3d5044611105e2ee654a5e438f4525 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 3 Feb 2015 13:09:54 -0500 Subject: [PATCH 0086/1125] [1.8.x] Added a "Writing migrations" how-to. Backport of 570912a97d5051fa3aeacd9d16c3be9afcf92198 from master --- docs/howto/index.txt | 1 + docs/howto/writing-migrations.txt | 69 +++++++++++++++++++++++++++++++ docs/index.txt | 3 +- docs/topics/migrations.txt | 69 +++---------------------------- 4 files changed, 78 insertions(+), 64 deletions(-) create mode 100644 docs/howto/writing-migrations.txt diff --git a/docs/howto/index.txt b/docs/howto/index.txt index 091afe69bc36..f06cd6b627d7 100644 --- a/docs/howto/index.txt +++ b/docs/howto/index.txt @@ -26,6 +26,7 @@ you quickly accomplish common tasks. static-files/index static-files/deployment windows + writing-migrations .. seealso:: diff --git a/docs/howto/writing-migrations.txt b/docs/howto/writing-migrations.txt new file mode 100644 index 000000000000..7edea84329b1 --- /dev/null +++ b/docs/howto/writing-migrations.txt @@ -0,0 +1,69 @@ +=========================== +Writing database migrations +=========================== + +This document explains how to structure and write database migrations for +different scenarios you might encounter. For introductory material on +migrations, see :doc:`the topic guide `. + +.. _data-migrations-and-multiple-databases: + +Data migrations and multiple databases +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using multiple databases, you may need to figure out whether or not to +run a migration against a particular database. For example, you may want to +**only** run a migration on a particular database. + +In order to do that you can check the database connection's alias inside a +``RunPython`` operation by looking at the ``schema_editor.connection.alias`` +attribute:: + + from django.db import migrations + + def forwards(apps, schema_editor): + if not schema_editor.connection.alias == 'default': + return + # Your migration code goes here + + class Migration(migrations.Migration): + + dependencies = [ + # Dependencies to other migrations + ] + + operations = [ + migrations.RunPython(forwards), + ] + +.. versionadded:: 1.8 + +You can also provide hints that will be passed to the :meth:`allow_migrate()` +method of database routers as ``**hints``: + +.. snippet:: + :filename: myapp/dbrouters.py + + class MyRouter(object): + + def allow_migrate(self, db, model, **hints): + if 'target_db' in hints: + return db == hints['target_db'] + return True + +Then, to leverage this in your migrations, do the following:: + + from django.db import migrations + + def forwards(apps, schema_editor): + # Your migration code goes here + + class Migration(migrations.Migration): + + dependencies = [ + # Dependencies to other migrations + ] + + operations = [ + migrations.RunPython(forwards, hints={'target_db': 'default'}), + ] diff --git a/docs/index.txt b/docs/index.txt index f4074ca5f2bb..58c63206b93c 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -76,7 +76,8 @@ manipulating the data of your Web application. Learn more about it below: * **Migrations:** :doc:`Introduction to Migrations` | :doc:`Operations reference ` | - :doc:`SchemaEditor ` + :doc:`SchemaEditor ` | + :doc:`Writing migrations ` * **Advanced:** :doc:`Managers ` | diff --git a/docs/topics/migrations.txt b/docs/topics/migrations.txt index b0bc13b32a1e..7944b61dcb6b 100644 --- a/docs/topics/migrations.txt +++ b/docs/topics/migrations.txt @@ -515,74 +515,13 @@ You can pass a second callable to want executed when migrating backwards. If this callable is omitted, migrating backwards will raise an exception. -.. _data-migrations-and-multiple-databases: - -Data migrations and multiple databases -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When using multiple databases, you may need to figure out whether or not to -run a migration against a particular database. For example, you may want to -**only** run a migration on a particular database. - -In order to do that you can check the database connection's alias inside a -``RunPython`` operation by looking at the ``schema_editor.connection.alias`` -attribute:: - - from django.db import migrations - - def forwards(apps, schema_editor): - if not schema_editor.connection.alias == 'default': - return - # Your migration code goes here - - class Migration(migrations.Migration): - - dependencies = [ - # Dependencies to other migrations - ] - - operations = [ - migrations.RunPython(forwards), - ] - -.. versionadded:: 1.8 - -You can also provide hints that will be passed to the :meth:`allow_migrate()` -method of database routers as ``**hints``: - -.. snippet:: - :filename: myapp/dbrouters.py - - class MyRouter(object): - - def allow_migrate(self, db, model, **hints): - if 'target_db' in hints: - return db == hints['target_db'] - return True - -Then, to leverage this in your migrations, do the following:: - - from django.db import migrations - - def forwards(apps, schema_editor): - # Your migration code goes here - - class Migration(migrations.Migration): - - dependencies = [ - # Dependencies to other migrations - ] - - operations = [ - migrations.RunPython(forwards, hints={'target_db': 'default'}), - ] - 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 -`. +` and the "how-to" on :doc:`writing migrations +`. .. _migration-squashing: @@ -849,3 +788,7 @@ More information is available in the :doc:`The Migrations Operations Reference ` Covers the schema operations API, special operations, and writing your own operations. + + :doc:`The Writing Migrations "how-to" ` + Explains how to structure and write database migrations for different + scenarios you might encounter. From 92d5bedc56155e4c91c7644200415b3e42cdd31f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 3 Feb 2015 15:12:28 -0500 Subject: [PATCH 0087/1125] [1.8.x] Reverted "Fixed #24146 -- Fixed a missing fields regression in admin checks." This reverts commit e8171daf0cd7f0e070395cb4c850c17fea32f11d. A new solution is forthcoming. Backport of 0e489c19f1554ecfd9825daacfbac73be8ce723e from master --- django/db/models/options.py | 28 ++++++++-------------------- tests/model_meta/tests.py | 16 +++++----------- 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/django/db/models/options.py b/django/db/models/options.py index 7daf7563917d..03470408e099 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -487,12 +487,6 @@ def _forward_fields_map(self): @cached_property def fields_map(self): - return self._get_fields_map() - - def _get_fields_map(self): - # Helper method to provide a way to access this without caching it. - # For example, admin checks run before the app cache is ready and we - # need to be able to lookup fields before we cache the final result. res = {} fields = self._get_fields(forward=False, include_hidden=True) for field in fields: @@ -537,26 +531,20 @@ def get_field(self, field_name, many_to_many=None): return field except KeyError: - pass - - if m2m_in_kwargs: - # Previous API does not allow searching reverse fields. - raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name)) - - # If the app registry is not ready, reverse fields are probably - # unavailable, but try anyway. - if not self.apps.ready: - try: - # Don't cache results - return self._get_fields_map()[field_name] - except KeyError: + # If the app registry is not ready, reverse fields are + # unavailable, therefore we throw a FieldDoesNotExist exception. + if not self.apps.ready: raise FieldDoesNotExist( "%s has no field named %r. The app cache isn't ready yet, " - "so if this is an auto-created related field, it might not " + "so if this is an auto-created related field, it won't " "be available yet." % (self.object_name, field_name) ) try: + if m2m_in_kwargs: + # Previous API does not allow searching reverse fields. + raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name)) + # Retrieve field instance by name from cached or just-computed # field map. return self.fields_map[field_name] diff --git a/tests/model_meta/tests.py b/tests/model_meta/tests.py index bc7927c31392..1a0c2de9f335 100644 --- a/tests/model_meta/tests.py +++ b/tests/model_meta/tests.py @@ -169,30 +169,24 @@ def test_get_generic_relation(self): self.assertEqual(field_info[1:], (None, True, False)) self.assertIsInstance(field_info[0], GenericRelation) - def test_get_fields_when_apps_not_ready(self): + def test_get_fields_only_searches_forward_on_apps_not_ready(self): opts = Person._meta # If apps registry is not ready, get_field() searches over only # forward fields. opts.apps.ready = False - # Clear cached data. - opts.__dict__.pop('fields_map', None) try: # 'data_abstract' is a forward field, and therefore will be found self.assertTrue(opts.get_field('data_abstract')) msg = ( - "Person has no field named 'some_missing_field'. The app " + "Person has no field named 'relating_baseperson'. The app " "cache isn't ready yet, so if this is an auto-created related " - "field, it might not be available yet." + "field, it won't be available yet." ) + # 'data_abstract' is a reverse field, and will raise an exception with self.assertRaisesMessage(FieldDoesNotExist, msg): - opts.get_field('some_missing_field') - # Be sure it's not cached - self.assertNotIn('fields_map', opts.__dict__) + opts.get_field('relating_baseperson') finally: opts.apps.ready = True - # At this point searching a related field would cache fields_map - opts.get_field('relating_baseperson') - self.assertIn('fields_map', opts.__dict__) class RelationTreeTests(TestCase): From dd83bab9316fff94637040344faf725a499a5abf Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 3 Feb 2015 16:35:28 -0500 Subject: [PATCH 0088/1125] [1.8.x] Demoted "Installing a distribution-specific package" in install notes. Backport of 281fc03474ac18c8281ed4cf289128c87bda2030 from master --- docs/topics/install.txt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 65e55ddbe659..73333ae6aa75 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -166,14 +166,6 @@ release, or fetching the latest development version. It's easy, no matter which way you choose. -Installing a distribution-specific package -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Check the :doc:`distribution specific notes ` to see if -your platform/distribution provides official Django packages/installers. -Distribution-provided packages will typically allow for automatic installation -of dependencies and easy upgrade paths. - .. _installing-official-release: Installing an official release with ``pip`` @@ -238,6 +230,15 @@ Installing an official release manually .. _bsdtar: http://gnuwin32.sourceforge.net/packages/bsdtar.htm .. _7-zip: http://www.7-zip.org/ +Installing a distribution-specific package +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Check the :doc:`distribution specific notes ` to see if +your platform/distribution provides official Django packages/installers. +Distribution-provided packages will typically allow for automatic installation +of dependencies and easy upgrade paths; however, these packages will rarely +contain the latest release of Django. + .. _installing-development-version: Installing the development version From cbcf92e95f8a6d275b55069de3c57835814b502f Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Tue, 3 Feb 2015 15:13:43 -0500 Subject: [PATCH 0089/1125] [1.8.x] Fixed #24266 -- Changed get_parent_list to return a list ordered by MRO. Thanks to Aron Podrigal for the initial patch and Tim for the review. Backport of 65e005f8cd9c656e558e53e6c8b890cd0fcc9e74 from master --- django/db/models/options.py | 14 +++++++------- tests/model_meta/models.py | 17 +++++++++++++++++ tests/model_meta/tests.py | 13 ++++++++++++- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/django/db/models/options.py b/django/db/models/options.py index 03470408e099..fa9726556b6d 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -12,7 +12,7 @@ from django.db.models.fields import AutoField from django.db.models.fields.proxy import OrderWrt from django.utils import six -from django.utils.datastructures import ImmutableList +from django.utils.datastructures import ImmutableList, OrderedSet from django.utils.deprecation import RemovedInDjango20Warning from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible from django.utils.functional import cached_property @@ -634,14 +634,14 @@ def get_base_chain(self, model): def get_parent_list(self): """ - Returns a list of all the ancestor of this model as a list. Useful for - determining if something is an ancestor, regardless of lineage. + Returns all the ancestors of this model as a list ordered by MRO. + Useful for determining if something is an ancestor, regardless of lineage. """ - result = set() + result = OrderedSet(self.parents) for parent in self.parents: - result.add(parent) - result.update(parent._meta.get_parent_list()) - return result + for ancestor in parent._meta.get_parent_list(): + result.add(ancestor) + return list(result) def get_ancestor_link(self, ancestor): """ diff --git a/tests/model_meta/models.py b/tests/model_meta/models.py index ab9713dc385c..b4ed9f89d2cb 100644 --- a/tests/model_meta/models.py +++ b/tests/model_meta/models.py @@ -118,3 +118,20 @@ class Relating(models.Model): # ManyToManyField to Person people = models.ManyToManyField(Person, related_name='relating_people') people_hidden = models.ManyToManyField(Person, related_name='+') + + +# ParentListTests models +class CommonAncestor(models.Model): + pass + + +class FirstParent(CommonAncestor): + first_ancestor = models.OneToOneField(CommonAncestor, primary_key=True, parent_link=True) + + +class SecondParent(CommonAncestor): + second_ancestor = models.OneToOneField(CommonAncestor, primary_key=True, parent_link=True) + + +class Child(FirstParent, SecondParent): + pass diff --git a/tests/model_meta/tests.py b/tests/model_meta/tests.py index 1a0c2de9f335..36b65f5ab810 100644 --- a/tests/model_meta/tests.py +++ b/tests/model_meta/tests.py @@ -5,7 +5,10 @@ from django.db.models.options import IMMUTABLE_WARNING, EMPTY_RELATION_TREE from django.test import TestCase -from .models import Relation, AbstractPerson, BasePerson, Person, ProxyPerson, Relating +from .models import ( + Relation, AbstractPerson, BasePerson, Person, ProxyPerson, Relating, + CommonAncestor, FirstParent, SecondParent, Child +) from .results import TEST_RESULTS @@ -245,3 +248,11 @@ def test_relations_related_objects(self): ]) ) self.assertEqual([field.related_query_name() for field in AbstractPerson._meta._relation_tree], []) + + +class ParentListTests(TestCase): + def test_get_parent_list(self): + self.assertEqual(CommonAncestor._meta.get_parent_list(), []) + self.assertEqual(FirstParent._meta.get_parent_list(), [CommonAncestor]) + self.assertEqual(SecondParent._meta.get_parent_list(), [CommonAncestor]) + self.assertEqual(Child._meta.get_parent_list(), [FirstParent, SecondParent, CommonAncestor]) From 3e24ab6f4ca6632c910c684fda3d0f937fade52a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 3 Feb 2015 18:02:59 -0500 Subject: [PATCH 0090/1125] [1.8.x] Refactored tests that rely on an ImportError for Python 3.5 compatibility A change in Python test discovery [1] causes the old packages that raised an error to be discovered; now we use a common directory that's ignored during discovery. Refs #23763. [1] http://bugs.python.org/issue7559 Backport of c0cc8f69e7abfa8578729031f97ae4b96c5cdafe from master --- tests/apps/failing_app/__init__.py | 1 - tests/apps/tests.py | 2 +- tests/import_error_package/__init__.py | 3 +++ tests/migrations/faulty_migrations/import_error/__init__.py | 1 - tests/migrations/test_loader.py | 2 +- tests/runtests.py | 1 + 6 files changed, 6 insertions(+), 4 deletions(-) delete mode 100644 tests/apps/failing_app/__init__.py create mode 100644 tests/import_error_package/__init__.py delete mode 100644 tests/migrations/faulty_migrations/import_error/__init__.py diff --git a/tests/apps/failing_app/__init__.py b/tests/apps/failing_app/__init__.py deleted file mode 100644 index 4ec7f18d40d4..000000000000 --- a/tests/apps/failing_app/__init__.py +++ /dev/null @@ -1 +0,0 @@ -raise ImportError("Oops") diff --git a/tests/apps/tests.py b/tests/apps/tests.py index f6bef17e42ac..b7652ed355a3 100644 --- a/tests/apps/tests.py +++ b/tests/apps/tests.py @@ -172,7 +172,7 @@ def test_import_exception_is_not_masked(self): App discovery should preserve stack traces. Regression test for #22920. """ with six.assertRaisesRegex(self, ImportError, "Oops"): - with self.settings(INSTALLED_APPS=['apps.failing_app']): + with self.settings(INSTALLED_APPS=['import_error_package']): pass def test_models_py(self): diff --git a/tests/import_error_package/__init__.py b/tests/import_error_package/__init__.py new file mode 100644 index 000000000000..c872aec5a767 --- /dev/null +++ b/tests/import_error_package/__init__.py @@ -0,0 +1,3 @@ +# A package that raises an ImportError that can be shared among test apps and +# excluded from test discovery. +raise ImportError("Oops") diff --git a/tests/migrations/faulty_migrations/import_error/__init__.py b/tests/migrations/faulty_migrations/import_error/__init__.py deleted file mode 100644 index a07bc4fa6df2..000000000000 --- a/tests/migrations/faulty_migrations/import_error/__init__.py +++ /dev/null @@ -1 +0,0 @@ -import fake_python_module # NOQA diff --git a/tests/migrations/test_loader.py b/tests/migrations/test_loader.py index 4416a86bc900..8df7b7aa9b1a 100644 --- a/tests/migrations/test_loader.py +++ b/tests/migrations/test_loader.py @@ -159,7 +159,7 @@ def test_name_match(self): migration_loader.get_migration_by_prefix("migrations", "blarg") def test_load_import_error(self): - with override_settings(MIGRATION_MODULES={"migrations": "migrations.faulty_migrations.import_error"}): + with override_settings(MIGRATION_MODULES={"migrations": "import_error_package"}): with self.assertRaises(ImportError): MigrationLoader(connection) diff --git a/tests/runtests.py b/tests/runtests.py index f3ec6407c9f6..1c6b4d569f7b 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -35,6 +35,7 @@ SUBDIRS_TO_SKIP = [ 'data', + 'import_error_package', 'test_discovery_sample', 'test_discovery_sample2', 'test_runner_deprecation_app', From fc49e736484892eaa516826d93660c7ab53a6c3e Mon Sep 17 00:00:00 2001 From: Aron Podrigal Date: Wed, 21 Jan 2015 22:15:59 -0500 Subject: [PATCH 0091/1125] [1.8.x] Fixed #15321 -- Honored ancestors unique checks. Thanks to Tim for the review. Backport of 79f27f2b61aeac763ae048416ef8a97c2b639ae8 from master --- django/db/models/base.py | 4 ++-- tests/model_inheritance/models.py | 21 +++++++++++++++++++ tests/model_inheritance/tests.py | 34 +++++++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index 3d65cc3d9dc3..7bc726a617f3 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -939,7 +939,7 @@ def _get_unique_checks(self, exclude=None): unique_checks = [] unique_togethers = [(self.__class__, self._meta.unique_together)] - for parent_class in self._meta.parents.keys(): + for parent_class in self._meta.get_parent_list(): if parent_class._meta.unique_together: unique_togethers.append((parent_class, parent_class._meta.unique_together)) @@ -959,7 +959,7 @@ def _get_unique_checks(self, exclude=None): # the list of checks. fields_with_class = [(self.__class__, self._meta.local_fields)] - for parent_class in self._meta.parents.keys(): + for parent_class in self._meta.get_parent_list(): fields_with_class.append((parent_class, parent_class._meta.local_fields)) for model_class, fields in fields_with_class: diff --git a/tests/model_inheritance/models.py b/tests/model_inheritance/models.py index 3245c7123224..af28159757dc 100644 --- a/tests/model_inheritance/models.py +++ b/tests/model_inheritance/models.py @@ -186,3 +186,24 @@ class Base(models.Model): class SubBase(Base): sub_id = models.IntegerField(primary_key=True) + + +class GrandParent(models.Model): + first_name = models.CharField(max_length=80) + last_name = models.CharField(max_length=80) + email = models.EmailField(unique=True) + + class Meta: + unique_together = ('first_name', 'last_name') + + +class Parent(GrandParent): + pass + + +class Child(Parent): + pass + + +class GrandChild(Child): + pass diff --git a/tests/model_inheritance/tests.py b/tests/model_inheritance/tests.py index 10721a6b3f5b..7ee7e19a1259 100644 --- a/tests/model_inheritance/tests.py +++ b/tests/model_inheritance/tests.py @@ -2,7 +2,7 @@ from operator import attrgetter -from django.core.exceptions import FieldError +from django.core.exceptions import FieldError, ValidationError from django.core.management import call_command from django.db import connection from django.test import TestCase, TransactionTestCase @@ -12,7 +12,7 @@ from .models import ( Chef, CommonInfo, ItalianRestaurant, ParkingLot, Place, Post, Restaurant, Student, Supplier, Worker, MixinModel, - Title, Copy, Base, SubBase) + Title, Copy, Base, SubBase, GrandParent, GrandChild) class ModelInheritanceTests(TestCase): @@ -423,3 +423,33 @@ def test_inheritance_with_same_model_name(self): def test_related_name_attribute_exists(self): # The Post model doesn't have an attribute called 'attached_%(app_label)s_%(class)s_set'. self.assertFalse(hasattr(self.title, 'attached_%(app_label)s_%(class)s_set')) + + +class InheritanceUniqueTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.grand_parent = GrandParent.objects.create( + email='grand_parent@example.com', + first_name='grand', + last_name='parent', + ) + + def test_unique(self): + grand_child = GrandChild( + email=self.grand_parent.email, + first_name='grand', + last_name='child', + ) + msg = 'Grand parent with this Email already exists.' + with self.assertRaisesMessage(ValidationError, msg): + grand_child.validate_unique() + + def test_unique_together(self): + grand_child = GrandChild( + email='grand_child@example.com', + first_name=self.grand_parent.first_name, + last_name=self.grand_parent.last_name, + ) + msg = 'Grand parent with this First name and Last name already exists.' + with self.assertRaisesMessage(ValidationError, msg): + grand_child.validate_unique() From 16e3910e9cb93550a500553b2fb3da2b1822478b Mon Sep 17 00:00:00 2001 From: Josh Schneier Date: Wed, 4 Feb 2015 01:01:59 -0500 Subject: [PATCH 0092/1125] [1.8.x] Fixed typos of "select_related" in docs and tests. Backport of 7d363ed43247a80d2b764723e1bf6e0e6da4e82f from master --- docs/ref/models/querysets.txt | 4 ++-- tests/defer/tests.py | 4 ++-- tests/defer_regress/tests.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 6dca128cbdee..2f3168c83e08 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -805,8 +805,8 @@ You can use ``select_related()`` with any queryset of objects:: The order of ``filter()`` and ``select_related()`` chaining isn't important. These querysets are equivalent:: - Entry.objects.filter(pub_date__gt=timezone.now()).selected_related('blog') - Entry.objects.selected_related('blog').filter(pub_date__gt=timezone.now()) + Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog') + Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now()) You can follow foreign keys in a similar way to querying them. If you have the following models:: diff --git a/tests/defer/tests.py b/tests/defer/tests.py index acc87d88b6a0..84719761733f 100644 --- a/tests/defer/tests.py +++ b/tests/defer/tests.py @@ -109,13 +109,13 @@ def test_only_with_select_related(self): self.assertEqual(obj.related_id, self.s1.pk) self.assertEqual(obj.name, "p1") - def test_defer_selected_related_raises_invalid_query(self): + def test_defer_select_related_raises_invalid_query(self): # When we defer a field and also select_related it, the query is # invalid and raises an exception. with self.assertRaises(InvalidQuery): Primary.objects.defer("related").select_related("related")[0] - def test_only_selected_related_raises_invalid_query(self): + def test_only_select_related_raises_invalid_query(self): with self.assertRaises(InvalidQuery): Primary.objects.only("name").select_related("related")[0] diff --git a/tests/defer_regress/tests.py b/tests/defer_regress/tests.py index 1222212d8d8a..87638bb99d21 100644 --- a/tests/defer_regress/tests.py +++ b/tests/defer_regress/tests.py @@ -219,7 +219,7 @@ def test_defer_with_select_related(self): self.assertEqual(obj.item, item2) self.assertEqual(obj.item_id, item2.id) - def test_proxy_model_defer_with_selected_related(self): + def test_proxy_model_defer_with_select_related(self): # Regression for #22050 item = Item.objects.create(name="first", value=47) RelatedItem.objects.create(item=item) From 4957b8a40615a082c9218c7c662fb8fccf46b14f Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Sat, 5 Apr 2014 17:58:05 +0200 Subject: [PATCH 0093/1125] [1.8.x] Fixed #14497 -- Improved admin widget for "read only" FileFields Based on patch by Adam J Forster, Paul Collins, and Julien. Backport of 2be621e44c1b5b68c895383f3e20b1f17ddb447a from master --- django/contrib/admin/utils.py | 18 ++++++++----- tests/admin_widgets/tests.py | 43 +++++++++++++++++++++++++----- tests/admin_widgets/widgetadmin.py | 7 ++++- 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py index 0f617b3fb964..95cbe634b2ee 100644 --- a/django/contrib/admin/utils.py +++ b/django/contrib/admin/utils.py @@ -1,23 +1,22 @@ from __future__ import unicode_literals -from collections import defaultdict import datetime import decimal +from collections import defaultdict from django.contrib.auth import get_permission_codename from django.core.exceptions import FieldDoesNotExist +from django.core.urlresolvers import reverse, NoReverseMatch from django.db import models from django.db.models.constants import LOOKUP_SEP from django.db.models.deletion import Collector from django.forms.forms import pretty_name -from django.utils import formats -from django.utils.html import format_html -from django.utils.text import capfirst -from django.utils import timezone +from django.utils import formats, six, timezone from django.utils.encoding import force_str, force_text, smart_text -from django.utils import six +from django.utils.html import conditional_escape, format_html +from django.utils.safestring import mark_safe +from django.utils.text import capfirst from django.utils.translation import ungettext -from django.core.urlresolvers import reverse, NoReverseMatch def lookup_needs_distinct(opts, lookup_path): @@ -389,6 +388,11 @@ def display_for_field(value, field): return formats.number_format(value, field.decimal_places) elif isinstance(field, models.FloatField): return formats.number_format(value) + elif isinstance(field, models.FileField): + return mark_safe('%s' % ( + conditional_escape(value.url), + conditional_escape(value), + )) else: return smart_text(value) diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index c5795c87fb41..ac0854ca257a 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -350,26 +350,55 @@ def test_render_quoting(self): ) -class AdminFileWidgetTest(DjangoTestCase): - def test_render(self): +@override_settings( + PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'], + ROOT_URLCONF='admin_widgets.urls', +) +class AdminFileWidgetTests(DjangoTestCase): + fixtures = ['admin-widgets-users.xml'] + + def setUp(self): band = models.Band.objects.create(name='Linkin Park') - album = band.album_set.create( + self.album = band.album_set.create( name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg' ) + def test_render(self): w = widgets.AdminFileWidget() self.assertHTMLEqual( - w.render('test', album.cover_art), - '

Currently: albums\hybrid_theory.jpg
Change:

' % { - 'STORAGE_URL': default_storage.url('') + w.render('test', self.album.cover_art), + '

Currently: albums\hybrid_theory.jpg ' + '' + ' ' + '
' + 'Change:

' % { + 'STORAGE_URL': default_storage.url(''), }, ) - self.assertHTMLEqual( w.render('test', SimpleUploadedFile('test', b'content')), '', ) + def test_readonly_fields(self): + """ + File widgets should render as a link when they're marked "read only." + """ + self.client.login(username="super", password="secret") + response = self.client.get('/admin_widgets/album/%s/' % self.album.id) + self.assertContains( + response, + '

' + 'albums\hybrid_theory.jpg

' % {'STORAGE_URL': default_storage.url('')}, + html=True, + ) + self.assertNotContains( + response, + '', + html=True, + ) + @override_settings(ROOT_URLCONF='admin_widgets.urls') class ForeignKeyRawIdWidgetTest(DjangoTestCase): diff --git a/tests/admin_widgets/widgetadmin.py b/tests/admin_widgets/widgetadmin.py index fa4653003701..6b205b70dcd7 100644 --- a/tests/admin_widgets/widgetadmin.py +++ b/tests/admin_widgets/widgetadmin.py @@ -24,6 +24,11 @@ class EventAdmin(admin.ModelAdmin): raw_id_fields = ['main_band', 'supporting_bands'] +class AlbumAdmin(admin.ModelAdmin): + fields = ('name', 'cover_art',) + readonly_fields = ('cover_art',) + + class SchoolAdmin(admin.ModelAdmin): filter_vertical = ('students',) filter_horizontal = ('alumni',) @@ -37,7 +42,7 @@ class SchoolAdmin(admin.ModelAdmin): site.register(models.Member) site.register(models.Band) site.register(models.Event, EventAdmin) -site.register(models.Album) +site.register(models.Album, AlbumAdmin) site.register(models.Inventory) From d585ade0dfef523c1c4be9fe6dad2dfaf3d1583d Mon Sep 17 00:00:00 2001 From: mlavin Date: Sun, 25 Jan 2015 09:53:40 -0500 Subject: [PATCH 0094/1125] [1.8.x] Fixed #24197 -- Added clearing of staticfiles caches on settings changes during tests Cleared caching in staticfiles_storage and get_finder when relevant settings are changed. Backport of 2730dad0d7c425f33f1ecc6cec01fdbf1cdd8376 from master --- django/test/signals.py | 21 ++++++++++++ tests/staticfiles_tests/tests.py | 19 ++++------- tests/test_utils/tests.py | 56 ++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 12 deletions(-) diff --git a/django/test/signals.py b/django/test/signals.py index 6888753b0686..56a8945e0170 100644 --- a/django/test/signals.py +++ b/django/test/signals.py @@ -156,3 +156,24 @@ def root_urlconf_changed(**kwargs): from django.core.urlresolvers import clear_url_caches, set_urlconf clear_url_caches() set_urlconf(None) + + +@receiver(setting_changed) +def static_storage_changed(**kwargs): + if kwargs['setting'] in { + 'STATICFILES_STORAGE', + 'STATIC_ROOT', + 'STATIC_URL', + }: + from django.contrib.staticfiles.storage import staticfiles_storage + staticfiles_storage._wrapped = empty + + +@receiver(setting_changed) +def static_finders_changed(**kwargs): + if kwargs['setting'] in { + 'STATICFILES_DIRS', + 'STATIC_ROOT', + }: + from django.contrib.staticfiles.finders import get_finder + get_finder.cache_clear() diff --git a/tests/staticfiles_tests/tests.py b/tests/staticfiles_tests/tests.py index 1d5ccfc0753a..60615ce1e0a1 100644 --- a/tests/staticfiles_tests/tests.py +++ b/tests/staticfiles_tests/tests.py @@ -60,14 +60,6 @@ class BaseStaticFilesTestCase(object): Test case with a couple utility assertions. """ def setUp(self): - # Clear the cached staticfiles_storage out, this is because when it first - # gets accessed (by some other test), it evaluates settings.STATIC_ROOT, - # since we're planning on changing that we need to clear out the cache. - storage.staticfiles_storage._wrapped = empty - # Clear the cached staticfile finders, so they are reinitialized every - # run and pick up changes in settings.STATICFILES_DIRS. - finders.get_finder.cache_clear() - self.testfiles_path = os.path.join(TEST_ROOT, 'apps', 'test', 'static', 'test') # To make sure SVN doesn't hangs itself with the non-ASCII characters # during checkout, we actually create one file dynamically. @@ -130,15 +122,18 @@ class BaseCollectionTestCase(BaseStaticFilesTestCase): """ def setUp(self): super(BaseCollectionTestCase, self).setUp() - self.old_root = settings.STATIC_ROOT - settings.STATIC_ROOT = tempfile.mkdtemp(dir=os.environ['DJANGO_TEST_TEMP_DIR']) + temp_dir = tempfile.mkdtemp(dir=os.environ['DJANGO_TEST_TEMP_DIR']) + # Override the STATIC_ROOT for all tests from setUp to tearDown + # rather than as a context manager + self.patched_settings = self.settings(STATIC_ROOT=temp_dir) + self.patched_settings.enable() self.run_collectstatic() # Use our own error handler that can handle .svn dirs on Windows - self.addCleanup(shutil.rmtree, settings.STATIC_ROOT, + self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True, onerror=rmtree_errorhandler) def tearDown(self): - settings.STATIC_ROOT = self.old_root + self.patched_settings.disable() super(BaseCollectionTestCase, self).tearDown() def run_collectstatic(self, **kwargs): diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index f515de595078..1deda66b2e4e 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -4,6 +4,8 @@ import unittest from django.conf.urls import url +from django.contrib.staticfiles.finders import get_finders, get_finder +from django.contrib.staticfiles.storage import staticfiles_storage from django.core.files.storage import default_storage from django.core.urlresolvers import NoReverseMatch, reverse from django.db import connection, router @@ -14,6 +16,7 @@ from django.test.html import HTMLParseError, parse_html from django.test.utils import CaptureQueriesContext, override_settings from django.utils import six +from django.utils._os import abspathu from .models import Car, Person, PossessedCar from .views import empty_response @@ -850,3 +853,56 @@ def test_override_database_routers(self): test_routers = (object(),) with self.settings(DATABASE_ROUTERS=test_routers): self.assertSequenceEqual(router.routers, test_routers) + + def test_override_static_url(self): + """ + Overriding the STATIC_URL setting should be reflected in the + base_url attribute of + django.contrib.staticfiles.storage.staticfiles_storage. + """ + with self.settings(STATIC_URL='/test/'): + self.assertEqual(staticfiles_storage.base_url, '/test/') + + def test_override_static_root(self): + """ + Overriding the STATIC_ROOT setting should be reflected in the + location attribute of + django.contrib.staticfiles.storage.staticfiles_storage. + """ + with self.settings(STATIC_ROOT='/tmp/test'): + self.assertEqual(staticfiles_storage.location, abspathu('/tmp/test')) + + def test_override_staticfiles_storage(self): + """ + Overriding the STATICFILES_STORAGE setting should be reflected in + the value of django.contrib.staticfiles.storage.staticfiles_storage. + """ + new_class = 'CachedStaticFilesStorage' + new_storage = 'django.contrib.staticfiles.storage.' + new_class + with self.settings(STATICFILES_STORAGE=new_storage): + self.assertEqual(staticfiles_storage.__class__.__name__, new_class) + + def test_override_staticfiles_finders(self): + """ + Overriding the STATICFILES_FINDERS setting should be reflected in + the return value of django.contrib.staticfiles.finders.get_finders. + """ + current = get_finders() + self.assertGreater(len(list(current)), 1) + finders = ['django.contrib.staticfiles.finders.FileSystemFinder'] + with self.settings(STATICFILES_FINDERS=finders): + self.assertEqual(len(list(get_finders())), len(finders)) + + def test_override_staticfiles_dirs(self): + """ + Overriding the STATICFILES_DIRS setting should be reflected in + the locations attribute of the + django.contrib.staticfiles.finders.FileSystemFinder instance. + """ + finder = get_finder('django.contrib.staticfiles.finders.FileSystemFinder') + test_path = '/tmp/test' + expected_location = ('', test_path) + self.assertNotIn(expected_location, finder.locations) + with self.settings(STATICFILES_DIRS=[test_path]): + finder = get_finder('django.contrib.staticfiles.finders.FileSystemFinder') + self.assertIn(expected_location, finder.locations) From 2a55301f9fe1c3b62ad4e79c3109bec77b57b317 Mon Sep 17 00:00:00 2001 From: Matthew Somerville Date: Thu, 29 Jan 2015 07:59:41 +0000 Subject: [PATCH 0095/1125] [1.8.x] Fixed #24242 -- Improved efficiency of utils.text.compress_sequence() The function no longer flushes zfile after each write as doing so can lead to the gzipped streamed content being larger than the original content; each flush adds a 5/6 byte type 0 block. Removing this means buf.read() may return nothing, so only yield if that has some data. Testing shows without the flush() the buffer is being flushed every 17k or so and compresses the same as if it had been done as a whole string. Backport of caa3562d5bec1196502352a715a539bdb0f73c2d from master --- django/utils/text.py | 7 +++++-- tests/utils_tests/test_text.py | 12 +++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/django/utils/text.py b/django/utils/text.py index 37bcd3150e6a..8446b0a23887 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -304,6 +304,8 @@ def write(self, val): self.vals.append(val) def read(self): + if not self.vals: + return b'' ret = b''.join(self.vals) self.vals = [] return ret @@ -323,8 +325,9 @@ def compress_sequence(sequence): yield buf.read() for item in sequence: zfile.write(item) - zfile.flush() - yield buf.read() + data = buf.read() + if data: + yield data zfile.close() yield buf.read() diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py index 142963893e7c..084645da27e8 100644 --- a/tests/utils_tests/test_text.py +++ b/tests/utils_tests/test_text.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from unittest import skipUnless +import json import warnings +from unittest import skipUnless from django.test import SimpleTestCase, ignore_warnings from django.test.utils import reset_warning_registry @@ -198,6 +199,15 @@ def test_get_valid_filename(self): filename = "^&'@{}[],$=!-#()%+~_123.txt" self.assertEqual(text.get_valid_filename(filename), "-_123.txt") + def test_compress_sequence(self): + data = [{'key': i} for i in range(10)] + seq = list(json.JSONEncoder().iterencode(data)) + seq = [s.encode('utf-8') for s in seq] + actual_length = len(b''.join(seq)) + out = text.compress_sequence(seq) + compressed_length = len(b''.join(out)) + self.assertTrue(compressed_length < actual_length) + @ignore_warnings(category=RemovedInDjango19Warning) def test_javascript_quote(self): input = "" From f5749252eac68274b1e8f71b5e74c976199c360c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 5 Feb 2015 09:09:13 +0000 Subject: [PATCH 0096/1125] [1.8.x] Improved nested ArrayField example Backport of 737b184d914d5cc4a6ed8fe2a1d66ec1b7369f46 from master --- docs/ref/contrib/postgres/fields.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt index a06cefd162af..760286dcaced 100644 --- a/docs/ref/contrib/postgres/fields.txt +++ b/docs/ref/contrib/postgres/fields.txt @@ -40,9 +40,11 @@ ArrayField class ChessBoard(models.Model): board = ArrayField( ArrayField( - CharField(max_length=10, blank=True, null=True), - size=8), - size=8) + models.CharField(max_length=10, blank=True), + size=8, + ), + size=8, + ) Transformation of values between the database and the model, validation of data and configuration, and serialization are all delegated to the From 5fbec369aa32d1717b7bffc169649ab5896a7ad9 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 5 Feb 2015 13:20:33 +0100 Subject: [PATCH 0097/1125] [1.8.x] Fixed #24273 -- Allowed copying RequestContext more than once. Thanks Collin Anderson for the report. Backport of 31d3a355 from master --- django/template/context.py | 3 ++- tests/template_tests/test_context.py | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/django/template/context.py b/django/template/context.py index d72c70c37a14..9543931cec0e 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -232,5 +232,6 @@ def new(self, values=None): new_context = super(RequestContext, self).new(values) # This is for backwards-compatibility: RequestContexts created via # Context.new don't include values from context processors. - del new_context._processors_index + if hasattr(new_context, '_processors_index'): + del new_context._processors_index return new_context diff --git a/tests/template_tests/test_context.py b/tests/template_tests/test_context.py index c5ad3e1d5078..24d589fb25a0 100644 --- a/tests/template_tests/test_context.py +++ b/tests/template_tests/test_context.py @@ -2,7 +2,8 @@ from unittest import TestCase -from django.template import Context, Variable, VariableDoesNotExist +from django.http import HttpRequest +from django.template import Context, RequestContext, Variable, VariableDoesNotExist from django.template.context import RenderContext @@ -83,3 +84,7 @@ def test_context_comparable(self): # make contexts equals again b.update({'a': 1}) self.assertEqual(a, b) + + def test_copy_request_context_twice(self): + # Regression test for #24273 - this doesn't raise an exception + RequestContext(HttpRequest()).new().new() From 67787db22a3d684ce2800c80fca477af1684deab Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 5 Feb 2015 11:38:58 +0100 Subject: [PATCH 0098/1125] [1.8.x] Caught all exceptions raised by Engine.get_default(). In addition to ImproperlyConfigured, Engine.get_default() may also raise ImportError or other exceptions. It's better to catch all exceptions in places where the default engine isn't strictly required. Backport of 27f9ff45 from master --- django/contrib/admin/sites.py | 10 ++++++++-- django/views/debug.py | 7 +++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index ab0ec1a83780..bcd05ed800c8 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -177,8 +177,14 @@ def check_dependencies(self): "setting in order to use the admin application.") try: default_template_engine = Engine.get_default() - except ImproperlyConfigured: - # Skip the check if the user has a non-trivial TEMPLATES setting + except Exception: + # Skip this non-critical check: + # 1. if the user has a non-trivial TEMPLATES setting and Django + # can't find a default template engine + # 2. if anything goes wrong while loading template engines, in + # order to avoid raising an exception from a confusing location + # Catching ImproperlyConfigured suffices for 1. but 2. requires + # catching all exceptions. pass else: if ('django.contrib.auth.context_processors.auth' diff --git a/django/views/debug.py b/django/views/debug.py index 03387efaaf2f..19254c5cf252 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -7,7 +7,6 @@ import types from django.conf import settings -from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import resolve, Resolver404 from django.http import (HttpResponse, HttpResponseNotFound, HttpRequest, build_request_repr) @@ -282,7 +281,11 @@ def get_traceback_data(self): """Return a dictionary containing traceback information.""" try: default_template_engine = Engine.get_default() - except ImproperlyConfigured: + except Exception: + # Since the debug view must never crash, catch all exceptions. + # If Django can't find a default template engine, get_default() + # raises ImproperlyConfigured. If some template engines fail to + # load, any exception may be raised. default_template_engine = None # TODO: add support for multiple template engines (#24120). From aed1b1f6e5133f91c063f6f8b75729c4a18f99c7 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Thu, 5 Feb 2015 11:53:04 +0100 Subject: [PATCH 0099/1125] [1.8.x] Fixed #24265 -- Preserved template backend loading exceptions. If importing or initializing a template backend fails, attempting to access this template backend again must raise the same exception. Backport of 44ad6915 from master --- django/template/utils.py | 4 +++ tests/template_backends/test_utils.py | 37 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 tests/template_backends/test_utils.py diff --git a/django/template/utils.py b/django/template/utils.py index 3303c1ccb7ed..be2166810c08 100644 --- a/django/template/utils.py +++ b/django/template/utils.py @@ -90,6 +90,10 @@ def __getitem__(self, alias): "Could not find config for '{}' " "in settings.TEMPLATES".format(alias)) + # If importing or initializing the backend raises an exception, + # self._engines[alias] isn't set and this code may get executed + # again, so we must preserve the original params. See #24265. + params = params.copy() backend = params.pop('BACKEND') engine_cls = import_string(backend) engine = engine_cls(params) diff --git a/tests/template_backends/test_utils.py b/tests/template_backends/test_utils.py new file mode 100644 index 000000000000..3d2de990159f --- /dev/null +++ b/tests/template_backends/test_utils.py @@ -0,0 +1,37 @@ +from django.core.exceptions import ImproperlyConfigured +from django.template import engines +from django.test import SimpleTestCase, override_settings + + +class TemplateStringsTests(SimpleTestCase): + + @override_settings(TEMPLATES=[{ + 'BACKEND': 'raise.import.error', + }]) + def test_backend_import_error(self): + """ + Failing to import a backend keeps raising the original import error. + + Regression test for #24265. + """ + with self.assertRaises(ImportError): + engines.all() + with self.assertRaises(ImportError): + engines.all() + + @override_settings(TEMPLATES=[{ + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + # Incorrect: APP_DIRS and loaders are mutually incompatible. + 'APP_DIRS': True, + 'OPTIONS': {'loaders': []}, + }]) + def test_backend_improperly_configured(self): + """ + Failing to initialize a backend keeps raising the original exception. + + Regression test for #24265. + """ + with self.assertRaises(ImproperlyConfigured): + engines.all() + with self.assertRaises(ImproperlyConfigured): + engines.all() From e3702dc3f2af9e8cc6c1155b248f458899be334e Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Thu, 29 Jan 2015 15:14:55 +0100 Subject: [PATCH 0100/1125] [1.8.x] Cleaned up schema tests Thanks Tim Graham for the review. Backport of 0204714b0bdf10d7558ee106de9a718407f3ec5a from master --- tests/schema/fields.py | 4 + tests/schema/models.py | 90 ++---- tests/schema/tests.py | 652 ++++++++++++++++++----------------------- 3 files changed, 315 insertions(+), 431 deletions(-) diff --git a/tests/schema/fields.py b/tests/schema/fields.py index 4f70c96b0bee..d4302a6677cf 100644 --- a/tests/schema/fields.py +++ b/tests/schema/fields.py @@ -52,3 +52,7 @@ def get_internal_type(self): _get_m2m_attr = ManyToManyField.__dict__['_get_m2m_attr'] _get_m2m_reverse_attr = ManyToManyField.__dict__['_get_m2m_reverse_attr'] _get_m2m_db_table = ManyToManyField.__dict__['_get_m2m_db_table'] + + +class InheritedManyToManyField(ManyToManyField): + pass diff --git a/tests/schema/models.py b/tests/schema/models.py index 6eba7ccae11b..cf49d95abb6b 100644 --- a/tests/schema/models.py +++ b/tests/schema/models.py @@ -25,24 +25,9 @@ class Meta: apps = new_apps -class AuthorWithM2M(models.Model): - name = models.CharField(max_length=255) - - class Meta: - apps = new_apps - - -class AuthorWithM2MThrough(models.Model): +class AuthorWithEvenLongerName(models.Model): name = models.CharField(max_length=255) - tags = models.ManyToManyField("schema.TagM2MTest", related_name="authors", through="AuthorTag") - - class Meta: - apps = new_apps - - -class AuthorTag(models.Model): - author = models.ForeignKey("schema.AuthorWithM2MThrough") - tag = models.ForeignKey("schema.TagM2MTest") + height = models.PositiveIntegerField(null=True, blank=True) class Meta: apps = new_apps @@ -67,39 +52,21 @@ class Meta: apps = new_apps -class BookWithO2O(models.Model): - author = models.OneToOneField(Author) - title = models.CharField(max_length=100, db_index=True) - pub_date = models.DateTimeField() +class BookWithLongName(models.Model): + author_foreign_key_with_really_long_field_name = models.ForeignKey(AuthorWithEvenLongerName) class Meta: apps = new_apps - db_table = "schema_book" -class BookWithM2M(models.Model): - author = models.ForeignKey(Author) +class BookWithO2O(models.Model): + author = models.OneToOneField(Author) title = models.CharField(max_length=100, db_index=True) pub_date = models.DateTimeField() - tags = models.ManyToManyField("TagM2MTest", related_name="books") - - class Meta: - apps = new_apps - - -class TagThrough(models.Model): - book = models.ForeignKey("schema.BookWithM2MThrough") - tag = models.ForeignKey("schema.TagM2MTest") - - class Meta: - apps = new_apps - - -class BookWithM2MThrough(models.Model): - tags = models.ManyToManyField("TagM2MTest", related_name="books", through=TagThrough) class Meta: apps = new_apps + db_table = "schema_book" class BookWithSlug(models.Model): @@ -113,6 +80,10 @@ class Meta: db_table = "schema_book" +class Note(models.Model): + info = models.TextField() + + class Tag(models.Model): title = models.CharField(max_length=255) slug = models.SlugField(unique=True) @@ -121,21 +92,21 @@ class Meta: apps = new_apps -class TagM2MTest(models.Model): +class TagIndexed(models.Model): title = models.CharField(max_length=255) slug = models.SlugField(unique=True) class Meta: apps = new_apps + index_together = [["slug", "title"]] -class TagIndexed(models.Model): +class TagM2MTest(models.Model): title = models.CharField(max_length=255) slug = models.SlugField(unique=True) class Meta: apps = new_apps - index_together = [["slug", "title"]] class TagUniqueRename(models.Model): @@ -147,30 +118,6 @@ class Meta: db_table = "schema_tag" -class UniqueTest(models.Model): - year = models.IntegerField() - slug = models.SlugField(unique=False) - - class Meta: - apps = new_apps - unique_together = ["year", "slug"] - - -class AuthorWithEvenLongerName(models.Model): - name = models.CharField(max_length=255) - height = models.PositiveIntegerField(null=True, blank=True) - - class Meta: - apps = new_apps - - -class BookWithLongName(models.Model): - author_foreign_key_with_really_long_field_name = models.ForeignKey(AuthorWithEvenLongerName) - - class Meta: - apps = new_apps - - # Based on tests/reserved_names/models.py @python_2_unicode_compatible class Thing(models.Model): @@ -183,5 +130,10 @@ def __str__(self): return self.when -class Note(models.Model): - info = models.TextField() +class UniqueTest(models.Model): + year = models.IntegerField() + slug = models.SlugField(unique=False) + + class Meta: + apps = new_apps + unique_together = ["year", "slug"] diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 2dd56d7248e5..17040c1a69dc 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -1,17 +1,21 @@ import datetime +import itertools import unittest -from django.test import TransactionTestCase from django.db import connection, DatabaseError, IntegrityError, OperationalError -from django.db.models.fields import (BinaryField, BooleanField, CharField, IntegerField, - PositiveIntegerField, SlugField, TextField) +from django.db.models import Model +from django.db.models.fields import (BinaryField, BooleanField, CharField, DateTimeField, + IntegerField, PositiveIntegerField, SlugField, TextField) from django.db.models.fields.related import ForeignKey, ManyToManyField, OneToOneField from django.db.transaction import atomic -from .fields import CustomManyToManyField -from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName, - BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, - UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough, - AuthorWithEvenLongerName, BookWeak, Note, BookWithO2O) +from django.test import TransactionTestCase + +from .fields import CustomManyToManyField, InheritedManyToManyField +from .models import ( + Author, AuthorWithDefaultHeight, AuthorWithEvenLongerName, Book, BookWeak, + BookWithLongName, BookWithO2O, BookWithSlug, Note, Tag, TagIndexed, + TagM2MTest, TagUniqueRename, Thing, UniqueTest, new_apps +) class SchemaTests(TransactionTestCase): @@ -26,24 +30,34 @@ class SchemaTests(TransactionTestCase): available_apps = [] models = [ - Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, - BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, - Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName, - BookWeak, BookWithO2O, + Author, AuthorWithDefaultHeight, AuthorWithEvenLongerName, Book, + BookWeak, BookWithLongName, BookWithO2O, BookWithSlug, Note, Tag, + TagIndexed, TagM2MTest, TagUniqueRename, Thing, UniqueTest, ] # Utility functions + def setUp(self): + # local_models should contain test dependent model classes that will be + # automatically removed from the app cache on test tear down. + self.local_models = [] + def tearDown(self): # Delete any tables made for our models self.delete_tables() + new_apps.clear_cache() + for model in new_apps.get_models(): + model._meta._expire_cache() + if 'schema' in new_apps.all_models: + for model in self.local_models: + del new_apps.all_models['schema'][model._meta.model_name] def delete_tables(self): "Deletes all model tables for our models for a clean test environment" with connection.cursor() as cursor: connection.disable_constraint_checking() table_names = connection.introspection.table_names(cursor) - for model in self.models: + 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(): @@ -134,15 +148,11 @@ def test_fk(self): pub_date=datetime.datetime.now(), ) # Repoint the FK constraint + old_field = Book._meta.get_field("author") new_field = ForeignKey(Tag) new_field.set_attributes_from_name("author") with connection.schema_editor() as editor: - editor.alter_field( - Book, - Book._meta.get_field("author"), - new_field, - strict=True, - ) + editor.alter_field(Book, old_field, new_field, strict=True) # Make sure the new FK constraint is present constraints = self.get_constraints(Book._meta.db_table) for name, details in constraints.items(): @@ -173,25 +183,17 @@ def test_fk_db_constraint(self): new_field = ForeignKey(Tag, db_constraint=False) new_field.set_attributes_from_name("tag") with connection.schema_editor() as editor: - editor.add_field( - Author, - new_field, - ) + editor.add_field(Author, new_field) # Make sure no FK constraint is present constraints = self.get_constraints(Author._meta.db_table) for name, details in constraints.items(): if details['columns'] == ["tag_id"] and details['foreign_key']: self.fail("FK constraint for tag_id found") # Alter to one with a constraint - new_field_2 = ForeignKey(Tag) - new_field_2.set_attributes_from_name("tag") - with connection.schema_editor() as editor: - editor.alter_field( - Author, - new_field, - new_field_2, - strict=True, - ) + new_field2 = ForeignKey(Tag) + new_field2.set_attributes_from_name("tag") + with connection.schema_editor() as editor: + editor.alter_field(Author, new_field, new_field2, strict=True) # Make sure the new FK constraint is present constraints = self.get_constraints(Author._meta.db_table) for name, details in constraints.items(): @@ -201,45 +203,56 @@ def test_fk_db_constraint(self): else: self.fail("No FK constraint for tag_id found") # Alter to one without a constraint again - new_field_2 = ForeignKey(Tag) - new_field_2.set_attributes_from_name("tag") - with connection.schema_editor() as editor: - editor.alter_field( - Author, - new_field_2, - new_field, - strict=True, - ) + new_field2 = ForeignKey(Tag) + new_field2.set_attributes_from_name("tag") + with connection.schema_editor() as editor: + editor.alter_field(Author, new_field2, new_field, strict=True) # Make sure no FK constraint is present constraints = self.get_constraints(Author._meta.db_table) for name, details in constraints.items(): if details['columns'] == ["tag_id"] and details['foreign_key']: self.fail("FK constraint for tag_id found") - @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support") - def test_m2m_db_constraint(self): + def _test_m2m_db_constraint(self, M2MFieldClass): + class LocalAuthorWithM2M(Model): + name = CharField(max_length=255) + + class Meta: + apps = new_apps + + self.local_models = [LocalAuthorWithM2M] + # Create the table with connection.schema_editor() as editor: editor.create_model(Tag) - editor.create_model(Author) + editor.create_model(LocalAuthorWithM2M) # Check that initial tables are there - list(Author.objects.all()) + list(LocalAuthorWithM2M.objects.all()) list(Tag.objects.all()) # Make a db_constraint=False FK - new_field = ManyToManyField("schema.Tag", related_name="authors", db_constraint=False) - new_field.contribute_to_class(Author, "tags") + new_field = M2MFieldClass(Tag, related_name="authors", db_constraint=False) + new_field.contribute_to_class(LocalAuthorWithM2M, "tags") # Add the field with connection.schema_editor() as editor: - editor.add_field( - Author, - new_field, - ) + editor.add_field(LocalAuthorWithM2M, new_field) # Make sure no FK constraint is present constraints = self.get_constraints(new_field.rel.through._meta.db_table) for name, details in constraints.items(): if details['columns'] == ["tag_id"] and details['foreign_key']: self.fail("FK constraint for tag_id found") + @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support") + def test_m2m_db_constraint(self): + self._test_m2m_db_constraint(ManyToManyField) + + @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support") + def test_m2m_db_constraint_custom(self): + self._test_m2m_db_constraint(CustomManyToManyField) + + @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support") + def test_m2m_db_constraint_inherited(self): + self._test_m2m_db_constraint(InheritedManyToManyField) + def test_add_field(self): """ Tests adding fields to models @@ -254,10 +267,7 @@ def test_add_field(self): new_field = IntegerField(null=True) new_field.set_attributes_from_name("age") with connection.schema_editor() as editor: - editor.add_field( - Author, - new_field, - ) + editor.add_field(Author, new_field) # Ensure the field is right afterwards columns = self.column_classes(Author) self.assertEqual(columns['age'][0], "IntegerField") @@ -280,10 +290,7 @@ def test_add_field_temp_default(self): new_field = CharField(max_length=30, default="Godwin") new_field.set_attributes_from_name("surname") with connection.schema_editor() as editor: - editor.add_field( - Author, - new_field, - ) + editor.add_field(Author, new_field) # Ensure the field is right afterwards columns = self.column_classes(Author) self.assertEqual(columns['surname'][0], "CharField") @@ -308,10 +315,7 @@ def test_add_field_temp_default_boolean(self): new_field = BooleanField(default=False) new_field.set_attributes_from_name("awesome") with connection.schema_editor() as editor: - editor.add_field( - Author, - new_field, - ) + editor.add_field(Author, new_field) # Ensure the field is right afterwards columns = self.column_classes(Author) # BooleanField are stored as TINYINT(1) on MySQL. @@ -345,10 +349,7 @@ def get_prep_value(self, value): new_field = TestTransformField(default={1: 2}) new_field.set_attributes_from_name("thing") with connection.schema_editor() as editor: - editor.add_field( - Author, - new_field, - ) + editor.add_field(Author, new_field) # Ensure the field is there columns = self.column_classes(Author) field_type, field_info = columns['thing'] @@ -367,10 +368,7 @@ def test_add_field_binary(self): new_field = BinaryField(blank=True) new_field.set_attributes_from_name("bits") with connection.schema_editor() as editor: - editor.add_field( - Author, - new_field, - ) + editor.add_field(Author, new_field) # Ensure the field is right afterwards columns = self.column_classes(Author) # MySQL annoyingly uses the same backend, so it'll come back as one of @@ -389,15 +387,11 @@ def test_alter(self): self.assertEqual(columns['name'][0], "CharField") self.assertEqual(bool(columns['name'][1][6]), bool(connection.features.interprets_empty_strings_as_nulls)) # Alter the name field to a TextField + old_field = Author._meta.get_field("name") new_field = TextField(null=True) new_field.set_attributes_from_name("name") with connection.schema_editor() as editor: - editor.alter_field( - Author, - Author._meta.get_field("name"), - new_field, - strict=True, - ) + editor.alter_field(Author, old_field, new_field, strict=True) # Ensure the field is right afterwards columns = self.column_classes(Author) self.assertEqual(columns['name'][0], "TextField") @@ -406,12 +400,7 @@ def test_alter(self): new_field2 = TextField(null=False) new_field2.set_attributes_from_name("name") with connection.schema_editor() as editor: - editor.alter_field( - Author, - new_field, - new_field2, - strict=True, - ) + editor.alter_field(Author, new_field, new_field2, strict=True) # Ensure the field is right afterwards columns = self.column_classes(Author) self.assertEqual(columns['name'][0], "TextField") @@ -420,15 +409,14 @@ def test_alter(self): def test_alter_text_field(self): # Regression for "BLOB/TEXT column 'info' can't have a default value") # on MySQL. + # Create the table + with connection.schema_editor() as editor: + editor.create_model(Note) + old_field = Note._meta.get_field("info") new_field = TextField(blank=True) new_field.set_attributes_from_name("info") with connection.schema_editor() as editor: - editor.alter_field( - Note, - Note._meta.get_field("info"), - new_field, - strict=True, - ) + editor.alter_field(Note, old_field, new_field, strict=True) def test_alter_null_to_not_null(self): """ @@ -447,14 +435,11 @@ def test_alter_null_to_not_null(self): self.assertEqual(Author.objects.get(name='Not null author').height, 12) self.assertIsNone(Author.objects.get(name='Null author').height) # Alter the height field to NOT NULL with default + old_field = Author._meta.get_field("height") new_field = PositiveIntegerField(default=42) new_field.set_attributes_from_name("height") with connection.schema_editor() as editor: - editor.alter_field( - Author, - Author._meta.get_field("height"), - new_field - ) + editor.alter_field(Author, old_field, new_field) # Ensure the field is right afterwards columns = self.column_classes(Author) self.assertFalse(columns['height'][1][6]) @@ -475,14 +460,11 @@ def test_alter_null_to_not_null_keeping_default(self): columns = self.column_classes(AuthorWithDefaultHeight) self.assertTrue(columns['height'][1][6]) # Alter the height field to NOT NULL keeping the previous default + old_field = AuthorWithDefaultHeight._meta.get_field("height") new_field = PositiveIntegerField(default=42) new_field.set_attributes_from_name("height") with connection.schema_editor() as editor: - editor.alter_field( - AuthorWithDefaultHeight, - AuthorWithDefaultHeight._meta.get_field("height"), - new_field, - ) + editor.alter_field(AuthorWithDefaultHeight, old_field, new_field) # Ensure the field is right afterwards columns = self.column_classes(AuthorWithDefaultHeight) self.assertFalse(columns['height'][1][6]) @@ -508,15 +490,11 @@ def test_alter_fk(self): else: self.fail("No FK constraint for author_id found") # Alter the FK + old_field = Book._meta.get_field("author") new_field = ForeignKey(Author, editable=False) new_field.set_attributes_from_name("author") with connection.schema_editor() as editor: - editor.alter_field( - Book, - Book._meta.get_field("author"), - new_field, - strict=True, - ) + editor.alter_field(Book, old_field, new_field, strict=True) # Ensure the field is right afterwards columns = self.column_classes(Book) self.assertEqual(columns['author_id'][0], "IntegerField") @@ -556,15 +534,11 @@ def test_alter_o2o_to_fk(self): author_is_fk = True self.assertTrue(author_is_fk, "No FK constraint for author_id found") # Alter the OneToOneField to ForeignKey + old_field = BookWithO2O._meta.get_field("author") new_field = ForeignKey(Author) new_field.set_attributes_from_name("author") with connection.schema_editor() as editor: - editor.alter_field( - BookWithO2O, - BookWithO2O._meta.get_field("author"), - new_field, - strict=True, - ) + editor.alter_field(BookWithO2O, old_field, new_field, strict=True) # Ensure the field is right afterwards columns = self.column_classes(Book) self.assertEqual(columns['author_id'][0], "IntegerField") @@ -606,15 +580,11 @@ def test_alter_fk_to_o2o(self): author_is_fk = True self.assertTrue(author_is_fk, "No FK constraint for author_id found") # Alter the ForeignKey to OneToOneField + old_field = Book._meta.get_field("author") new_field = OneToOneField(Author) new_field.set_attributes_from_name("author") with connection.schema_editor() as editor: - editor.alter_field( - Book, - Book._meta.get_field("author"), - new_field, - strict=True, - ) + editor.alter_field(Book, old_field, new_field, strict=True) # Ensure the field is right afterwards columns = self.column_classes(BookWithO2O) self.assertEqual(columns['author_id'][0], "IntegerField") @@ -639,17 +609,12 @@ def test_alter_implicit_id_to_explicit(self): with connection.schema_editor() as editor: editor.create_model(Author) + old_field = Author._meta.get_field("id") new_field = IntegerField(primary_key=True) new_field.set_attributes_from_name("id") new_field.model = Author with connection.schema_editor() as editor: - editor.alter_field( - Author, - Author._meta.get_field("id"), - new_field, - strict=True, - ) - + editor.alter_field(Author, old_field, new_field, strict=True) # 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') @@ -666,127 +631,202 @@ def test_rename(self): self.assertEqual(columns['name'][0], "CharField") self.assertNotIn("display_name", columns) # Alter the name field's name + old_field = Author._meta.get_field("name") new_field = CharField(max_length=254) new_field.set_attributes_from_name("display_name") with connection.schema_editor() as editor: - editor.alter_field( - Author, - Author._meta.get_field("name"), - new_field, - strict=True, - ) + editor.alter_field(Author, old_field, new_field, strict=True) # Ensure the field is right afterwards columns = self.column_classes(Author) self.assertEqual(columns['display_name'][0], "CharField") self.assertNotIn("name", columns) - def test_m2m_create(self): + def _test_m2m_create(self, M2MFieldClass): """ Tests M2M fields on models during creation """ + class LocalBookWithM2M(Model): + author = ForeignKey(Author) + title = CharField(max_length=100, db_index=True) + pub_date = DateTimeField() + tags = M2MFieldClass("TagM2MTest", related_name="books") + + class Meta: + apps = new_apps + + self.local_models = [LocalBookWithM2M] + # Create the tables with connection.schema_editor() as editor: editor.create_model(Author) editor.create_model(TagM2MTest) - editor.create_model(BookWithM2M) + editor.create_model(LocalBookWithM2M) # Ensure there is now an m2m table there - columns = self.column_classes(BookWithM2M._meta.get_field("tags").rel.through) + columns = self.column_classes(LocalBookWithM2M._meta.get_field("tags").rel.through) self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField") - def test_m2m_create_through(self): + def test_m2m_create(self): + self._test_m2m_create(ManyToManyField) + + def test_m2m_create_custom(self): + self._test_m2m_create(CustomManyToManyField) + + def test_m2m_create_inherited(self): + self._test_m2m_create(InheritedManyToManyField) + + def _test_m2m_create_through(self, M2MFieldClass): """ Tests M2M fields on models during creation with through models """ + class LocalTagThrough(Model): + book = ForeignKey("schema.LocalBookWithM2MThrough") + tag = ForeignKey("schema.TagM2MTest") + + class Meta: + apps = new_apps + + class LocalBookWithM2MThrough(Model): + tags = M2MFieldClass("TagM2MTest", related_name="books", through=LocalTagThrough) + + class Meta: + apps = new_apps + + self.local_models = [LocalTagThrough, LocalBookWithM2MThrough] + # Create the tables with connection.schema_editor() as editor: - editor.create_model(TagThrough) + editor.create_model(LocalTagThrough) editor.create_model(TagM2MTest) - editor.create_model(BookWithM2MThrough) + editor.create_model(LocalBookWithM2MThrough) # Ensure there is now an m2m table there - columns = self.column_classes(TagThrough) + columns = self.column_classes(LocalTagThrough) self.assertEqual(columns['book_id'][0], "IntegerField") self.assertEqual(columns['tag_id'][0], "IntegerField") - def test_m2m(self): + def test_m2m_create_through(self): + self._test_m2m_create_through(ManyToManyField) + + def test_m2m_create_through_custom(self): + self._test_m2m_create_through(CustomManyToManyField) + + def test_m2m_create_through_inherited(self): + self._test_m2m_create_through(InheritedManyToManyField) + + def _test_m2m(self, M2MFieldClass): """ Tests adding/removing M2M fields on models """ + class LocalAuthorWithM2M(Model): + name = CharField(max_length=255) + + class Meta: + apps = new_apps + + self.local_models = [LocalAuthorWithM2M] + # Create the tables with connection.schema_editor() as editor: - editor.create_model(AuthorWithM2M) + editor.create_model(LocalAuthorWithM2M) editor.create_model(TagM2MTest) # Create an M2M field - new_field = ManyToManyField("schema.TagM2MTest", related_name="authors") - new_field.contribute_to_class(AuthorWithM2M, "tags") - try: - # Ensure there's no m2m table there - self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through) - # Add the field - with connection.schema_editor() as editor: - editor.add_field( - Author, - new_field, - ) - # Ensure there is now an m2m table there - columns = self.column_classes(new_field.rel.through) - self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField") - - # "Alter" the field. This should not rename the DB table to itself. - with connection.schema_editor() as editor: - editor.alter_field( - Author, - new_field, - new_field, - ) + new_field = M2MFieldClass("schema.TagM2MTest", related_name="authors") + new_field.contribute_to_class(LocalAuthorWithM2M, "tags") + # Ensure there's no m2m table there + self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through) + # Add the field + with connection.schema_editor() as editor: + editor.add_field(LocalAuthorWithM2M, new_field) + # Ensure there is now an m2m table there + columns = self.column_classes(new_field.rel.through) + self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField") - # Remove the M2M table again - with connection.schema_editor() as editor: - editor.remove_field( - Author, - new_field, - ) - # Ensure there's no m2m table there - self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through) - finally: - # Cleanup model states - AuthorWithM2M._meta.local_many_to_many.remove(new_field) + # "Alter" the field. This should not rename the DB table to itself. + with connection.schema_editor() as editor: + editor.alter_field(LocalAuthorWithM2M, new_field, new_field) - def test_m2m_through_alter(self): + # Remove the M2M table again + with connection.schema_editor() as editor: + editor.remove_field(LocalAuthorWithM2M, new_field) + # Ensure there's no m2m table there + self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through) + + def test_m2m(self): + self._test_m2m(ManyToManyField) + + def test_m2m_custom(self): + self._test_m2m(CustomManyToManyField) + + def test_m2m_inherited(self): + self._test_m2m(InheritedManyToManyField) + + def _test_m2m_through_alter(self, M2MFieldClass): """ Tests altering M2Ms with explicit through models (should no-op) """ + class LocalAuthorTag(Model): + author = ForeignKey("schema.LocalAuthorWithM2MThrough") + tag = ForeignKey("schema.TagM2MTest") + + class Meta: + apps = new_apps + + class LocalAuthorWithM2MThrough(Model): + name = CharField(max_length=255) + tags = M2MFieldClass("schema.TagM2MTest", related_name="authors", through=LocalAuthorTag) + + class Meta: + apps = new_apps + + self.local_models = [LocalAuthorTag, LocalAuthorWithM2MThrough] + # Create the tables with connection.schema_editor() as editor: - editor.create_model(AuthorTag) - editor.create_model(AuthorWithM2MThrough) + editor.create_model(LocalAuthorTag) + editor.create_model(LocalAuthorWithM2MThrough) editor.create_model(TagM2MTest) # Ensure the m2m table is there - self.assertEqual(len(self.column_classes(AuthorTag)), 3) + self.assertEqual(len(self.column_classes(LocalAuthorTag)), 3) # "Alter" the field's blankness. This should not actually do anything. + old_field = LocalAuthorWithM2MThrough._meta.get_field("tags") + new_field = M2MFieldClass("schema.TagM2MTest", related_name="authors", through=LocalAuthorTag) + new_field.contribute_to_class(LocalAuthorWithM2MThrough, "tags") with connection.schema_editor() as editor: - old_field = AuthorWithM2MThrough._meta.get_field("tags") - new_field = ManyToManyField("schema.TagM2MTest", related_name="authors", through="AuthorTag") - new_field.contribute_to_class(AuthorWithM2MThrough, "tags") - editor.alter_field( - Author, - old_field, - new_field, - ) + editor.alter_field(LocalAuthorWithM2MThrough, old_field, new_field) # Ensure the m2m table is still there - self.assertEqual(len(self.column_classes(AuthorTag)), 3) + self.assertEqual(len(self.column_classes(LocalAuthorTag)), 3) - def test_m2m_repoint(self): + def test_m2m_through_alter(self): + self._test_m2m_through_alter(ManyToManyField) + + def test_m2m_through_alter_custom(self): + self._test_m2m_through_alter(CustomManyToManyField) + + def test_m2m_through_alter_inherited(self): + self._test_m2m_through_alter(InheritedManyToManyField) + + def _test_m2m_repoint(self, M2MFieldClass): """ Tests repointing M2M fields """ + class LocalBookWithM2M(Model): + author = ForeignKey(Author) + title = CharField(max_length=100, db_index=True) + pub_date = DateTimeField() + tags = M2MFieldClass("TagM2MTest", related_name="books") + + class Meta: + apps = new_apps + + self.local_models = [LocalBookWithM2M] + # Create the tables with connection.schema_editor() as editor: editor.create_model(Author) - editor.create_model(BookWithM2M) + editor.create_model(LocalBookWithM2M) editor.create_model(TagM2MTest) editor.create_model(UniqueTest) # Ensure the M2M exists and points to TagM2MTest - constraints = self.get_constraints(BookWithM2M._meta.get_field("tags").rel.through._meta.db_table) + constraints = self.get_constraints(LocalBookWithM2M._meta.get_field("tags").rel.through._meta.db_table) if connection.features.supports_foreign_keys: for name, details in constraints.items(): if details['columns'] == ["tagm2mtest_id"] and details['foreign_key']: @@ -795,33 +835,31 @@ def test_m2m_repoint(self): else: self.fail("No FK constraint for tagm2mtest_id found") # Repoint the M2M - new_field = ManyToManyField(UniqueTest) - new_field.contribute_to_class(BookWithM2M, "uniques") - try: - with connection.schema_editor() as editor: - editor.alter_field( - Author, - BookWithM2M._meta.get_field("tags"), - new_field, - ) - # Ensure old M2M is gone - self.assertRaises(DatabaseError, self.column_classes, BookWithM2M._meta.get_field("tags").rel.through) - # Ensure the new M2M exists and points to UniqueTest - constraints = self.get_constraints(new_field.rel.through._meta.db_table) - if connection.features.supports_foreign_keys: - for name, details in constraints.items(): - if details['columns'] == ["uniquetest_id"] and details['foreign_key']: - self.assertEqual(details['foreign_key'], ('schema_uniquetest', 'id')) - break - else: - self.fail("No FK constraint for uniquetest_id found") - finally: - # Cleanup through table separately - with connection.schema_editor() as editor: - editor.remove_field(BookWithM2M, BookWithM2M._meta.get_field("uniques")) - # Cleanup model states - BookWithM2M._meta.local_many_to_many.remove(new_field) - BookWithM2M._meta._expire_cache() + old_field = LocalBookWithM2M._meta.get_field("tags") + new_field = M2MFieldClass(UniqueTest) + new_field.contribute_to_class(LocalBookWithM2M, "uniques") + with connection.schema_editor() as editor: + editor.alter_field(LocalBookWithM2M, old_field, new_field) + # Ensure old M2M is gone + self.assertRaises(DatabaseError, self.column_classes, LocalBookWithM2M._meta.get_field("tags").rel.through) + # Ensure the new M2M exists and points to UniqueTest + constraints = self.get_constraints(new_field.rel.through._meta.db_table) + if connection.features.supports_foreign_keys: + for name, details in constraints.items(): + if details['columns'] == ["uniquetest_id"] and details['foreign_key']: + self.assertEqual(details['foreign_key'], ('schema_uniquetest', 'id')) + break + else: + self.fail("No FK constraint for uniquetest_id found") + + def test_m2m_repoint(self): + self._test_m2m_repoint(ManyToManyField) + + def test_m2m_repoint_custom(self): + self._test_m2m_repoint(CustomManyToManyField) + + def test_m2m_repoint_inherited(self): + self._test_m2m_repoint(InheritedManyToManyField) @unittest.skipUnless(connection.features.supports_column_check_constraints, "No check constraints") def test_check_constraints(self): @@ -839,27 +877,19 @@ def test_check_constraints(self): else: self.fail("No check constraint for height found") # Alter the column to remove it + old_field = Author._meta.get_field("height") new_field = IntegerField(null=True, blank=True) new_field.set_attributes_from_name("height") with connection.schema_editor() as editor: - editor.alter_field( - Author, - Author._meta.get_field("height"), - new_field, - strict=True, - ) + editor.alter_field(Author, old_field, new_field, strict=True) constraints = self.get_constraints(Author._meta.db_table) for name, details in constraints.items(): if details['columns'] == ["height"] and details['check']: self.fail("Check constraint for height found") # Alter the column to re-add it + new_field2 = Author._meta.get_field("height") with connection.schema_editor() as editor: - editor.alter_field( - Author, - new_field, - Author._meta.get_field("height"), - strict=True, - ) + editor.alter_field(Author, new_field, new_field2, strict=True) constraints = self.get_constraints(Author._meta.db_table) for name, details in constraints.items(): if details['columns'] == ["height"] and details['check']: @@ -879,43 +909,29 @@ def test_unique(self): self.assertRaises(IntegrityError, Tag.objects.create, title="bar", slug="foo") Tag.objects.all().delete() # Alter the slug field to be non-unique + old_field = Tag._meta.get_field("slug") new_field = SlugField(unique=False) new_field.set_attributes_from_name("slug") with connection.schema_editor() as editor: - editor.alter_field( - Tag, - Tag._meta.get_field("slug"), - new_field, - strict=True, - ) + editor.alter_field(Tag, old_field, new_field, strict=True) # Ensure the field is no longer unique Tag.objects.create(title="foo", slug="foo") Tag.objects.create(title="bar", slug="foo") Tag.objects.all().delete() # Alter the slug field to be unique - new_new_field = SlugField(unique=True) - new_new_field.set_attributes_from_name("slug") - with connection.schema_editor() as editor: - editor.alter_field( - Tag, - new_field, - new_new_field, - strict=True, - ) + new_field2 = SlugField(unique=True) + new_field2.set_attributes_from_name("slug") + with connection.schema_editor() as editor: + editor.alter_field(Tag, new_field, new_field2, strict=True) # Ensure the field is unique again Tag.objects.create(title="foo", slug="foo") self.assertRaises(IntegrityError, Tag.objects.create, title="bar", slug="foo") Tag.objects.all().delete() # Rename the field - new_field = SlugField(unique=False) - new_field.set_attributes_from_name("slug2") + new_field3 = SlugField(unique=True) + new_field3.set_attributes_from_name("slug2") with connection.schema_editor() as editor: - editor.alter_field( - Tag, - Tag._meta.get_field("slug"), - TagUniqueRename._meta.get_field("slug2"), - strict=True, - ) + editor.alter_field(Tag, new_field2, new_field3, strict=True) # Ensure the field is still unique TagUniqueRename.objects.create(title="foo", slug2="foo") self.assertRaises(IntegrityError, TagUniqueRename.objects.create, title="bar", slug2="foo") @@ -936,24 +952,16 @@ def test_unique_together(self): UniqueTest.objects.all().delete() # Alter the model to its non-unique-together companion with connection.schema_editor() as editor: - editor.alter_unique_together( - UniqueTest, - UniqueTest._meta.unique_together, - [], - ) + editor.alter_unique_together(UniqueTest, UniqueTest._meta.unique_together, []) # Ensure the fields are no longer unique UniqueTest.objects.create(year=2012, slug="foo") UniqueTest.objects.create(year=2012, slug="foo") UniqueTest.objects.all().delete() # Alter it back - new_new_field = SlugField(unique=True) - new_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_unique_together( - UniqueTest, - [], - UniqueTest._meta.unique_together, - ) + editor.alter_unique_together(UniqueTest, [], UniqueTest._meta.unique_together) # Ensure the fields are unique again UniqueTest.objects.create(year=2012, slug="foo") self.assertRaises(IntegrityError, UniqueTest.objects.create, year=2012, slug="foo") @@ -977,11 +985,7 @@ def test_index_together(self): ) # Alter the model to add an index with connection.schema_editor() as editor: - editor.alter_index_together( - Tag, - [], - [("slug", "title")], - ) + editor.alter_index_together(Tag, [], [("slug", "title")]) # Ensure there is now an index self.assertEqual( True, @@ -992,14 +996,10 @@ def test_index_together(self): ), ) # Alter it back - new_new_field = SlugField(unique=True) - new_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_index_together( - Tag, - [("slug", "title")], - [], - ) + editor.alter_index_together(Tag, [("slug", "title")], []) # Ensure there's no index self.assertEqual( False, @@ -1039,22 +1039,14 @@ def test_db_table(self): self.assertEqual(columns['name'][0], "CharField") # Alter the table with connection.schema_editor() as editor: - editor.alter_db_table( - Author, - "schema_author", - "schema_otherauthor", - ) + editor.alter_db_table(Author, "schema_author", "schema_otherauthor") # Ensure the table is there afterwards Author._meta.db_table = "schema_otherauthor" columns = self.column_classes(Author) self.assertEqual(columns['name'][0], "CharField") # Alter the table again with connection.schema_editor() as editor: - editor.alter_db_table( - Author, - "schema_otherauthor", - "schema_author", - ) + editor.alter_db_table(Author, "schema_otherauthor", "schema_author") # Ensure the table is still there Author._meta.db_table = "schema_author" columns = self.column_classes(Author) @@ -1074,53 +1066,38 @@ def test_indexes(self): self.get_indexes(Book._meta.db_table), ) # Alter to remove the index + old_field = Book._meta.get_field("title") new_field = CharField(max_length=100, db_index=False) new_field.set_attributes_from_name("title") with connection.schema_editor() as editor: - editor.alter_field( - Book, - Book._meta.get_field("title"), - new_field, - strict=True, - ) + editor.alter_field(Book, old_field, new_field, strict=True) # Ensure the table is there and has no index self.assertNotIn( "title", self.get_indexes(Book._meta.db_table), ) # Alter to re-add the index + new_field2 = Book._meta.get_field("title") with connection.schema_editor() as editor: - editor.alter_field( - Book, - new_field, - Book._meta.get_field("title"), - strict=True, - ) + editor.alter_field(Book, new_field, new_field2, strict=True) # Ensure the table is there and has the index again self.assertIn( "title", self.get_indexes(Book._meta.db_table), ) # Add a unique column, verify that creates an implicit index + new_field3 = BookWithSlug._meta.get_field("slug") with connection.schema_editor() as editor: - editor.add_field( - Book, - BookWithSlug._meta.get_field("slug"), - ) + editor.add_field(Book, new_field3) self.assertIn( "slug", self.get_indexes(Book._meta.db_table), ) # Remove the unique, check the index goes with it - new_field2 = CharField(max_length=20, unique=False) - new_field2.set_attributes_from_name("slug") + new_field4 = CharField(max_length=20, unique=False) + new_field4.set_attributes_from_name("slug") with connection.schema_editor() as editor: - editor.alter_field( - BookWithSlug, - BookWithSlug._meta.get_field("slug"), - new_field2, - strict=True, - ) + editor.alter_field(BookWithSlug, new_field3, new_field4, strict=True) self.assertNotIn( "slug", self.get_indexes(Book._meta.db_table), @@ -1138,16 +1115,14 @@ def test_primary_key(self): self.get_indexes(Tag._meta.db_table)['id']['primary_key'], ) # Alter to change the PK + id_field = Tag._meta.get_field("id") + old_field = Tag._meta.get_field("slug") new_field = SlugField(primary_key=True) new_field.set_attributes_from_name("slug") new_field.model = Tag with connection.schema_editor() as editor: - editor.remove_field(Tag, Tag._meta.get_field("id")) - editor.alter_field( - Tag, - Tag._meta.get_field("slug"), - new_field, - ) + editor.remove_field(Tag, id_field) + editor.alter_field(Tag, old_field, new_field) # Ensure the PK changed self.assertNotIn( 'id', @@ -1203,10 +1178,7 @@ def test_add_foreign_key_long_names(self): new_field = ForeignKey(AuthorWithEvenLongerName, related_name="something") new_field.set_attributes_from_name("author_other_really_long_named_i_mean_so_long_fk") with connection.schema_editor() as editor: - editor.add_field( - BookWithLongName, - new_field, - ) + editor.add_field(BookWithLongName, new_field) def test_creation_deletion_reserved_names(self): """ @@ -1304,47 +1276,3 @@ def test_add_field_use_effective_default(self): cursor.execute("SELECT surname FROM schema_author;") item = cursor.fetchall()[0] self.assertEqual(item[0], None if connection.features.interprets_empty_strings_as_nulls else '') - - def test_custom_manytomanyfield(self): - """ - #24104 - Schema editors should look for many_to_many - """ - # Create the tables - with connection.schema_editor() as editor: - editor.create_model(AuthorWithM2M) - editor.create_model(TagM2MTest) - # Create an M2M field - new_field = CustomManyToManyField("schema.TagM2MTest", related_name="authors") - new_field.contribute_to_class(AuthorWithM2M, "tags") - # Ensure there's no m2m table there - self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through) - try: - # Add the field - with connection.schema_editor() as editor: - editor.add_field( - AuthorWithM2M, - new_field, - ) - # Ensure there is now an m2m table there - columns = self.column_classes(new_field.rel.through) - self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField") - - # "Alter" the field. This should not rename the DB table to itself. - with connection.schema_editor() as editor: - editor.alter_field( - AuthorWithM2M, - new_field, - new_field, - ) - - # Remove the M2M table again - with connection.schema_editor() as editor: - editor.remove_field( - AuthorWithM2M, - new_field, - ) - # Ensure there's no m2m table there - self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through) - finally: - # Cleanup model states - AuthorWithM2M._meta.local_many_to_many.remove(new_field) From c9addfd4bfadde043a1aec7854b285aa212bdd5a Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Fri, 6 Feb 2015 00:40:36 +0100 Subject: [PATCH 0101/1125] [1.8.x] Fixed small regression caused by e3702dc3f2af9e8cc6c1155b248f458899be334e Since 1.7 models need to declare an explicit app_label if they are not in an application in INSTALLED_APPS or were imported before their application was loaded. Backport of 235124d3eaf6cc7ab9f97f1d1467d1caf18e1016 from master --- tests/schema/tests.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 17040c1a69dc..f6df3e7633fe 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -218,6 +218,7 @@ class LocalAuthorWithM2M(Model): name = CharField(max_length=255) class Meta: + app_label = 'schema' apps = new_apps self.local_models = [LocalAuthorWithM2M] @@ -652,6 +653,7 @@ class LocalBookWithM2M(Model): tags = M2MFieldClass("TagM2MTest", related_name="books") class Meta: + app_label = 'schema' apps = new_apps self.local_models = [LocalBookWithM2M] @@ -683,12 +685,14 @@ class LocalTagThrough(Model): tag = ForeignKey("schema.TagM2MTest") class Meta: + app_label = 'schema' apps = new_apps class LocalBookWithM2MThrough(Model): tags = M2MFieldClass("TagM2MTest", related_name="books", through=LocalTagThrough) class Meta: + app_label = 'schema' apps = new_apps self.local_models = [LocalTagThrough, LocalBookWithM2MThrough] @@ -720,6 +724,7 @@ class LocalAuthorWithM2M(Model): name = CharField(max_length=255) class Meta: + app_label = 'schema' apps = new_apps self.local_models = [LocalAuthorWithM2M] @@ -768,6 +773,7 @@ class LocalAuthorTag(Model): tag = ForeignKey("schema.TagM2MTest") class Meta: + app_label = 'schema' apps = new_apps class LocalAuthorWithM2MThrough(Model): @@ -775,6 +781,7 @@ class LocalAuthorWithM2MThrough(Model): tags = M2MFieldClass("schema.TagM2MTest", related_name="authors", through=LocalAuthorTag) class Meta: + app_label = 'schema' apps = new_apps self.local_models = [LocalAuthorTag, LocalAuthorWithM2MThrough] @@ -815,6 +822,7 @@ class LocalBookWithM2M(Model): tags = M2MFieldClass("TagM2MTest", related_name="books") class Meta: + app_label = 'schema' apps = new_apps self.local_models = [LocalBookWithM2M] From fc1e9107d7d750f6eed3c8e679dfe96af8f05150 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 5 Feb 2015 16:13:57 -0500 Subject: [PATCH 0102/1125] [1.8.x] Added UUIDField.deconstruct() Backport of 0f54cf28c09a80254571487e3af93be2096cfdac from master --- django/db/models/fields/__init__.py | 5 +++++ tests/model_fields/test_uuid.py | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 627af0c98571..c46851c7fe9e 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -2374,6 +2374,11 @@ def __init__(self, **kwargs): kwargs['max_length'] = 32 super(UUIDField, self).__init__(**kwargs) + def deconstruct(self): + name, path, args, kwargs = super(UUIDField, self).deconstruct() + del kwargs['max_length'] + return name, path, args, kwargs + def get_internal_type(self): return "UUIDField" diff --git a/tests/model_fields/test_uuid.py b/tests/model_fields/test_uuid.py index 13fe245be312..d2ffe71a3ace 100644 --- a/tests/model_fields/test_uuid.py +++ b/tests/model_fields/test_uuid.py @@ -35,6 +35,14 @@ def test_null_handling(self): self.assertEqual(loaded.field, None) +class TestMigrations(TestCase): + + def test_deconstruct(self): + field = models.UUIDField() + name, path, args, kwargs = field.deconstruct() + self.assertEqual(kwargs, {}) + + class TestQuerying(TestCase): def setUp(self): self.objs = [ From 5bc9904b3598a3cbdf7f5ebff744ffd77dc8fc87 Mon Sep 17 00:00:00 2001 From: minusf Date: Tue, 3 Feb 2015 13:43:51 +0100 Subject: [PATCH 0103/1125] [1.8.x] Removed inaccurate sentence about PO files in translation docs. Backport of aea103b6a599a5e4c71a355b2cac237f067be13b from master --- docs/topics/i18n/translation.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index ad618c9d5a4d..e53b925968d9 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1357,12 +1357,10 @@ otherwise, they'll be tacked together without whitespace! .. admonition:: Mind your charset - When creating a PO file with your favorite text editor, first edit - the charset line (search for ``"CHARSET"``) and set it to the charset - you'll be using to edit the content. Due to the way the ``gettext`` tools - work internally and because we want to allow non-ASCII source strings in - Django's core and your applications, you **must** use UTF-8 as the encoding - for your PO file. This means that everybody will be using the same + Due to the way the ``gettext`` tools work internally and because we want to + allow non-ASCII source strings in Django's core and your applications, you + **must** use UTF-8 as the encoding for your PO files (the default when PO + files are created). This means that everybody will be using the same encoding, which is important when Django processes the PO files. To reexamine all source code and templates for new translation strings and From 6adc23d6b6203eaa5b4c72824c1b2abc368be857 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 6 Feb 2015 07:58:31 -0500 Subject: [PATCH 0104/1125] [1.8.x] Removed gather_profile_stats.py This script uses the unmaintained hotshot module (gone on Python 3) and doesn't seem to be Django specific in any way. Backport of 388d986b8a6bb1363dab9f53ea435dff4dfe92cb from master --- django/bin/profiling/__init__.py | 0 django/bin/profiling/gather_profile_stats.py | 38 -------------------- docs/man/gather_profile_stats.1 | 26 -------------- 3 files changed, 64 deletions(-) delete mode 100644 django/bin/profiling/__init__.py delete mode 100644 django/bin/profiling/gather_profile_stats.py delete mode 100644 docs/man/gather_profile_stats.1 diff --git a/django/bin/profiling/__init__.py b/django/bin/profiling/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/django/bin/profiling/gather_profile_stats.py b/django/bin/profiling/gather_profile_stats.py deleted file mode 100644 index dd2733377c33..000000000000 --- a/django/bin/profiling/gather_profile_stats.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python - -""" -gather_profile_stats.py /path/to/dir/of/profiles - -Note that the aggregated profiles must be read with pstats.Stats, not -hotshot.stats (the formats are incompatible) -""" - -from hotshot import stats -import os -import pstats -import sys - - -def gather_stats(p): - profiles = {} - for f in os.listdir(p): - if f.endswith('.agg.prof'): - path = f[:-9] - prof = pstats.Stats(os.path.join(p, f)) - elif f.endswith('.prof'): - bits = f.split('.') - path = ".".join(bits[:-3]) - prof = stats.load(os.path.join(p, f)) - else: - continue - print("Processing %s" % f) - if path in profiles: - profiles[path].add(prof) - else: - profiles[path] = prof - os.unlink(os.path.join(p, f)) - for (path, prof) in profiles.items(): - prof.dump_stats(os.path.join(p, "%s.agg.prof" % path)) - -if __name__ == '__main__': - gather_stats(sys.argv[1]) diff --git a/docs/man/gather_profile_stats.1 b/docs/man/gather_profile_stats.1 deleted file mode 100644 index 72b443726f4c..000000000000 --- a/docs/man/gather_profile_stats.1 +++ /dev/null @@ -1,26 +0,0 @@ -.TH "gather_profile_stats.py" "1" "August 2007" "Django Project" "" -.SH "NAME" -gather_profile_stats.py \- Performance analysis tool for the Django Web -framework -.SH "SYNOPSIS" -.B python gather_profile_stats.py -.I - -.SH "DESCRIPTION" -This utility script aggregates profiling logs generated using Python's -hotshot profiler. The sole command-line argument is the full path to the -directory containing the profiling logfiles. - -.SH "SEE ALSO" -Discussion of profiling Django applications on the Django project's wiki: -.sp -.I https://www.djangoproject.com/wiki/ProfilingDjango - -.SH "AUTHORS/CREDITS" -Originally developed at World Online in Lawrence, Kansas, USA. Refer to the -AUTHORS file in the Django distribution for contributors. - -.SH "LICENSE" -New BSD license. For the full license text refer to the LICENSE file in the -Django distribution. - From 289660f52d501df2bc4612a820cbe2097d070b70 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 6 Feb 2015 08:47:38 -0500 Subject: [PATCH 0105/1125] [1.8.x] Removed bin/unique-messages.py This script is no longer used according to Claude, our translations manager. Backport of eb45a29565fe006b87657e9ee24054c66a6474f9 from master --- django/bin/unique-messages.py | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100755 django/bin/unique-messages.py diff --git a/django/bin/unique-messages.py b/django/bin/unique-messages.py deleted file mode 100755 index 16ec0a7e06dd..000000000000 --- a/django/bin/unique-messages.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python - -import os -import sys - - -def unique_messages(): - basedir = None - - if os.path.isdir(os.path.join('conf', 'locale')): - basedir = os.path.abspath(os.path.join('conf', 'locale')) - elif os.path.isdir('locale'): - basedir = os.path.abspath('locale') - else: - print("This script should be run from the Django Git tree or your project or app tree.") - sys.exit(1) - - for (dirpath, dirnames, filenames) in os.walk(basedir): - for f in filenames: - if f.endswith('.po'): - sys.stderr.write('processing file %s in %s\n' % (f, dirpath)) - pf = os.path.splitext(os.path.join(dirpath, f))[0] - cmd = 'msguniq "%s.po"' % pf - stdout = os.popen(cmd) - msg = stdout.read() - with open('%s.po' % pf, 'w') as fp: - fp.write(msg) - -if __name__ == "__main__": - unique_messages() From 232a1d297c8431a77f3dbb9bcf453d17eb5e3315 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 6 Feb 2015 06:22:00 -0500 Subject: [PATCH 0106/1125] [1.8.x] Removed django-2to3.py Aymeric says, "It was fun to write, but I don't think it's very useful." Backport of 607af78bb82404d55cc3e80e5f6772fb87c168ee from master --- django/bin/django-2to3.py | 9 ------- django/utils/2to3_fixes/__init__.py | 0 django/utils/2to3_fixes/fix_unicode.py | 36 -------------------------- 3 files changed, 45 deletions(-) delete mode 100755 django/bin/django-2to3.py delete mode 100644 django/utils/2to3_fixes/__init__.py delete mode 100644 django/utils/2to3_fixes/fix_unicode.py diff --git a/django/bin/django-2to3.py b/django/bin/django-2to3.py deleted file mode 100755 index 6d2daec8bf20..000000000000 --- a/django/bin/django-2to3.py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python - -# This works exactly like 2to3, except that it uses Django's fixers rather -# than 2to3's built-in fixers. - -import sys -from lib2to3.main import main - -sys.exit(main("django.utils.2to3_fixes")) diff --git a/django/utils/2to3_fixes/__init__.py b/django/utils/2to3_fixes/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/django/utils/2to3_fixes/fix_unicode.py b/django/utils/2to3_fixes/fix_unicode.py deleted file mode 100644 index 613734ca867d..000000000000 --- a/django/utils/2to3_fixes/fix_unicode.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Fixer for __unicode__ methods. - -Uses the django.utils.encoding.python_2_unicode_compatible decorator. -""" - -from __future__ import unicode_literals - -from lib2to3 import fixer_base -from lib2to3.fixer_util import find_indentation, Name, syms, touch_import -from lib2to3.pgen2 import token -from lib2to3.pytree import Leaf, Node - - -class FixUnicode(fixer_base.BaseFix): - - BM_compatible = True - PATTERN = """ - classdef< 'class' any+ ':' - suite< any* - funcdef< 'def' unifunc='__unicode__' - parameters< '(' NAME ')' > any+ > - any* > > - """ - - def transform(self, node, results): - unifunc = results["unifunc"] - strfunc = Name("__str__", prefix=unifunc.prefix) - unifunc.replace(strfunc) - - klass = node.clone() - klass.prefix = '\n' + find_indentation(node) - decorator = Node(syms.decorator, [Leaf(token.AT, "@"), Name('python_2_unicode_compatible')]) - decorated = Node(syms.decorated, [decorator, klass], prefix=node.prefix) - node.replace(decorated) - - touch_import('django.utils.encoding', 'python_2_unicode_compatible', decorated) From fc8e1e0c107fe5fc8c82f01dfabfa9b7a196a67c Mon Sep 17 00:00:00 2001 From: Collin Anderson Date: Thu, 5 Feb 2015 13:25:34 -0500 Subject: [PATCH 0107/1125] [1.8.x] Fixed E265 comment style Backport of db77915c9fd35a203edd8206f702ee4082f04d4a from master --- django/apps/registry.py | 2 +- django/contrib/gis/db/models/fields.py | 4 +- django/contrib/gis/db/models/query.py | 4 +- django/contrib/gis/feeds.py | 4 +- django/contrib/gis/gdal/datasource.py | 1 - django/contrib/gis/gdal/error.py | 4 +- django/contrib/gis/gdal/feature.py | 5 +- django/contrib/gis/gdal/field.py | 7 +- django/contrib/gis/gdal/geometries.py | 14 +-- django/contrib/gis/gdal/layer.py | 5 +- django/contrib/gis/gdal/libgdal.py | 2 +- django/contrib/gis/gdal/prototypes/ds.py | 12 +-- .../contrib/gis/gdal/prototypes/errcheck.py | 12 +-- django/contrib/gis/gdal/prototypes/geom.py | 4 +- django/contrib/gis/gdal/prototypes/raster.py | 8 +- django/contrib/gis/gdal/prototypes/srs.py | 2 +- django/contrib/gis/gdal/srs.py | 16 ++-- django/contrib/gis/gdal/tests/test_geom.py | 2 +- django/contrib/gis/geoip/base.py | 7 +- django/contrib/gis/geoip/prototypes.py | 4 +- django/contrib/gis/geos/collections.py | 2 +- django/contrib/gis/geos/coordseq.py | 9 +- django/contrib/gis/geos/geometry.py | 21 +++-- django/contrib/gis/geos/libgeos.py | 2 +- django/contrib/gis/geos/linestring.py | 3 +- django/contrib/gis/geos/mutable_list.py | 12 +-- django/contrib/gis/geos/point.py | 2 +- django/contrib/gis/geos/polygon.py | 4 +- .../contrib/gis/geos/prototypes/coordseq.py | 6 +- .../contrib/gis/geos/prototypes/errcheck.py | 1 - django/contrib/gis/geos/prototypes/geom.py | 4 +- django/contrib/gis/geos/prototypes/io.py | 18 ++-- django/contrib/gis/geos/prototypes/misc.py | 4 +- .../contrib/gis/geos/prototypes/predicates.py | 6 +- .../contrib/gis/geos/prototypes/topology.py | 2 +- django/contrib/gis/geos/tests/test_geos.py | 14 +-- django/contrib/gis/tests/geoapp/tests.py | 4 +- django/contrib/gis/utils/layermapping.py | 6 +- django/db/backends/base/base.py | 24 +++--- django/template/__init__.py | 4 +- docs/_ext/applyxrefs.py | 4 +- docs/conf.py | 86 +++++++++---------- setup.cfg | 2 +- tests/backends/tests.py | 14 +-- tests/httpwrappers/tests.py | 2 +- tests/introspection/tests.py | 2 +- tests/migrations/test_commands.py | 2 +- tests/model_fields/models.py | 2 +- tests/model_forms/tests.py | 4 +- tests/model_regress/models.py | 4 +- tests/prefetch_related/models.py | 14 +-- tests/serializers_regress/tests.py | 2 +- tests/syndication_tests/tests.py | 1 - tests/urlpatterns_reverse/no_urls.py | 1 - 54 files changed, 197 insertions(+), 210 deletions(-) diff --git a/django/apps/registry.py b/django/apps/registry.py index 68aa411d91f6..efef0c9174e9 100644 --- a/django/apps/registry.py +++ b/django/apps/registry.py @@ -344,7 +344,7 @@ def clear_cache(self): for model in self.get_models(include_auto_created=True): model._meta._expire_cache() - ### DEPRECATED METHODS GO BELOW THIS LINE ### + # ### DEPRECATED METHODS GO BELOW THIS LINE ### def load_app(self, app_name): """ diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py index 5e70697a2b86..acfb0915dd92 100644 --- a/django/contrib/gis/db/models/fields.py +++ b/django/contrib/gis/db/models/fields.py @@ -163,7 +163,7 @@ def units_name(self, connection): self._get_srid_info(connection) return self._units_name - ### Routines specific to GeometryField ### + # ### Routines specific to GeometryField ### def geodetic(self, connection): """ Returns true if this field's SRID corresponds with a coordinate @@ -236,7 +236,7 @@ def get_srid(self, geom): else: return gsrid - ### Routines overloaded from Field ### + # ### Routines overloaded from Field ### def contribute_to_class(self, cls, name, **kwargs): super(GeometryField, self).contribute_to_class(cls, name, **kwargs) diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py index c36381ecaa93..6e231a8607a3 100644 --- a/django/contrib/gis/db/models/query.py +++ b/django/contrib/gis/db/models/query.py @@ -23,7 +23,7 @@ class GeoQuerySet(QuerySet): "The Geographic QuerySet." - ### GeoQuerySet Methods ### + # ### GeoQuerySet Methods ### def area(self, tolerance=0.05, **kwargs): """ Returns the area of the geographic field in an `area` attribute on @@ -428,7 +428,7 @@ def unionagg(self, **kwargs): ) return self._spatial_aggregate(aggregates.Union, **kwargs) - ### Private API -- Abstracted DRY routines. ### + # ### Private API -- Abstracted DRY routines. ### def _spatial_setup(self, att, desc=None, field_name=None, geo_field_type=None): """ Performs set up for executing the spatial function. diff --git a/django/contrib/gis/feeds.py b/django/contrib/gis/feeds.py index 006b0298f4ff..fc66beec45d2 100644 --- a/django/contrib/gis/feeds.py +++ b/django/contrib/gis/feeds.py @@ -81,7 +81,7 @@ def add_georss_element(self, handler, item, w3c_geo=False): raise ValueError('Geometry type "%s" not supported.' % geom.geom_type) -### SyndicationFeed subclasses ### +# ### SyndicationFeed subclasses ### class GeoRSSFeed(Rss201rev2Feed, GeoFeedMixin): def rss_attributes(self): attrs = super(GeoRSSFeed, self).rss_attributes() @@ -127,7 +127,7 @@ def add_root_elements(self, handler): self.add_georss_element(handler, self.feed, w3c_geo=True) -### Feed subclass ### +# ### Feed subclass ### class Feed(BaseFeed): """ This is a subclass of the `Feed` from `django.contrib.syndication`. diff --git a/django/contrib/gis/gdal/datasource.py b/django/contrib/gis/gdal/datasource.py index ec9ab8c2a40c..7fa9d83e1286 100644 --- a/django/contrib/gis/gdal/datasource.py +++ b/django/contrib/gis/gdal/datasource.py @@ -57,7 +57,6 @@ class DataSource(GDALBase): "Wraps an OGR Data Source object." - #### Python 'magic' routines #### def __init__(self, ds_input, ds_driver=False, write=False, encoding='utf-8'): # The write flag. if write: diff --git a/django/contrib/gis/gdal/error.py b/django/contrib/gis/gdal/error.py index e7269beca1b2..4dc0b71678c8 100644 --- a/django/contrib/gis/gdal/error.py +++ b/django/contrib/gis/gdal/error.py @@ -5,7 +5,7 @@ """ -#### GDAL & SRS Exceptions #### +# #### GDAL & SRS Exceptions #### class GDALException(Exception): pass @@ -27,7 +27,7 @@ class OGRIndexError(GDALException, KeyError): """ silent_variable_failure = True -#### GDAL/OGR error checking codes and routine #### +# #### GDAL/OGR error checking codes and routine #### # OGR Error Codes OGRERR_DICT = { diff --git a/django/contrib/gis/gdal/feature.py b/django/contrib/gis/gdal/feature.py index da86c7024463..b1a0b19fd0c1 100644 --- a/django/contrib/gis/gdal/feature.py +++ b/django/contrib/gis/gdal/feature.py @@ -22,7 +22,6 @@ class Feature(GDALBase): from a Layer object. """ - #### Python 'magic' routines #### def __init__(self, feat, layer): """ Initializes Feature from a pointer and its Layer object. @@ -69,7 +68,7 @@ def __eq__(self, other): "Does equivalence testing on the features." return bool(capi.feature_equal(self.ptr, other._ptr)) - #### Feature Properties #### + # #### Feature Properties #### @property def encoding(self): return self._layer._ds.encoding @@ -108,7 +107,7 @@ def geom_type(self): "Returns the OGR Geometry Type for this Feture." return OGRGeomType(capi.get_fd_geom_type(self._layer._ldefn)) - #### Feature Methods #### + # #### Feature Methods #### def get(self, field): """ Returns the value of the field, instead of an instance of the Field diff --git a/django/contrib/gis/gdal/field.py b/django/contrib/gis/gdal/field.py index 487c29ea4205..fb77480fa101 100644 --- a/django/contrib/gis/gdal/field.py +++ b/django/contrib/gis/gdal/field.py @@ -16,7 +16,6 @@ class Field(GDALBase): from a Feature object. """ - #### Python 'magic' routines #### def __init__(self, feat, index): """ Initializes on the feature object and the integer index of @@ -44,7 +43,7 @@ def __str__(self): "Returns the string representation of the Field." return str(self.value).strip() - #### Field Methods #### + # #### Field Methods #### def as_double(self): "Retrieves the Field's value as a double (float)." return capi.get_field_as_double(self._feat.ptr, self._index) @@ -69,7 +68,7 @@ def as_datetime(self): else: raise GDALException('Unable to retrieve date & time information from the field.') - #### Field Properties #### + # #### Field Properties #### @property def name(self): "Returns the name of this Field." @@ -103,7 +102,7 @@ def width(self): return capi.get_field_width(self.ptr) -### The Field sub-classes for each OGR Field type. ### +# ### The Field sub-classes for each OGR Field type. ### class OFTInteger(Field): _double = False diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index d00aa1a78574..b984eea4eee1 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -154,7 +154,7 @@ def from_bbox(cls, bbox): return OGRGeometry('POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % ( x0, y0, x0, y1, x1, y1, x1, y0, x0, y0)) - ### Geometry set-like operations ### + # ### Geometry set-like operations ### # g = g1 | g2 def __or__(self, other): "Returns the union of the two geometries." @@ -190,7 +190,7 @@ def __str__(self): "WKT is used for the string representation." return self.wkt - #### Geometry Properties #### + # #### Geometry Properties #### @property def dimension(self): "Returns 0 for points, 1 for lines, and 2 for surfaces." @@ -254,7 +254,7 @@ def extent(self): "Returns the envelope as a 4-tuple, instead of as an Envelope object." return self.envelope.tuple - #### SpatialReference-related Properties #### + # #### SpatialReference-related Properties #### # The SRS property def _get_srs(self): @@ -297,7 +297,7 @@ def _set_srid(self, srid): srid = property(_get_srid, _set_srid) - #### Output Methods #### + # #### Output Methods #### @property def geos(self): "Returns a GEOSGeometry object from this OGRGeometry." @@ -360,7 +360,7 @@ def ewkt(self): else: return self.wkt - #### Geometry Methods #### + # #### Geometry Methods #### def clone(self): "Clones this OGR Geometry." return OGRGeometry(capi.clone_geom(self.ptr), self.srs) @@ -405,7 +405,7 @@ def transform_to(self, srs): "For backwards-compatibility." self.transform(srs) - #### Topology Methods #### + # #### Topology Methods #### def _topology(self, func, other): """A generalized function for topology operations, takes a GDAL function and the other geometry to perform the operation on.""" @@ -448,7 +448,7 @@ def overlaps(self, other): "Returns True if this geometry overlaps the other." return self._topology(capi.ogr_overlaps, other) - #### Geometry-generation Methods #### + # #### Geometry-generation Methods #### def _geomgen(self, gen_func, other=None): "A helper routine for the OGR routines that generate geometries." if isinstance(other, OGRGeometry): diff --git a/django/contrib/gis/gdal/layer.py b/django/contrib/gis/gdal/layer.py index e2627a2da978..b5c730776766 100644 --- a/django/contrib/gis/gdal/layer.py +++ b/django/contrib/gis/gdal/layer.py @@ -26,7 +26,6 @@ class Layer(GDALBase): "A class that wraps an OGR Layer, needs to be instantiated from a DataSource object." - #### Python 'magic' routines #### def __init__(self, layer_ptr, ds): """ Initializes on an OGR C pointer to the Layer and the `DataSource` object @@ -95,7 +94,7 @@ def _make_feature(self, feat_id): # Should have returned a Feature, raise an OGRIndexError. raise OGRIndexError('Invalid feature id: %s.' % feat_id) - #### Layer properties #### + # #### Layer properties #### @property def extent(self): "Returns the extent (an Envelope) of this layer." @@ -189,7 +188,7 @@ def _set_spatial_filter(self, filter): spatial_filter = property(_get_spatial_filter, _set_spatial_filter) - #### Layer Methods #### + # #### Layer Methods #### def get_fields(self, field_name): """ Returns a list containing the given field name for every Feature diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index 280ae2ef5b7d..fd9a9946a016 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -66,7 +66,7 @@ def std_call(func): else: return lgdal[func] -#### Version-information functions. #### +# #### Version-information functions. #### # Returns GDAL library version information with the given key. _version_info = std_call('GDALVersionInfo') diff --git a/django/contrib/gis/gdal/prototypes/ds.py b/django/contrib/gis/gdal/prototypes/ds.py index 69257a114052..1b24df4bf39d 100644 --- a/django/contrib/gis/gdal/prototypes/ds.py +++ b/django/contrib/gis/gdal/prototypes/ds.py @@ -11,7 +11,7 @@ c_int_p = POINTER(c_int) # shortcut type -### Driver Routines ### +# Driver Routines register_all = void_output(lgdal.OGRRegisterAll, [], errcheck=False) cleanup_all = void_output(lgdal.OGRCleanupAll, [], errcheck=False) get_driver = voidptr_output(lgdal.OGRGetDriver, [c_int]) @@ -19,7 +19,7 @@ get_driver_count = int_output(lgdal.OGRGetDriverCount, []) get_driver_name = const_string_output(lgdal.OGR_Dr_GetName, [c_void_p], decoding='ascii') -### DataSource ### +# DataSource open_ds = voidptr_output(lgdal.OGROpen, [c_char_p, c_int, POINTER(c_void_p)]) destroy_ds = void_output(lgdal.OGR_DS_Destroy, [c_void_p], errcheck=False) release_ds = void_output(lgdal.OGRReleaseDataSource, [c_void_p]) @@ -28,7 +28,7 @@ get_layer_by_name = voidptr_output(lgdal.OGR_DS_GetLayerByName, [c_void_p, c_char_p]) get_layer_count = int_output(lgdal.OGR_DS_GetLayerCount, [c_void_p]) -### Layer Routines ### +# Layer Routines get_extent = void_output(lgdal.OGR_L_GetExtent, [c_void_p, POINTER(OGREnvelope), c_int]) get_feature = voidptr_output(lgdal.OGR_L_GetFeature, [c_void_p, c_long]) get_feature_count = int_output(lgdal.OGR_L_GetFeatureCount, [c_void_p, c_int]) @@ -43,14 +43,14 @@ [c_void_p, c_double, c_double, c_double, c_double], errcheck=False ) -### Feature Definition Routines ### +# Feature Definition Routines get_fd_geom_type = int_output(lgdal.OGR_FD_GetGeomType, [c_void_p]) get_fd_name = const_string_output(lgdal.OGR_FD_GetName, [c_void_p]) get_feat_name = const_string_output(lgdal.OGR_FD_GetName, [c_void_p]) get_field_count = int_output(lgdal.OGR_FD_GetFieldCount, [c_void_p]) get_field_defn = voidptr_output(lgdal.OGR_FD_GetFieldDefn, [c_void_p, c_int]) -### Feature Routines ### +# Feature Routines clone_feature = voidptr_output(lgdal.OGR_F_Clone, [c_void_p]) destroy_feature = void_output(lgdal.OGR_F_Destroy, [c_void_p], errcheck=False) feature_equal = int_output(lgdal.OGR_F_Equal, [c_void_p, c_void_p]) @@ -66,7 +66,7 @@ get_field_as_string = const_string_output(lgdal.OGR_F_GetFieldAsString, [c_void_p, c_int]) get_field_index = int_output(lgdal.OGR_F_GetFieldIndex, [c_void_p, c_char_p]) -### Field Routines ### +# Field Routines get_field_name = const_string_output(lgdal.OGR_Fld_GetNameRef, [c_void_p]) get_field_precision = int_output(lgdal.OGR_Fld_GetPrecision, [c_void_p]) get_field_type = int_output(lgdal.OGR_Fld_GetType, [c_void_p]) diff --git a/django/contrib/gis/gdal/prototypes/errcheck.py b/django/contrib/gis/gdal/prototypes/errcheck.py index d6c5ad17ebca..2940079b03c4 100644 --- a/django/contrib/gis/gdal/prototypes/errcheck.py +++ b/django/contrib/gis/gdal/prototypes/errcheck.py @@ -21,7 +21,7 @@ def ptr_byref(args, offset=-1): return args[offset]._obj -### String checking Routines ### +# ### String checking Routines ### def check_const_string(result, func, cargs, offset=None, cpl=False): """ Similar functionality to `check_string`, but does not free the pointer. @@ -62,17 +62,17 @@ def check_string(result, func, cargs, offset=-1, str_result=False): lgdal.VSIFree(ptr) return s -### DataSource, Layer error-checking ### +# ### DataSource, Layer error-checking ### -### Envelope checking ### +# ### Envelope checking ### def check_envelope(result, func, cargs, offset=-1): "Checks a function that returns an OGR Envelope by reference." env = ptr_byref(cargs, offset) return env -### Geometry error-checking routines ### +# ### Geometry error-checking routines ### def check_geom(result, func, cargs): "Checks a function that returns a geometry." # OGR_G_Clone may return an integer, even though the @@ -91,7 +91,7 @@ def check_geom_offset(result, func, cargs, offset=-1): return check_geom(geom, func, cargs) -### Spatial Reference error-checking routines ### +# ### Spatial Reference error-checking routines ### def check_srs(result, func, cargs): if isinstance(result, six.integer_types): result = c_void_p(result) @@ -100,7 +100,7 @@ def check_srs(result, func, cargs): return result -### Other error-checking routines ### +# ### Other error-checking routines ### def check_arg_errcode(result, func, cargs, cpl=False): """ The error code is returned in the last argument, by reference. diff --git a/django/contrib/gis/gdal/prototypes/geom.py b/django/contrib/gis/gdal/prototypes/geom.py index 22ff1ed833e1..15f4d5b89382 100644 --- a/django/contrib/gis/gdal/prototypes/geom.py +++ b/django/contrib/gis/gdal/prototypes/geom.py @@ -6,7 +6,7 @@ double_output, geom_output, int_output, srs_output, string_output, void_output) -### Generation routines specific to this module ### +# ### Generation routines specific to this module ### def env_func(f, argtypes): "For getting OGREnvelopes." f.argtypes = argtypes @@ -26,7 +26,7 @@ def topology_func(f): f.errchck = bool return f -### OGR_G ctypes function prototypes ### +# ### OGR_G ctypes function prototypes ### # GeoJSON routines. from_json = geom_output(lgdal.OGR_G_CreateGeometryFromJson, [c_char_p]) diff --git a/django/contrib/gis/gdal/prototypes/raster.py b/django/contrib/gis/gdal/prototypes/raster.py index db32ee1e0b0c..781c4ded89dc 100644 --- a/django/contrib/gis/gdal/prototypes/raster.py +++ b/django/contrib/gis/gdal/prototypes/raster.py @@ -18,14 +18,14 @@ const_string_output = partial(const_string_output, cpl=True) double_output = partial(double_output, cpl=True) -### Raster Driver Routines ### +# Raster Driver Routines register_all = void_output(lgdal.GDALAllRegister, []) get_driver = voidptr_output(lgdal.GDALGetDriver, [c_int]) get_driver_by_name = voidptr_output(lgdal.GDALGetDriverByName, [c_char_p], errcheck=False) get_driver_count = int_output(lgdal.GDALGetDriverCount, []) get_driver_description = const_string_output(lgdal.GDALGetDescription, [c_void_p]) -### Raster Data Source Routines ### +# Raster Data Source Routines create_ds = voidptr_output(lgdal.GDALCreate, [c_void_p, c_char_p, c_int, c_int, c_int, c_int]) open_ds = voidptr_output(lgdal.GDALOpen, [c_char_p, c_int]) close_ds = void_output(lgdal.GDALClose, [c_void_p]) @@ -43,7 +43,7 @@ get_ds_geotransform = void_output(lgdal.GDALGetGeoTransform, [c_void_p, POINTER(c_double * 6)], errcheck=False) set_ds_geotransform = void_output(lgdal.GDALSetGeoTransform, [c_void_p, POINTER(c_double * 6)]) -### Raster Band Routines ### +# Raster Band Routines band_io = void_output(lgdal.GDALRasterIO, [c_void_p, c_int, c_int, c_int, c_int, c_int, c_void_p, c_int, c_int, c_int, c_int, c_int]) get_band_xsize = int_output(lgdal.GDALGetRasterBandXSize, [c_void_p]) @@ -57,7 +57,7 @@ get_band_minimum = double_output(lgdal.GDALGetRasterMinimum, [c_void_p, POINTER(c_int)]) get_band_maximum = double_output(lgdal.GDALGetRasterMaximum, [c_void_p, POINTER(c_int)]) -### Reprojection routine ### +# Reprojection routine reproject_image = void_output(lgdal.GDALReprojectImage, [c_void_p, c_char_p, c_void_p, c_char_p, c_int, c_double, c_double, c_void_p, c_void_p, c_void_p]) diff --git a/django/contrib/gis/gdal/prototypes/srs.py b/django/contrib/gis/gdal/prototypes/srs.py index 71ec1ed603bc..424853704d86 100644 --- a/django/contrib/gis/gdal/prototypes/srs.py +++ b/django/contrib/gis/gdal/prototypes/srs.py @@ -4,7 +4,7 @@ double_output, int_output, srs_output, string_output, void_output) -## Shortcut generation for routines with known parameters. +# Shortcut generation for routines with known parameters. def srs_double(f): """ Creates a function prototype for the OSR routines that take diff --git a/django/contrib/gis/gdal/srs.py b/django/contrib/gis/gdal/srs.py index 8749d079b3b8..9450277275f4 100644 --- a/django/contrib/gis/gdal/srs.py +++ b/django/contrib/gis/gdal/srs.py @@ -37,7 +37,6 @@ from django.utils.encoding import force_bytes, force_text -#### Spatial Reference class. #### class SpatialReference(GDALBase): """ A wrapper for the OGRSpatialReference object. According to the GDAL Web site, @@ -45,7 +44,6 @@ class SpatialReference(GDALBase): systems (projections and datums) and to transform between them." """ - #### Python 'magic' routines #### def __init__(self, srs_input='', srs_type='user'): """ Creates a GDAL OSR Spatial Reference object from the given input. @@ -135,7 +133,7 @@ def __str__(self): "The string representation uses 'pretty' WKT." return self.pretty_wkt - #### SpatialReference Methods #### + # #### SpatialReference Methods #### def attr_value(self, target, index=0): """ The attribute value for the given target node (e.g. 'PROJCS'). The index @@ -176,7 +174,7 @@ def validate(self): "Checks to see if the given spatial reference is valid." capi.srs_validate(self.ptr) - #### Name & SRID properties #### + # #### Name & SRID properties #### @property def name(self): "Returns the name of this Spatial Reference." @@ -197,7 +195,7 @@ def srid(self): except (TypeError, ValueError): return None - #### Unit Properties #### + # #### Unit Properties #### @property def linear_name(self): "Returns the name of the linear units." @@ -238,7 +236,7 @@ def units(self): name = force_text(name) return (units, name) - #### Spheroid/Ellipsoid Properties #### + # #### Spheroid/Ellipsoid Properties #### @property def ellipsoid(self): """ @@ -262,7 +260,7 @@ def inverse_flattening(self): "Returns the Inverse Flattening for this Spatial Reference." return capi.invflattening(self.ptr, byref(c_int())) - #### Boolean Properties #### + # #### Boolean Properties #### @property def geographic(self): """ @@ -284,7 +282,7 @@ def projected(self): """ return bool(capi.isprojected(self.ptr)) - #### Import Routines ##### + # #### Import Routines ##### def import_epsg(self, epsg): "Imports the Spatial Reference from the EPSG code (an integer)." capi.from_epsg(self.ptr, epsg) @@ -305,7 +303,7 @@ def import_xml(self, xml): "Imports the Spatial Reference from an XML string." capi.from_xml(self.ptr, xml) - #### Export Properties #### + # #### Export Properties #### @property def wkt(self): "Returns the WKT representation of this Spatial Reference." diff --git a/django/contrib/gis/gdal/tests/test_geom.py b/django/contrib/gis/gdal/tests/test_geom.py index 4f422c06f634..d0651fcaf2c4 100644 --- a/django/contrib/gis/gdal/tests/test_geom.py +++ b/django/contrib/gis/gdal/tests/test_geom.py @@ -196,7 +196,7 @@ def test06_linearring(self): prev = OGRGeometry('POINT(0 0)') for rr in self.geometries.linearrings: lr = OGRGeometry(rr.wkt) - #self.assertEqual(101, lr.geom_type.num) + # self.assertEqual(101, lr.geom_type.num) self.assertEqual('LINEARRING', lr.geom_name) self.assertEqual(rr.n_p, len(lr)) self.assertEqual(lr, OGRGeometry(rr.wkt)) diff --git a/django/contrib/gis/geoip/base.py b/django/contrib/gis/geoip/base.py index 9409019d9401..de0bfd18a7d7 100644 --- a/django/contrib/gis/geoip/base.py +++ b/django/contrib/gis/geoip/base.py @@ -18,7 +18,6 @@ lite_regex = re.compile(r'^GEO-\d{3}LITE') -#### GeoIP classes #### class GeoIPException(Exception): pass @@ -201,7 +200,7 @@ def country(self, query): 'country_name': self.country_name(query), } - #### Coordinate retrieval routines #### + # #### Coordinate retrieval routines #### def coords(self, query, ordering=('longitude', 'latitude')): cdict = self.city(query) if cdict is None: @@ -226,7 +225,7 @@ def geos(self, query): else: return None - #### GeoIP Database Information Routines #### + # #### GeoIP Database Information Routines #### @property def country_info(self): "Returns information about the GeoIP country database." @@ -253,7 +252,7 @@ def info(self): info += 'GeoIP Library:\n\t%s\n' % GeoIP_lib_version() return info + 'Country:\n\t%s\nCity:\n\t%s' % (self.country_info, self.city_info) - #### Methods for compatibility w/the GeoIP-Python API. #### + # #### Methods for compatibility w/the GeoIP-Python API. #### @classmethod def open(cls, full_path, cache): return GeoIP(full_path, cache) diff --git a/django/contrib/gis/geoip/prototypes.py b/django/contrib/gis/geoip/prototypes.py index 22c17f006fd3..751b4c4b0510 100644 --- a/django/contrib/gis/geoip/prototypes.py +++ b/django/contrib/gis/geoip/prototypes.py @@ -2,7 +2,7 @@ from django.contrib.gis.geoip.libgeoip import lgeoip, free -#### GeoIP C Structure definitions #### +# #### GeoIP C Structure definitions #### class GeoIPRecord(Structure): _fields_ = [('country_code', c_char_p), @@ -36,7 +36,7 @@ class GeoIPTag(Structure): RECTYPE = POINTER(GeoIPRecord) DBTYPE = POINTER(GeoIPTag) -#### ctypes function prototypes #### +# #### ctypes function prototypes #### # GeoIP_lib_version appeared in version 1.4.7. if hasattr(lgeoip, 'GeoIP_lib_version'): diff --git a/django/contrib/gis/geos/collections.py b/django/contrib/gis/geos/collections.py index 83f57cbd18fa..82a2169ab018 100644 --- a/django/contrib/gis/geos/collections.py +++ b/django/contrib/gis/geos/collections.py @@ -49,7 +49,7 @@ def __len__(self): "Returns the number of geometries in this Collection." return self.num_geom - ### Methods for compatibility with ListMixin ### + # ### Methods for compatibility with ListMixin ### def _create_collection(self, length, items): # Creating the geometry pointer array. geoms = get_pointer_arr(length) diff --git a/django/contrib/gis/geos/coordseq.py b/django/contrib/gis/geos/coordseq.py index c238d4cda513..d11c6fbde399 100644 --- a/django/contrib/gis/geos/coordseq.py +++ b/django/contrib/gis/geos/coordseq.py @@ -16,7 +16,6 @@ class GEOSCoordSeq(GEOSBase): ptr_type = CS_PTR - #### Python 'magic' routines #### def __init__(self, ptr, z=False): "Initializes from a GEOS pointer." if not isinstance(ptr, CS_PTR): @@ -68,7 +67,7 @@ def __setitem__(self, index, value): if set_3d: self.setZ(index, value[2]) - #### Internal Routines #### + # #### Internal Routines #### def _checkindex(self, index): "Checks the given index." sz = self.size @@ -80,7 +79,7 @@ def _checkdim(self, dim): if dim < 0 or dim > 2: raise GEOSException('invalid ordinate dimension "%d"' % dim) - #### Ordinate getting and setting routines #### + # #### Ordinate getting and setting routines #### def getOrdinate(self, dimension, index): "Returns the value for the given dimension and index." self._checkindex(index) @@ -117,7 +116,7 @@ def setZ(self, index, value): "Set Z with the value at the given index." self.setOrdinate(2, index, value) - ### Dimensions ### + # ### Dimensions ### @property def size(self): "Returns the size of this coordinate sequence." @@ -136,7 +135,7 @@ def hasz(self): """ return self._z - ### Other Methods ### + # ### Other Methods ### def clone(self): "Clones this coordinate sequence." return GEOSCoordSeq(capi.cs_clone(self.ptr), self.hasz) diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index c3ba634df10e..dd110c02363e 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -43,7 +43,6 @@ class GEOSGeometry(GEOSBase, ListMixin): ptr_type = GEOM_PTR - #### Python 'magic' routines #### def __init__(self, geo_input, srid=None): """ The base constructor for GEOS geometry objects, and may take the @@ -175,7 +174,7 @@ def __ne__(self, other): "The not equals operator." return not (self == other) - ### Geometry set-like operations ### + # ### Geometry set-like operations ### # Thanks to Sean Gillies for inspiration: # http://lists.gispython.org/pipermail/community/2007-July/001034.html # g = g1 | g2 @@ -198,7 +197,7 @@ def __xor__(self, other): "Return the symmetric difference of this Geometry and the other." return self.sym_difference(other) - #### Coordinate Sequence Routines #### + # #### Coordinate Sequence Routines #### @property def has_cs(self): "Returns True if this Geometry has a coordinate sequence, False if not." @@ -221,7 +220,7 @@ def coord_seq(self): if self.has_cs: return self._cs.clone() - #### Geometry Info #### + # #### Geometry Info #### @property def geom_type(self): "Returns a string representing the Geometry type, e.g. 'Polygon'" @@ -256,7 +255,7 @@ def normalize(self): "Converts this Geometry to normal form (or canonical form)." return capi.geos_normalize(self.ptr) - #### Unary predicates #### + # #### Unary predicates #### @property def empty(self): """ @@ -292,7 +291,7 @@ def valid_reason(self): """ return capi.geos_isvalidreason(self.ptr).decode() - #### Binary predicates. #### + # #### Binary predicates. #### def contains(self, other): "Returns true if other.within(this) returns true." return capi.geos_contains(self.ptr, other.ptr) @@ -360,7 +359,7 @@ def within(self, other): """ return capi.geos_within(self.ptr, other.ptr) - #### SRID Routines #### + # #### SRID Routines #### def get_srid(self): "Gets the SRID for the geometry, returns None if no SRID is set." s = capi.geos_get_srid(self.ptr) @@ -374,7 +373,7 @@ def set_srid(self, srid): capi.geos_set_srid(self.ptr, srid) srid = property(get_srid, set_srid) - #### Output Routines #### + # #### Output Routines #### @property def ewkt(self): """ @@ -454,7 +453,7 @@ def prepared(self): """ return PreparedGeometry(self) - #### GDAL-specific output routines #### + # #### GDAL-specific output routines #### @property def ogr(self): "Returns the OGR Geometry for this Geometry." @@ -525,7 +524,7 @@ def transform(self, ct, clone=False): else: raise GEOSException('Transformed WKB was invalid.') - #### Topology Routines #### + # #### Topology Routines #### def _topology(self, gptr): "Helper routine to return Geometry from the given pointer." return GEOSGeometry(gptr, srid=self.srid) @@ -639,7 +638,7 @@ def union(self, other): "Returns a Geometry representing all the points in this Geometry and other." return self._topology(capi.geos_union(self.ptr, other.ptr)) - #### Other Routines #### + # #### Other Routines #### @property def area(self): "Returns the area of the Geometry." diff --git a/django/contrib/gis/geos/libgeos.py b/django/contrib/gis/geos/libgeos.py index 207fa780a4df..f218b17f6f7a 100644 --- a/django/contrib/gis/geos/libgeos.py +++ b/django/contrib/gis/geos/libgeos.py @@ -87,7 +87,7 @@ def error_h(fmt, lst): logger.error('GEOS_ERROR: %s\n' % err_msg) error_h = ERRORFUNC(error_h) -#### GEOS Geometry C data structures, and utility functions. #### +# #### GEOS Geometry C data structures, and utility functions. #### # Opaque GEOS geometry structures, used for GEOM_PTR and CS_PTR diff --git a/django/contrib/gis/geos/linestring.py b/django/contrib/gis/geos/linestring.py index 829ab48945e6..ec06cb19e805 100644 --- a/django/contrib/gis/geos/linestring.py +++ b/django/contrib/gis/geos/linestring.py @@ -11,7 +11,6 @@ class LineString(GEOSGeometry): _init_func = capi.create_linestring _minlength = 2 - #### Python 'magic' routines #### def __init__(self, *args, **kwargs): """ Initializes on the given sequence -- may take lists, tuples, NumPy arrays @@ -116,7 +115,7 @@ def _checkdim(self, dim): if dim not in (2, 3): raise TypeError('Dimension mismatch.') - #### Sequence Properties #### + # #### Sequence Properties #### @property def tuple(self): "Returns a tuple version of the geometry from the coordinate sequence." diff --git a/django/contrib/gis/geos/mutable_list.py b/django/contrib/gis/geos/mutable_list.py index f01bc4275f69..cea371062241 100644 --- a/django/contrib/gis/geos/mutable_list.py +++ b/django/contrib/gis/geos/mutable_list.py @@ -63,7 +63,7 @@ class _IndexError: _maxlength = None _IndexError = IndexError - ### Python initialization and special list interface methods ### + # ### Python initialization and special list interface methods ### def __init__(self, *args, **kwargs): if not hasattr(self, '_get_single_internal'): @@ -117,7 +117,7 @@ def __iter__(self): for i in range(len(self)): yield self[i] - ### Special methods for arithmetic operations ### + # ### Special methods for arithmetic operations ### def __add__(self, other): 'add another list-like object' return self.__class__(list(self) + list(other)) @@ -175,8 +175,8 @@ def __lt__(self, other): return False return len(self) < olen - ### Public list interface Methods ### - ## Non-mutating ## + # ### Public list interface Methods ### + # ## Non-mutating ## def count(self, val): "Standard list count method" count = 0 @@ -192,7 +192,7 @@ def index(self, val): return i raise ValueError('%s not found in object' % str(val)) - ## Mutating ## + # ## Mutating ## def append(self, val): "Standard list append method" self[len(self):] = [val] @@ -235,7 +235,7 @@ def sort(self, cmp=None, key=None, reverse=False): temp.sort(reverse=reverse) self[:] = temp - ### Private routines ### + # ### Private routines ### def _rebuild(self, newLen, newItems): if newLen < self._minlength: raise ValueError('Must have at least %d items' % self._minlength) diff --git a/django/contrib/gis/geos/point.py b/django/contrib/gis/geos/point.py index 2cc5c13683b0..dadab29bbf16 100644 --- a/django/contrib/gis/geos/point.py +++ b/django/contrib/gis/geos/point.py @@ -128,7 +128,7 @@ def set_z(self, value): y = property(get_y, set_y) z = property(get_z, set_z) - ### Tuple setting and retrieval routines. ### + # ### Tuple setting and retrieval routines. ### def get_coords(self): "Returns a tuple of the point." return self._cs.tuple diff --git a/django/contrib/gis/geos/polygon.py b/django/contrib/gis/geos/polygon.py index 38b533df8f4e..89dd0f06f6b3 100644 --- a/django/contrib/gis/geos/polygon.py +++ b/django/contrib/gis/geos/polygon.py @@ -66,7 +66,7 @@ def from_bbox(cls, bbox): (x0, y0, x0, y1, x1, y1, x1, y0, x0, y0)) return Polygon(((x0, y0), (x0, y1), (x1, y1), (x1, y0), (x0, y0))) - ### These routines are needed for list-like operation w/ListMixin ### + # ### These routines are needed for list-like operation w/ListMixin ### def _create_polygon(self, length, items): # Instantiate LinearRing objects if necessary, but don't clone them yet # _construct_ring will throw a TypeError if a parameter isn't a valid ring @@ -143,7 +143,7 @@ def _get_single_external(self, index): _set_single = GEOSGeometry._set_single_rebuild _assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild - #### Polygon Properties #### + # #### Polygon Properties #### @property def num_interior_rings(self): "Returns the number of interior rings." diff --git a/django/contrib/gis/geos/prototypes/coordseq.py b/django/contrib/gis/geos/prototypes/coordseq.py index b115fc34473d..d5aa3da318ce 100644 --- a/django/contrib/gis/geos/prototypes/coordseq.py +++ b/django/contrib/gis/geos/prototypes/coordseq.py @@ -4,7 +4,7 @@ from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc -## Error-checking routines specific to coordinate sequences. ## +# ## Error-checking routines specific to coordinate sequences. ## def check_cs_ptr(result, func, cargs): "Error checking on routines that return Geometries." if not result: @@ -30,7 +30,7 @@ def check_cs_get(result, func, cargs): return last_arg_byref(cargs) -## Coordinate sequence prototype generation functions. ## +# ## Coordinate sequence prototype generation functions. ## def cs_int(func): "For coordinate sequence routines that return an integer." func.argtypes = [CS_PTR, POINTER(c_uint)] @@ -66,7 +66,7 @@ def cs_output(func, argtypes): func.errcheck = check_cs_ptr return func -## Coordinate Sequence ctypes prototypes ## +# ## Coordinate Sequence ctypes prototypes ## # Coordinate Sequence constructors & cloning. cs_clone = cs_output(GEOSFunc('GEOSCoordSeq_clone'), [CS_PTR]) diff --git a/django/contrib/gis/geos/prototypes/errcheck.py b/django/contrib/gis/geos/prototypes/errcheck.py index 034cfe589929..0a624ccec7f4 100644 --- a/django/contrib/gis/geos/prototypes/errcheck.py +++ b/django/contrib/gis/geos/prototypes/errcheck.py @@ -13,7 +13,6 @@ free.restype = None -### ctypes error checking routines ### def last_arg_byref(args): "Returns the last C argument's value by reference." return args[-1]._obj.value diff --git a/django/contrib/gis/geos/prototypes/geom.py b/django/contrib/gis/geos/prototypes/geom.py index 021fe6e469b8..5416625c678a 100644 --- a/django/contrib/gis/geos/prototypes/geom.py +++ b/django/contrib/gis/geos/prototypes/geom.py @@ -19,7 +19,7 @@ class geos_char_p(c_char_p): pass -### ctypes generation functions ### +# ### ctypes generation functions ### def bin_constructor(func): "Generates a prototype for binary construction (HEX, WKB) GEOS routines." func.argtypes = [c_char_p, c_size_t] @@ -69,7 +69,7 @@ def string_from_geom(func): func.errcheck = check_string return func -### ctypes prototypes ### +# ### ctypes prototypes ### # Deprecated creation routines from WKB, HEX, WKT from_hex = bin_constructor(GEOSFunc('GEOSGeomFromHEX_buf')) diff --git a/django/contrib/gis/geos/prototypes/io.py b/django/contrib/gis/geos/prototypes/io.py index 34bd34886ac2..8083d93ee078 100644 --- a/django/contrib/gis/geos/prototypes/io.py +++ b/django/contrib/gis/geos/prototypes/io.py @@ -10,7 +10,7 @@ from django.utils.encoding import force_bytes -### The WKB/WKT Reader/Writer structures and pointers ### +# ### The WKB/WKT Reader/Writer structures and pointers ### class WKTReader_st(Structure): pass @@ -31,7 +31,7 @@ class WKBWriter_st(Structure): WKB_READ_PTR = POINTER(WKBReader_st) WKB_WRITE_PTR = POINTER(WKBReader_st) -### WKTReader routines ### +# WKTReader routines wkt_reader_create = GEOSFunc('GEOSWKTReader_create') wkt_reader_create.restype = WKT_READ_PTR @@ -43,7 +43,7 @@ class WKBWriter_st(Structure): wkt_reader_read.restype = GEOM_PTR wkt_reader_read.errcheck = check_geom -### WKTWriter routines ### +# WKTWriter routines wkt_writer_create = GEOSFunc('GEOSWKTWriter_create') wkt_writer_create.restype = WKT_WRITE_PTR @@ -67,7 +67,7 @@ class WKBWriter_st(Structure): wkt_writer_get_outdim = lambda ptr: 2 wkt_writer_set_outdim = lambda ptr, dim: None -### WKBReader routines ### +# WKBReader routines wkb_reader_create = GEOSFunc('GEOSWKBReader_create') wkb_reader_create.restype = WKB_READ_PTR @@ -89,7 +89,7 @@ def wkb_read_func(func): wkb_reader_read = wkb_read_func(GEOSFunc('GEOSWKBReader_read')) wkb_reader_read_hex = wkb_read_func(GEOSFunc('GEOSWKBReader_readHEX')) -### WKBWriter routines ### +# WKBWriter routines wkb_writer_create = GEOSFunc('GEOSWKBWriter_create') wkb_writer_create.restype = WKB_WRITE_PTR @@ -127,7 +127,7 @@ def wkb_writer_set(func, argtype=c_int): wkb_writer_set_include_srid = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setIncludeSRID'), argtype=c_char) -### Base I/O Class ### +# ### Base I/O Class ### class IOBase(GEOSBase): "Base class for GEOS I/O objects." def __init__(self): @@ -139,7 +139,7 @@ def __del__(self): if self._ptr: self._destructor(self._ptr) -### Base WKB/WKT Reading and Writing objects ### +# ### Base WKB/WKT Reading and Writing objects ### # Non-public WKB/WKT reader classes for internal use because @@ -172,7 +172,7 @@ def read(self, wkb): raise TypeError -### WKB/WKT Writer Classes ### +# ### WKB/WKT Writer Classes ### class WKTWriter(IOBase): _constructor = wkt_writer_create _destructor = wkt_writer_destroy @@ -206,7 +206,7 @@ def write_hex(self, geom): "Returns the HEXEWKB representation of the given geometry." return wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t())) - ### WKBWriter Properties ### + # ### WKBWriter Properties ### # Property for getting/setting the byteorder. def _get_byteorder(self): diff --git a/django/contrib/gis/geos/prototypes/misc.py b/django/contrib/gis/geos/prototypes/misc.py index 0e62e916b042..b256fd3d0bbd 100644 --- a/django/contrib/gis/geos/prototypes/misc.py +++ b/django/contrib/gis/geos/prototypes/misc.py @@ -12,7 +12,7 @@ __all__ = ['geos_area', 'geos_distance', 'geos_length', 'geos_isvalidreason'] -### ctypes generator function ### +# ### ctypes generator function ### def dbl_from_geom(func, num_geom=1): """ Argument is a Geometry, return type is double that is passed @@ -25,7 +25,7 @@ def dbl_from_geom(func, num_geom=1): func.errcheck = check_dbl return func -### ctypes prototypes ### +# ### ctypes prototypes ### # Area, distance, and length prototypes. geos_area = dbl_from_geom(GEOSFunc('GEOSArea')) diff --git a/django/contrib/gis/geos/prototypes/predicates.py b/django/contrib/gis/geos/prototypes/predicates.py index f3c30a50623d..0bd1fabf9faf 100644 --- a/django/contrib/gis/geos/prototypes/predicates.py +++ b/django/contrib/gis/geos/prototypes/predicates.py @@ -8,7 +8,7 @@ from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc -## Binary & unary predicate functions ## +# ## Binary & unary predicate functions ## def binary_predicate(func, *args): "For GEOS binary predicate functions." argtypes = [GEOM_PTR, GEOM_PTR] @@ -27,14 +27,14 @@ def unary_predicate(func): func.errcheck = check_predicate return func -## Unary Predicates ## +# ## Unary Predicates ## geos_hasz = unary_predicate(GEOSFunc('GEOSHasZ')) geos_isempty = unary_predicate(GEOSFunc('GEOSisEmpty')) geos_isring = unary_predicate(GEOSFunc('GEOSisRing')) geos_issimple = unary_predicate(GEOSFunc('GEOSisSimple')) geos_isvalid = unary_predicate(GEOSFunc('GEOSisValid')) -## Binary Predicates ## +# ## Binary Predicates ## geos_contains = binary_predicate(GEOSFunc('GEOSContains')) geos_crosses = binary_predicate(GEOSFunc('GEOSCrosses')) geos_disjoint = binary_predicate(GEOSFunc('GEOSDisjoint')) diff --git a/django/contrib/gis/geos/prototypes/topology.py b/django/contrib/gis/geos/prototypes/topology.py index 83d6706eaa50..06daa12cca65 100644 --- a/django/contrib/gis/geos/prototypes/topology.py +++ b/django/contrib/gis/geos/prototypes/topology.py @@ -27,7 +27,7 @@ def topology(func, *args, **kwargs): func.errcheck = kwargs.get('errcheck', check_geom) return func -### Topology Routines ### +# Topology Routines geos_boundary = topology(GEOSFunc('GEOSBoundary')) geos_buffer = topology(GEOSFunc('GEOSBuffer'), c_double, c_int) geos_centroid = topology(GEOSFunc('GEOSGetCentroid')) diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index 48d4aabf1365..794ed6d75e7b 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -466,7 +466,7 @@ def test_multipolygons(self): def test_memory_hijinks(self): "Testing Geometry __del__() on rings and polygons." - #### Memory issues with rings and polygons + # #### Memory issues with rings and poly # These tests are needed to ensure sanity with writable geometries. @@ -661,7 +661,7 @@ def test_custom_srid(self): def test_mutable_geometries(self): "Testing the mutability of Polygons and Geometry Collections." - ### Testing the mutability of Polygons ### + # ### Testing the mutability of Polygons ### for p in self.geometries.polygons: poly = fromstr(p.wkt) @@ -681,7 +681,7 @@ def test_mutable_geometries(self): self.assertEqual(poly.exterior_ring, new_shell) self.assertEqual(poly[0], new_shell) - ### Testing the mutability of Geometry Collections + # ### Testing the mutability of Geometry Collections for tg in self.geometries.multipoints: mp = fromstr(tg.wkt) for i in range(len(mp)): @@ -719,11 +719,11 @@ def test_mutable_geometries(self): # Extreme (!!) __setitem__ -- no longer works, have to detect # in the first object that __setitem__ is called in the subsequent # objects -- maybe mpoly[0, 0, 0] = (3.14, 2.71)? - #mpoly[0][0][0] = (3.14, 2.71) - #self.assertEqual((3.14, 2.71), mpoly[0][0][0]) + # mpoly[0][0][0] = (3.14, 2.71) + # self.assertEqual((3.14, 2.71), mpoly[0][0][0]) # Doing it more slowly.. - #self.assertEqual((3.14, 2.71), mpoly[0].shell[0]) - #del mpoly + # self.assertEqual((3.14, 2.71), mpoly[0].shell[0]) + # del mpoly def test_threed(self): "Testing three-dimensional geometries." diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index 7c999a20d0aa..830fe72e6231 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -37,7 +37,7 @@ def test_fixtures(self): def test_proxy(self): "Testing Lazy-Geometry support (using the GeometryProxy)." - ## Testing on a Point + # Testing on a Point pnt = Point(0, 0) nullcity = City(name='NullCity', point=pnt) nullcity.save() @@ -74,7 +74,7 @@ def test_proxy(self): self.assertEqual(Point(23, 5), City.objects.get(name='NullCity').point) nullcity.delete() - ## Testing on a Polygon + # Testing on a Polygon shell = LinearRing((0, 0), (0, 100), (100, 100), (100, 0), (0, 0)) inner = LinearRing((40, 40), (40, 60), (60, 60), (60, 40), (40, 40)) diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py index 2ff23fc38be0..410024e324ae 100644 --- a/django/contrib/gis/utils/layermapping.py +++ b/django/contrib/gis/utils/layermapping.py @@ -144,7 +144,7 @@ def __init__(self, model, data, mapping, layer=0, else: raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode) - #### Checking routines used during initialization #### + # #### Checking routines used during initialization #### def check_fid_range(self, fid_range): "This checks the `fid_range` keyword." if fid_range: @@ -331,7 +331,7 @@ def unique_kwargs(self, kwargs): else: return {fld: kwargs[fld] for fld in self.unique} - #### Verification routines used in constructing model keyword arguments. #### + # #### Verification routines used in constructing model keyword arguments. #### def verify_ogr_field(self, ogr_field, model_field): """ Verifies if the OGR Field contents are acceptable to the Django @@ -441,7 +441,7 @@ def verify_geom(self, geom, model_field): # Returning the WKT of the geometry. return g.wkt - #### Other model methods #### + # #### Other model methods #### def coord_transform(self): "Returns the coordinate transformation object." SpatialRefSys = self.spatial_backend.spatial_ref_sys() diff --git a/django/db/backends/base/base.py b/django/db/backends/base/base.py index cbc49830463c..5c0e8755eaa8 100644 --- a/django/db/backends/base/base.py +++ b/django/db/backends/base/base.py @@ -84,7 +84,7 @@ def queries(self): "will be returned.".format(self.queries_log.maxlen)) return list(self.queries_log) - ##### Backend-specific methods for creating connections and cursors ##### + # ##### Backend-specific methods for creating connections and cursors ##### def get_connection_params(self): """Returns a dict of parameters suitable for get_new_connection.""" @@ -102,7 +102,7 @@ def create_cursor(self): """Creates a cursor. Assumes that a connection is established.""" raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a create_cursor() method') - ##### Backend-specific methods for creating connections ##### + # ##### Backend-specific methods for creating connections ##### def connect(self): """Connects to the database. Assumes that the connection is closed.""" @@ -130,7 +130,7 @@ def ensure_connection(self): with self.wrap_database_errors: self.connect() - ##### Backend-specific wrappers for PEP-249 connection methods ##### + # ##### Backend-specific wrappers for PEP-249 connection methods ##### def _cursor(self): self.ensure_connection() @@ -152,7 +152,7 @@ def _close(self): with self.wrap_database_errors: return self.connection.close() - ##### Generic wrappers for PEP-249 connection methods ##### + # ##### Generic wrappers for PEP-249 connection methods ##### def cursor(self): """ @@ -204,7 +204,7 @@ def close(self): else: self.connection = None - ##### Backend-specific savepoint management methods ##### + # ##### Backend-specific savepoint management methods ##### def _savepoint(self, sid): with self.cursor() as cursor: @@ -222,7 +222,7 @@ def _savepoint_allowed(self): # Savepoints cannot be created outside a transaction return self.features.uses_savepoints and not self.get_autocommit() - ##### Generic savepoint management methods ##### + # ##### Generic savepoint management methods ##### def savepoint(self): """ @@ -270,7 +270,7 @@ def clean_savepoints(self): """ self.savepoint_state = 0 - ##### Backend-specific transaction management methods ##### + # ##### Backend-specific transaction management methods ##### def _set_autocommit(self, autocommit): """ @@ -278,7 +278,7 @@ def _set_autocommit(self, autocommit): """ raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a _set_autocommit() method') - ##### Generic transaction management methods ##### + # ##### Generic transaction management methods ##### def get_autocommit(self): """ @@ -328,7 +328,7 @@ def validate_no_broken_transaction(self): "An error occurred in the current transaction. You can't " "execute queries until the end of the 'atomic' block.") - ##### Foreign key constraints checks handling ##### + # ##### Foreign key constraints checks handling ##### @contextmanager def constraint_checks_disabled(self): @@ -365,7 +365,7 @@ def check_constraints(self, table_names=None): """ pass - ##### Connection termination handling ##### + # ##### Connection termination handling ##### def is_usable(self): """ @@ -404,7 +404,7 @@ def close_if_unusable_or_obsolete(self): self.close() return - ##### Thread safety handling ##### + # ##### Thread safety handling ##### def validate_thread_sharing(self): """ @@ -421,7 +421,7 @@ def validate_thread_sharing(self): "thread id %s." % (self.alias, self._thread_ident, thread.get_ident())) - ##### Miscellaneous ##### + # ##### Miscellaneous ##### def prepare_database(self): """ diff --git a/django/template/__init__.py b/django/template/__init__.py index 3d26bcc945ef..b2566decad58 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -39,7 +39,7 @@ """ -### Multiple Template Engines +# Multiple Template Engines from .engine import Engine @@ -51,7 +51,7 @@ __all__ = ('Engine', 'engines') -### Django Template Language +# Django Template Language # Public exceptions from .base import (TemplateDoesNotExist, TemplateSyntaxError, # NOQA diff --git a/docs/_ext/applyxrefs.py b/docs/_ext/applyxrefs.py index 513c17d84ecc..d4cd79137e08 100644 --- a/docs/_ext/applyxrefs.py +++ b/docs/_ext/applyxrefs.py @@ -34,7 +34,7 @@ def has_target(fn): print("Can't open or read %s. Not touching it." % fn) return (True, None) - #print fn, len(lines) + # print fn, len(lines) if len(lines) < 1: print("Not touching empty file %s." % fn) return (True, None) @@ -56,7 +56,7 @@ def main(argv=None): files.extend((dirpath, f) for f in filenames) files.sort() files = [os.path.join(p, fn) for p, fn in files if fn.endswith('.txt')] - #print files + # print files for fn in files: if fn in DONT_TOUCH: diff --git a/docs/conf.py b/docs/conf.py index a8ad09028739..be1050ba36f3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,7 +56,7 @@ source_suffix = '.txt' # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'contents' @@ -91,14 +91,14 @@ def django_release(): # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # Location for .po/.mo translation files used when language is set locale_dirs = ['locale/'] # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. today_fmt = '%B %d, %Y' @@ -107,7 +107,7 @@ def django_release(): exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True @@ -145,31 +145,31 @@ def django_release(): # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ["_theme"] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ["_static"] +# html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. @@ -183,40 +183,40 @@ def django_release(): html_translator_class = "djangodocs.DjangoHTMLTranslator" # Content template for the index page. -#html_index = '' +# html_index = '' # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Djangodoc' @@ -241,7 +241,7 @@ def django_release(): # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). -#latex_documents = [] +# latex_documents = [] latex_documents = [ ('contents', 'django.tex', 'Django Documentation', 'Django Software Foundation', 'manual'), @@ -249,23 +249,23 @@ def django_release(): # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- @@ -296,7 +296,7 @@ def django_release(): epub_copyright = copyright # The basename for the epub file. It defaults to the project name. -#epub_basename = 'Django' +# epub_basename = 'Django' # The HTML theme for the epub output. Since the default themes are not optimized # for small screen space, using the same theme for HTML and epub output is @@ -306,55 +306,55 @@ def django_release(): # The language of the text. It defaults to the language option # or en if the language is not set. -#epub_language = '' +# epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' +# epub_scheme = '' # The unique identifier of the text. This can be an ISBN number # or the project homepage. -#epub_identifier = '' +# epub_identifier = '' # A unique identification for the text. -#epub_uid = '' +# epub_uid = '' # A tuple containing the cover image and cover page html template filenames. epub_cover = ('', 'epub-cover.html') # A sequence of (type, uri, title) tuples for the guide element of content.opf. -#epub_guide = () +# epub_guide = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. -#epub_pre_files = [] +# epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. -#epub_post_files = [] +# epub_post_files = [] # A list of files that should not be packed into the epub file. -#epub_exclude_files = [] +# epub_exclude_files = [] # The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 +# epub_tocdepth = 3 # Allow duplicate toc entries. -#epub_tocdup = True +# epub_tocdup = True # Choose between 'default' and 'includehidden'. -#epub_tocscope = 'default' +# epub_tocscope = 'default' # Fix unsupported image types using the PIL. -#epub_fix_images = False +# epub_fix_images = False # Scale large images. -#epub_max_image_width = 0 +# epub_max_image_width = 0 # How to display URL addresses: 'footnote', 'no', or 'inline'. -#epub_show_urls = 'inline' +# epub_show_urls = 'inline' # If false, no index is generated. -#epub_use_index = True +# epub_use_index = True # -- ticket options ------------------------------------------------------------ ticket_url = 'https://code.djangoproject.com/ticket/%s' diff --git a/setup.cfg b/setup.cfg index 58c9b2914c6a..28353841d8a9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,7 +4,7 @@ install-script = scripts/rpm-install.sh [flake8] exclude=build,.git,./django/utils/dictconfig.py,./django/utils/unittest.py,./django/utils/lru_cache.py,./django/utils/six.py,./django/conf/app_template/*,./django/dispatch/weakref_backports.py,./tests/.env,./xmlrunner -ignore=E123,E128,E265,E501,W601 +ignore=E123,E128,E501,W601 max-line-length = 119 [metadata] diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 979ebefa01c7..983073a25f02 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -464,7 +464,7 @@ def test_parameter_escaping(self): @unittest.skipUnless(connection.vendor == 'sqlite', "This is an sqlite-specific issue") def test_sqlite_parameter_escaping(self): - #13648: '%s' escaping support for sqlite3 + # '%s' escaping support for sqlite3 #13648 cursor = connection.cursor() cursor.execute("select strftime('%s', date('now'))") response = cursor.fetchall()[0][0] @@ -502,7 +502,7 @@ def create_squares(self, args, paramstyle, multiple): cursor.execute(query, args) def test_cursor_executemany(self): - #4896: Test cursor.executemany + # Test cursor.executemany #4896 args = [(i, i ** 2) for i in range(-5, 6)] self.create_squares_with_executemany(args) self.assertEqual(models.Square.objects.count(), 11) @@ -511,13 +511,13 @@ def test_cursor_executemany(self): self.assertEqual(square.square, i ** 2) def test_cursor_executemany_with_empty_params_list(self): - #4765: executemany with params=[] does nothing + # Test executemany with params=[] does nothing #4765 args = [] self.create_squares_with_executemany(args) self.assertEqual(models.Square.objects.count(), 0) def test_cursor_executemany_with_iterator(self): - #10320: executemany accepts iterators + # Test executemany accepts iterators #10320 args = iter((i, i ** 2) for i in range(-3, 2)) self.create_squares_with_executemany(args) self.assertEqual(models.Square.objects.count(), 5) @@ -530,14 +530,14 @@ def test_cursor_executemany_with_iterator(self): @skipUnlessDBFeature('supports_paramstyle_pyformat') def test_cursor_execute_with_pyformat(self): - #10070: Support pyformat style passing of parameters + # Support pyformat style passing of parameters #10070 args = {'root': 3, 'square': 9} self.create_squares(args, 'pyformat', multiple=False) self.assertEqual(models.Square.objects.count(), 1) @skipUnlessDBFeature('supports_paramstyle_pyformat') def test_cursor_executemany_with_pyformat(self): - #10070: Support pyformat style passing of parameters + # Support pyformat style passing of parameters #10070 args = [{'root': i, 'square': i ** 2} for i in range(-5, 6)] self.create_squares(args, 'pyformat', multiple=True) self.assertEqual(models.Square.objects.count(), 11) @@ -558,7 +558,7 @@ def test_cursor_executemany_with_pyformat_iterator(self): self.assertEqual(models.Square.objects.count(), 9) def test_unicode_fetches(self): - #6254: fetchone, fetchmany, fetchall return strings as unicode objects + # fetchone, fetchmany, fetchall return strings as unicode objects #6254 qn = connection.ops.quote_name models.Person(first_name="John", last_name="Doe").save() models.Person(first_name="Jane", last_name="Doe").save() diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py index 14ad306c6572..8bc3302afc11 100644 --- a/tests/httpwrappers/tests.py +++ b/tests/httpwrappers/tests.py @@ -345,7 +345,7 @@ def test_iter_content(self): # test odd inputs r = HttpResponse() r.content = ['1', '2', 3, '\u079e'] - #'\xde\x9e' == unichr(1950).encode('utf-8') + # '\xde\x9e' == unichr(1950).encode('utf-8') self.assertEqual(r.content, b'123\xde\x9e') # .content can safely be accessed multiple times. diff --git a/tests/introspection/tests.py b/tests/introspection/tests.py index cc6b33e57275..c6c230b9c549 100644 --- a/tests/introspection/tests.py +++ b/tests/introspection/tests.py @@ -30,7 +30,7 @@ def test_django_table_names(self): "django_table_names() returned a non-Django table") def test_django_table_names_retval_type(self): - #15216 - Table name is a list + # Table name is a list #15216 tl = connection.introspection.django_table_names(only_existing=True) self.assertIs(type(tl), list) tl = connection.introspection.django_table_names(only_existing=False) diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 0f86289cff8a..8f6c5746000f 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -360,7 +360,7 @@ def test_files_content(self): self.assertIn('\\u201c\\xd0j\\xe1\\xf1g\\xf3\\u201d', content) # title.default def test_failing_migration(self): - #21280 - If a migration fails to serialize, it shouldn't generate an empty file. + # If a migration fails to serialize, it shouldn't generate an empty file. #21280 apps.register_model('migrations', UnserializableModel) with six.assertRaisesRegex(self, ValueError, r'Cannot serialize'): diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py index 2c6b707b8853..f8ce329e7bc7 100644 --- a/tests/model_fields/models.py +++ b/tests/model_fields/models.py @@ -166,7 +166,7 @@ class VerboseNameField(models.Model): field10 = models.FilePathField("verbose field10") field11 = models.FloatField("verbose field11") # Don't want to depend on Pillow in this test - #field_image = models.ImageField("verbose field") + # field_image = models.ImageField("verbose field") field12 = models.IntegerField("verbose field12") field13 = models.IPAddressField("verbose field13") field14 = models.GenericIPAddressField("verbose field14", protocol="ipv4") diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index 4bb7c47eb25b..c7acf948a745 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -955,10 +955,10 @@ def test_reuse_prefetched(self): with self.assertNumQueries(0): d = model_to_dict(art) - #Ensure all many-to-many categories appear in model_to_dict + # Ensure all many-to-many categories appear in model_to_dict for c in categories: self.assertIn(c.pk, d['categories']) - #Ensure many-to-many relation appears as a list + # Ensure many-to-many relation appears as a list self.assertIsInstance(d['categories'], list) diff --git a/tests/model_regress/models.py b/tests/model_regress/models.py index c42b0f0d356a..07980690434b 100644 --- a/tests/model_regress/models.py +++ b/tests/model_regress/models.py @@ -29,7 +29,7 @@ def __str__(self): class Movie(models.Model): - #5218: Test models with non-default primary keys / AutoFields + # Test models with non-default primary keys / AutoFields #5218 movie_id = models.AutoField(primary_key=True) name = models.CharField(max_length=60) @@ -73,7 +73,7 @@ class NonAutoPK(models.Model): name = models.CharField(max_length=10, primary_key=True) -#18432: Chained foreign keys with to_field produce incorrect query +# Chained foreign keys with to_field produce incorrect query #18432 class Model1(models.Model): pkey = models.IntegerField(unique=True, db_index=True) diff --git a/tests/prefetch_related/models.py b/tests/prefetch_related/models.py index 40540e9b2610..c4adb00877f9 100644 --- a/tests/prefetch_related/models.py +++ b/tests/prefetch_related/models.py @@ -6,7 +6,7 @@ from django.utils.encoding import python_2_unicode_compatible -## Basic tests +# Basic tests @python_2_unicode_compatible class Author(models.Model): @@ -88,7 +88,7 @@ class BookReview(models.Model): notes = models.TextField(null=True, blank=True) -## Models for default manager tests +# Models for default manager tests class Qualification(models.Model): name = models.CharField(max_length=10) @@ -124,7 +124,7 @@ class Meta: ordering = ['id'] -## GenericRelation/GenericForeignKey tests +# GenericRelation/GenericForeignKey tests @python_2_unicode_compatible class TaggedItem(models.Model): @@ -172,7 +172,7 @@ class Meta: ordering = ['id'] -## Models for lookup ordering tests +# Models for lookup ordering tests class House(models.Model): name = models.CharField(max_length=50) @@ -209,7 +209,7 @@ class Meta: ordering = ['id'] -## Models for nullable FK tests +# Models for nullable FK tests @python_2_unicode_compatible class Employee(models.Model): @@ -224,7 +224,7 @@ class Meta: ordering = ['id'] -## Ticket #19607 +# Ticket #19607 @python_2_unicode_compatible class LessonEntry(models.Model): @@ -244,7 +244,7 @@ def __str__(self): return "%s (%s)" % (self.name, self.id) -## Ticket #21410: Regression when related_name="+" +# Ticket #21410: Regression when related_name="+" @python_2_unicode_compatible class Author2(models.Model): diff --git a/tests/serializers_regress/tests.py b/tests/serializers_regress/tests.py index 2c99ed77b361..0c07b3162c4e 100644 --- a/tests/serializers_regress/tests.py +++ b/tests/serializers_regress/tests.py @@ -241,7 +241,7 @@ def inherited_compare(testcase, pk, klass, data): (data_obj, 81, IntegerData, -123456789), (data_obj, 82, IntegerData, 0), (data_obj, 83, IntegerData, None), - #(XX, ImageData + # (XX, ImageData (data_obj, 90, IPAddressData, "127.0.0.1"), (data_obj, 91, IPAddressData, None), (data_obj, 95, GenericIPAddressData, "fe80:1424:2223:6cff:fe8a:2e8a:2151:abcd"), diff --git a/tests/syndication_tests/tests.py b/tests/syndication_tests/tests.py index 63638fa58905..221d4c112eb5 100644 --- a/tests/syndication_tests/tests.py +++ b/tests/syndication_tests/tests.py @@ -94,7 +94,6 @@ def test_rss2_feed(self): 'link': 'http://example.com/blog/', 'language': 'en', 'lastBuildDate': last_build_date, - #'atom:link': '', 'ttl': '600', 'copyright': 'Copyright (c) 2007, Sally Smith', }) diff --git a/tests/urlpatterns_reverse/no_urls.py b/tests/urlpatterns_reverse/no_urls.py index 1cd34c78f7d7..e69de29bb2d1 100644 --- a/tests/urlpatterns_reverse/no_urls.py +++ b/tests/urlpatterns_reverse/no_urls.py @@ -1 +0,0 @@ -#from django.conf.urls import patterns, url, include From b6323302e03a4bb3cd87ca6a14a951c74ab82046 Mon Sep 17 00:00:00 2001 From: Peter Inglesby Date: Sat, 7 Feb 2015 16:57:37 +0000 Subject: [PATCH 0108/1125] [1.8.x] Fixed docs typo Backport of a8f1c70dce792831688588f271efb3a825dffef8 from master --- docs/internals/contributing/writing-code/submitting-patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/internals/contributing/writing-code/submitting-patches.txt b/docs/internals/contributing/writing-code/submitting-patches.txt index 965ce45eaaf7..e5c01faca2fa 100644 --- a/docs/internals/contributing/writing-code/submitting-patches.txt +++ b/docs/internals/contributing/writing-code/submitting-patches.txt @@ -119,7 +119,7 @@ to publish your work. If you use the Git workflow, then you should announce your branch in the ticket by including a link to your branch. When you think your work is ready to be merged in create a pull request. -See the :doc:`working-with-git` documentation for mode details. +See the :doc:`working-with-git` documentation for more details. You can also use patches in Trac. When using this style, follow these guidelines. From b2b5ea88b7159a89e373a71f215596de4d8b015f Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Sat, 7 Feb 2015 19:22:31 +0100 Subject: [PATCH 0109/1125] [1.8.x] Revert "Refs #24075 -- Silenced needless call_command output while running tests" This reverts commit 51dc617b21e67636d96cf645905797a4d6ff4bf0. Backport of bd3d796ecd9a66832ad26024df65caeb63b60a5d from master --- django/contrib/auth/tests/test_management.py | 4 ++-- django/contrib/contenttypes/tests/tests.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py index 24e43173920b..36b81a8cebf8 100644 --- a/django/contrib/auth/tests/test_management.py +++ b/django/contrib/auth/tests/test_management.py @@ -585,6 +585,6 @@ def test_unmigrating_first_migration_post_migrate_signal(self): INSTALLED_APPS=["django.contrib.auth", "django.contrib.contenttypes"], MIGRATION_MODULES={'auth': 'django.contrib.auth.migrations'}, ): - call_command("migrate", "auth", "zero", verbosity=0) + call_command("migrate", "auth", "zero", stdout=six.StringIO()) finally: - call_command("migrate", verbosity=0) + call_command("migrate", stdout=six.StringIO()) diff --git a/django/contrib/contenttypes/tests/tests.py b/django/contrib/contenttypes/tests/tests.py index f41732b6b5d2..ba0c33900758 100644 --- a/django/contrib/contenttypes/tests/tests.py +++ b/django/contrib/contenttypes/tests/tests.py @@ -255,9 +255,9 @@ def test_unmigrating_first_migration_post_migrate_signal(self): INSTALLED_APPS=["django.contrib.contenttypes"], MIGRATION_MODULES={'contenttypes': 'django.contrib.contenttypes.migrations'}, ): - call_command("migrate", "contenttypes", "zero", verbosity=0) + call_command("migrate", "contenttypes", "zero", stdout=six.StringIO()) finally: - call_command("migrate", verbosity=0) + call_command("migrate", stdout=six.StringIO()) def test_name_deprecation(self): """ From edbf6de7536f7a6c1e5df019a5e1947d2c9dadf8 Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Sat, 7 Feb 2015 19:34:15 +0100 Subject: [PATCH 0110/1125] [1.8.x] Revert "Fixed #24075 -- Prevented running post_migrate signals when unapplying initial migrations of contenttypes and auth" This reverts commit 737d24923ac69bb8b89af1bb2f3f4c4c744349e8. Backport of 2832a9b028c267997b2fd3dd0989670d57cdd08f from master --- django/contrib/auth/management/__init__.py | 5 ----- django/contrib/auth/tests/test_management.py | 20 +------------------- django/contrib/contenttypes/management.py | 5 ----- django/contrib/contenttypes/tests/tests.py | 20 ++------------------ django/db/migrations/loader.py | 16 ++-------------- docs/releases/1.7.5.txt | 5 ++++- 6 files changed, 9 insertions(+), 62 deletions(-) diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py index 973900587533..44d8563c14e1 100644 --- a/django/contrib/auth/management/__init__.py +++ b/django/contrib/auth/management/__init__.py @@ -11,7 +11,6 @@ from django.core import exceptions from django.core.management.base import CommandError from django.db import DEFAULT_DB_ALIAS, router -from django.db.migrations.loader import is_latest_migration_applied from django.utils.encoding import DEFAULT_LOCALE_ENCODING from django.utils import six @@ -59,10 +58,6 @@ def _check_permission_clashing(custom, builtin, ctype): def create_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): - # TODO: Remove when migration plan / state is passed (#24100). - if not is_latest_migration_applied('auth'): - return - if not app_config.models_module: return diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py index 36b81a8cebf8..3d2aaceee984 100644 --- a/django/contrib/auth/tests/test_management.py +++ b/django/contrib/auth/tests/test_management.py @@ -17,7 +17,7 @@ from django.core import exceptions from django.core.management import call_command from django.core.management.base import CommandError -from django.test import TestCase, override_settings, override_system_checks, skipUnlessDBFeature +from django.test import TestCase, override_settings, override_system_checks from django.utils import six from django.utils.encoding import force_str @@ -570,21 +570,3 @@ def test_verbose_name_length(self): six.assertRaisesRegex(self, exceptions.ValidationError, "The verbose_name of auth.permission is longer than 244 characters", create_permissions, auth_app_config, verbosity=0) - - -class MigrateTests(TestCase): - - @skipUnlessDBFeature('can_rollback_ddl') - def test_unmigrating_first_migration_post_migrate_signal(self): - """ - #24075 - When unmigrating an app before its first migration, - post_migrate signal handler must be aware of the missing tables. - """ - try: - with override_settings( - INSTALLED_APPS=["django.contrib.auth", "django.contrib.contenttypes"], - MIGRATION_MODULES={'auth': 'django.contrib.auth.migrations'}, - ): - call_command("migrate", "auth", "zero", stdout=six.StringIO()) - finally: - call_command("migrate", stdout=six.StringIO()) diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py index 7dad8f1fe1bc..6364c752d23f 100644 --- a/django/contrib/contenttypes/management.py +++ b/django/contrib/contenttypes/management.py @@ -1,6 +1,5 @@ from django.apps import apps from django.db import DEFAULT_DB_ALIAS, router -from django.db.migrations.loader import is_latest_migration_applied from django.utils import six from django.utils.six.moves import input @@ -10,10 +9,6 @@ def update_contenttypes(app_config, verbosity=2, interactive=True, using=DEFAULT Creates content types for models in the given app, removing any model entries that no longer have a matching model class. """ - # TODO: Remove when migration plan / state is passed (#24100). - if not is_latest_migration_applied('contenttypes'): - return - if not app_config.models_module: return diff --git a/django/contrib/contenttypes/tests/tests.py b/django/contrib/contenttypes/tests/tests.py index ba0c33900758..5ce9b395f197 100644 --- a/django/contrib/contenttypes/tests/tests.py +++ b/django/contrib/contenttypes/tests/tests.py @@ -5,9 +5,8 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.views import shortcut from django.contrib.sites.shortcuts import get_current_site -from django.core.management import call_command -from django.http import HttpRequest, Http404 -from django.test import TestCase, override_settings, skipUnlessDBFeature +from django.http import Http404, HttpRequest +from django.test import TestCase, override_settings from django.utils import six from .models import ConcreteModel, ProxyModel, FooWithoutUrl, FooWithUrl, FooWithBrokenAbsoluteUrl @@ -244,21 +243,6 @@ def test_missing_model(self): ct_fetched = ContentType.objects.get_for_id(ct.pk) self.assertIsNone(ct_fetched.model_class()) - @skipUnlessDBFeature('can_rollback_ddl') - def test_unmigrating_first_migration_post_migrate_signal(self): - """ - #24075 - When unmigrating an app before its first migration, - post_migrate signal handler must be aware of the missing tables. - """ - try: - with override_settings( - INSTALLED_APPS=["django.contrib.contenttypes"], - MIGRATION_MODULES={'contenttypes': 'django.contrib.contenttypes.migrations'}, - ): - call_command("migrate", "contenttypes", "zero", stdout=six.StringIO()) - finally: - call_command("migrate", stdout=six.StringIO()) - def test_name_deprecation(self): """ ContentType.name has been removed. Test that a warning is emitted when diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py index d7ee805bb1c3..ce6849f2807d 100644 --- a/django/db/migrations/loader.py +++ b/django/db/migrations/loader.py @@ -5,11 +5,10 @@ import sys from django.apps import apps -from django.db import connection -from django.db.migrations.recorder import MigrationRecorder +from django.conf import settings from django.db.migrations.graph import MigrationGraph, NodeNotFoundError +from django.db.migrations.recorder import MigrationRecorder from django.utils import six -from django.conf import settings MIGRATIONS_MODULE_NAME = 'migrations' @@ -340,14 +339,3 @@ class AmbiguityError(Exception): Raised when more than one migration matches a name prefix """ pass - - -def is_latest_migration_applied(app_label): - # TODO: Remove when migration plan / state is passed (#24100). - loader = MigrationLoader(connection) - loader.load_disk() - leaf_nodes = loader.graph.leaf_nodes(app=app_label) - return ( - leaf_nodes and leaf_nodes[0] in loader.applied_migrations or - app_label in loader.unmigrated_apps - ) diff --git a/docs/releases/1.7.5.txt b/docs/releases/1.7.5.txt index ba81a64b9a92..2039c9dc4e88 100644 --- a/docs/releases/1.7.5.txt +++ b/docs/releases/1.7.5.txt @@ -9,4 +9,7 @@ Django 1.7.5 fixes several bugs in 1.7.4. Bugfixes ======== -* ... +* Reverted a fix that prevented a migration crash when unapplying + ``contrib.contenttypes``’s or ``contrib.auth``’s first migration + (:ticket:`24075`) due to severe impact on the test performance + (:ticket:`24251`) and problems in multi-database setups (:ticket:`24298`). From b44a56c3080312ab94679a396e6b60a18f9b6463 Mon Sep 17 00:00:00 2001 From: Varun Sharma Date: Sun, 8 Feb 2015 03:06:45 +0530 Subject: [PATCH 0111/1125] [1.8.x] Fixed #24181 -- Fixed multi-char THOUSAND_SEPARATOR insertion Report and original patch by Kay Cha. Backport of 540ca563de from master. --- django/utils/numberformat.py | 2 +- tests/utils_tests/test_numberformat.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py index c3cf55bca5fd..a70799852de5 100644 --- a/django/utils/numberformat.py +++ b/django/utils/numberformat.py @@ -47,7 +47,7 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', int_part_gd = '' for cnt, digit in enumerate(int_part[::-1]): if cnt and not cnt % grouping: - int_part_gd += thousand_sep + int_part_gd += thousand_sep[::-1] int_part_gd += digit int_part = int_part_gd[::-1] return sign + int_part + dec_part diff --git a/tests/utils_tests/test_numberformat.py b/tests/utils_tests/test_numberformat.py index 84ba378e46fa..43579060bca4 100644 --- a/tests/utils_tests/test_numberformat.py +++ b/tests/utils_tests/test_numberformat.py @@ -26,6 +26,9 @@ def test_format_string(self): self.assertEqual(nformat('1234', '.', grouping=2, thousand_sep=',', force_grouping=True), '12,34') self.assertEqual(nformat('-1234.33', '.', decimal_pos=1), '-1234.3') + self.assertEqual(nformat('10000', '.', grouping=3, + thousand_sep='comma', force_grouping=True), + '10comma000') def test_large_number(self): most_max = ('{}179769313486231570814527423731704356798070567525844996' From ee86bf24d269869012d5538c22d37588cec68685 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 8 Feb 2015 12:14:30 -0600 Subject: [PATCH 0112/1125] [1.8.x] Optimized allow_lazy() by not generating a new lazy wrapper on each invocation. This dramatically improves performance on PyPy. The following benchmark: python -mtimeit -s "from django.utils.functional import allow_lazy; from django.utils.translation import ugettext_lazy; f = allow_lazy(lambda s: s, str)" "f(ugettext_lazy('abc'))" goes from 390us per loop to 165us. Backport of 82e0cd15711c7171aed7af5e481967cc721c9642 from master --- django/utils/functional.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/django/utils/functional.py b/django/utils/functional.py index 37cc7c7f0f29..628e02e8d859 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -205,6 +205,8 @@ def allow_lazy(func, *resultclasses): immediately, otherwise a __proxy__ is returned that will evaluate the function when needed. """ + lazy_func = lazy(func, *resultclasses) + @wraps(func) def wrapper(*args, **kwargs): for arg in list(args) + list(six.itervalues(kwargs)): @@ -212,7 +214,7 @@ def wrapper(*args, **kwargs): break else: return func(*args, **kwargs) - return lazy(func, *resultclasses)(*args, **kwargs) + return lazy_func(*args, **kwargs) return wrapper empty = object() From d54638727ae23e8dc1e1ed0823efbd15806d9778 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 8 Feb 2015 12:24:11 -0600 Subject: [PATCH 0113/1125] [1.8.x] Simplified the lazy CSRF token implementation in csrf context processor. This significantly improves performance on PyPy. The previous implementation would generate a new class on every single request, which is relatively slow. Backport of 8099d33b6553c9ee7de779ae9d191a1bf22adbda from master --- django/template/context_processors.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/django/template/context_processors.py b/django/template/context_processors.py index dcd737f4ae6a..a81fe71829d2 100644 --- a/django/template/context_processors.py +++ b/django/template/context_processors.py @@ -11,9 +11,8 @@ from django.conf import settings from django.middleware.csrf import get_token -from django.utils import six from django.utils.encoding import smart_text -from django.utils.functional import lazy +from django.utils.functional import SimpleLazyObject, lazy def csrf(request): @@ -30,9 +29,8 @@ def _get_val(): return 'NOTPROVIDED' else: return smart_text(token) - _get_val = lazy(_get_val, six.text_type) - return {'csrf_token': _get_val()} + return {'csrf_token': SimpleLazyObject(_get_val)} def debug(request): From cd260d03bd7c43b78ad3a394e1b62f526f40f4ce Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 6 Feb 2015 23:25:15 +0100 Subject: [PATCH 0114/1125] [1.8.x] Replaced hardcoded URLs in admin_* tests Refs #15779. This will allow easier admin URL changes, when needed. Thanks Simon Charette for the review. Backport of 32e6a7d3a57b2287d55e8b8efa4e8cb7643b1720 from master --- django/contrib/auth/admin.py | 2 +- tests/admin_changelist/tests.py | 4 +- tests/admin_custom_urls/tests.py | 8 +- tests/admin_inlines/tests.py | 115 +-- tests/admin_views/customadmin.py | 2 +- tests/admin_views/tests.py | 1195 +++++++++++++++--------------- tests/admin_views/urls.py | 4 +- tests/admin_widgets/tests.py | 35 +- 8 files changed, 707 insertions(+), 658 deletions(-) diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py index 90394c8f0930..2df33c3c86dc 100644 --- a/django/contrib/auth/admin.py +++ b/django/contrib/auth/admin.py @@ -79,7 +79,7 @@ def get_form(self, request, obj=None, **kwargs): def get_urls(self): return [ - url(r'^(\d+)/password/$', self.admin_site.admin_view(self.user_change_password)), + url(r'^(\d+)/password/$', self.admin_site.admin_view(self.user_change_password), name='auth_user_password_change'), ] + super(UserAdmin, self).get_urls() def lookup_allowed(self, lookup, value): diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index 5ee5a512dba3..7b35c84b45c7 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -365,7 +365,7 @@ def test_computed_list_display_localization(self): username='super', email='super@localhost', password='secret') self.client.login(username='super', password='secret') event = Event.objects.create(date=datetime.date.today()) - response = self.client.get('/admin/admin_changelist/event/') + response = self.client.get(reverse('admin:admin_changelist_event_changelist')) self.assertContains(response, formats.localize(event.date)) self.assertNotContains(response, six.text_type(event.date)) @@ -678,7 +678,7 @@ def test_add_row_selection(self): """ self.admin_login(username='super', password='secret') self.selenium.get('%s%s' % (self.live_server_url, - '/admin/auth/user/')) + reverse('admin:auth_user_changelist'))) form_id = '#changelist-form' diff --git a/tests/admin_custom_urls/tests.py b/tests/admin_custom_urls/tests.py index ea3c0704b3c6..63cebed70841 100644 --- a/tests/admin_custom_urls/tests.py +++ b/tests/admin_custom_urls/tests.py @@ -26,7 +26,9 @@ def test_basic_add_GET(self): """ Ensure GET on the add_view works. """ - response = self.client.get('/admin/admin_custom_urls/action/!add/') + add_url = reverse('admin:admin_custom_urls_action_add') + self.assertTrue(add_url.endswith('/!add/')) + response = self.client.get(add_url) self.assertIsInstance(response, TemplateResponse) self.assertEqual(response.status_code, 200) @@ -35,7 +37,7 @@ def test_add_with_GET_args(self): Ensure GET on the add_view plus specifying a field value in the query string works. """ - response = self.client.get('/admin/admin_custom_urls/action/!add/', {'name': 'My Action'}) + response = self.client.get(reverse('admin:admin_custom_urls_action_add'), {'name': 'My Action'}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'value="My Action"') @@ -48,7 +50,7 @@ def test_basic_add_POST(self): "name": 'Action added through a popup', "description": "Description of added action", } - response = self.client.post('/admin/admin_custom_urls/action/!add/', post_data) + response = self.client.post(reverse('admin:admin_custom_urls_action_add'), post_data) self.assertEqual(response.status_code, 200) self.assertContains(response, 'dismissAddRelatedObjectPopup') self.assertContains(response, 'Action added through a popup') diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index ed931a0cc136..d5c1728bbb21 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -2,21 +2,25 @@ import warnings -from django.contrib.admin import TabularInline, ModelAdmin -from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase +from django.contrib.admin import ModelAdmin, TabularInline from django.contrib.admin.helpers import InlineAdminForm -from django.contrib.auth.models import User, Permission +from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase +from django.contrib.auth.models import Permission, User from django.contrib.contenttypes.models import ContentType -from django.test import TestCase, override_settings, RequestFactory +from django.core.urlresolvers import reverse +from django.test import RequestFactory, TestCase, override_settings from django.utils.encoding import force_text -# local test models -from .admin import InnerInline, site as admin_site -from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person, - OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile, - ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2, - Sighting, Novel, Chapter, FootNote, BinaryTree, SomeParentModel, - SomeChildModel, Poll, Question, Inner4Stacked, Inner4Tabular, Holder4) +from .admin import site as admin_site +from .admin import InnerInline +from .models import ( + Author, BinaryTree, Book, Chapter, Child, ChildModel1, ChildModel2, + Fashionista, FootNote, Holder, Holder2, Holder3, Holder4, Inner, Inner2, + Inner3, Inner4Stacked, Inner4Tabular, Novel, OutfitItem, Parent, + ParentModelWithCustomPk, Person, Poll, Profile, + ProfileCollection, Question, Sighting, SomeChildModel, + SomeParentModel, Teacher, +) INLINE_CHANGELINK_HTML = 'class="inlinechangelink">Change' @@ -30,7 +34,6 @@ def setUp(self): holder = Holder(dummy=13) holder.save() Inner(dummy=42, holder=holder).save() - self.change_url = '/admin/admin_inlines/holder/%i/' % holder.id result = self.client.login(username='super', password='secret') self.assertEqual(result, True) @@ -40,7 +43,10 @@ def test_can_delete(self): """ can_delete should be passed to inlineformset factory. """ - response = self.client.get(self.change_url) + holder = Holder.objects.get(dummy=13) + response = self.client.get( + reverse('admin:admin_inlines_holder_change', args=(holder.id,)) + ) inner_formset = response.context['inline_admin_formsets'][0].formset expected = InnerInline.can_delete actual = inner_formset.can_delete @@ -50,13 +56,14 @@ def test_readonly_stacked_inline_label(self): """Bug #13174.""" holder = Holder.objects.create(dummy=42) Inner.objects.create(holder=holder, dummy=42, readonly='') - response = self.client.get('/admin/admin_inlines/holder/%i/' - % holder.id) + response = self.client.get( + reverse('admin:admin_inlines_holder_change', args=(holder.id,)) + ) self.assertContains(response, '') def test_many_to_many_inlines(self): "Autogenerated many-to-many inlines are displayed correctly (#13407)" - response = self.client.get('/admin/admin_inlines/author/add/') + response = self.client.get(reverse('admin:admin_inlines_author_add')) # The heading for the m2m inline block uses the right text self.assertContains(response, '

Author-book relationships

') # The "add another" label is correct @@ -77,7 +84,7 @@ def test_inline_primary(self): 'max_weight': 0, 'shoppingweakness_set-0-item': item.id, } - response = self.client.post('/admin/admin_inlines/fashionista/add/', data) + response = self.client.post(reverse('admin:admin_inlines_fashionista_add'), data) self.assertEqual(response.status_code, 302) self.assertEqual(len(Fashionista.objects.filter(person__firstname='Imelda')), 1) @@ -94,7 +101,7 @@ def test_tabular_non_field_errors(self): 'title_set-0-title1': 'a title', 'title_set-0-title2': 'a different title', } - response = self.client.post('/admin/admin_inlines/titlecollection/add/', data) + response = self.client.post(reverse('admin:admin_inlines_titlecollection_add'), data) # Here colspan is "4": two fields (title1 and title2), one hidden field and the delete checkbox. self.assertContains(response, '
  • The two titles must be the same
') @@ -102,14 +109,14 @@ def test_no_parent_callable_lookup(self): """Admin inline `readonly_field` shouldn't invoke parent ModelAdmin callable""" # Identically named callable isn't present in the parent ModelAdmin, # rendering of the add view shouldn't explode - response = self.client.get('/admin/admin_inlines/novel/add/') + response = self.client.get(reverse('admin:admin_inlines_novel_add')) self.assertEqual(response.status_code, 200) # View should have the child inlines section self.assertContains(response, '
') def test_callable_lookup(self): """Admin inline should invoke local callable when its name is listed in readonly_fields""" - response = self.client.get('/admin/admin_inlines/poll/add/') + response = self.client.get(reverse('admin:admin_inlines_poll_add')) self.assertEqual(response.status_code, 200) # Add parent object view should have the child inlines section self.assertContains(response, '
') @@ -123,11 +130,11 @@ def test_help_text(self): using both the stacked and tabular layouts. Ref #8190. """ - response = self.client.get('/admin/admin_inlines/holder4/add/') + response = self.client.get(reverse('admin:admin_inlines_holder4_add')) self.assertContains(response, '

Awesome stacked help text is awesome.

', 4) self.assertContains(response, '(Awesome tabular help text is awesome.)', 1) # ReadOnly fields - response = self.client.get('/admin/admin_inlines/capofamiglia/add/') + response = self.client.get(reverse('admin:admin_inlines_capofamiglia_add')) self.assertContains(response, '(Help text for ReadOnlyInline)', 1) def test_inline_hidden_field_no_column(self): @@ -135,7 +142,7 @@ def test_inline_hidden_field_no_column(self): parent = SomeParentModel.objects.create(name='a') SomeChildModel.objects.create(name='b', position='0', parent=parent) SomeChildModel.objects.create(name='c', position='1', parent=parent) - response = self.client.get('/admin/admin_inlines/someparentmodel/%s/' % parent.pk) + response = self.client.get(reverse('admin:admin_inlines_someparentmodel_change', args=(parent.pk,))) self.assertNotContains(response, '') self.assertContains(response, ( '', html=True) @@ -172,7 +179,7 @@ def test_localize_pk_shortcut(self): """ holder = Holder.objects.create(pk=123456789, dummy=42) inner = Inner.objects.create(pk=987654321, holder=holder, dummy=42, readonly='') - response = self.client.get('/admin/admin_inlines/holder/%i/' % holder.id) + response = self.client.get(reverse('admin:admin_inlines_holder_change', args=(holder.id,))) inner_shortcut = 'r/%s/%s/' % (ContentType.objects.get_for_model(inner).pk, inner.pk) self.assertContains(response, inner_shortcut) @@ -184,7 +191,7 @@ def test_custom_pk_shortcut(self): parent = ParentModelWithCustomPk.objects.create(my_own_pk="foo", name="Foo") child1 = ChildModel1.objects.create(my_own_pk="bar", name="Bar", parent=parent) child2 = ChildModel2.objects.create(my_own_pk="baz", name="Baz", parent=parent) - response = self.client.get('/admin/admin_inlines/parentmodelwithcustompk/foo/') + response = self.client.get(reverse('admin:admin_inlines_parentmodelwithcustompk_change', args=('foo',))) child1_shortcut = 'r/%s/%s/' % (ContentType.objects.get_for_model(child1).pk, child1.pk) child2_shortcut = 'r/%s/%s/' % (ContentType.objects.get_for_model(child2).pk, child2.pk) self.assertContains(response, child1_shortcut) @@ -203,7 +210,7 @@ def test_create_inlines_on_inherited_model(self): 'sighting_set-0-place': 'Zone 51', '_save': 'Save', } - response = self.client.post('/admin/admin_inlines/extraterrestrial/add/', data) + response = self.client.post(reverse('admin:admin_inlines_extraterrestrial_add'), data) self.assertEqual(response.status_code, 302) self.assertEqual(Sighting.objects.filter(et__name='Martian').count(), 1) @@ -217,11 +224,11 @@ def test_custom_get_extra_form(self): # The total number of forms will remain the same in either case total_forms_hidden = '' - response = self.client.get('/admin/admin_inlines/binarytree/add/') + response = self.client.get(reverse('admin:admin_inlines_binarytree_add')) self.assertContains(response, max_forms_input % 3) self.assertContains(response, total_forms_hidden) - response = self.client.get("/admin/admin_inlines/binarytree/%d/" % bt_head.id) + response = self.client.get(reverse('admin:admin_inlines_binarytree_change', args=(bt_head.id,))) self.assertContains(response, max_forms_input % 2) self.assertContains(response, total_forms_hidden) @@ -240,7 +247,7 @@ class MinNumInline(TabularInline): min_forms = '' total_forms = '' - request = self.factory.get('/admin/admin_inlines/binarytree/add/') + request = self.factory.get(reverse('admin:admin_inlines_binarytree_add')) request.user = User(username='super', is_superuser=True) response = modeladmin.changeform_view(request) self.assertContains(response, min_forms) @@ -268,20 +275,20 @@ def get_min_num(self, request, obj=None, **kwargs): min_forms = '' total_forms = '' - request = self.factory.get('/admin/admin_inlines/binarytree/add/') + request = self.factory.get(reverse('admin:admin_inlines_binarytree_add')) request.user = User(username='super', is_superuser=True) response = modeladmin.changeform_view(request) self.assertContains(response, min_forms % 2) self.assertContains(response, total_forms % 5) - request = self.factory.get("/admin/admin_inlines/binarytree/%d/" % bt_head.id) + request = self.factory.get(reverse('admin:admin_inlines_binarytree_change', args=(bt_head.id,))) request.user = User(username='super', is_superuser=True) response = modeladmin.changeform_view(request, object_id=str(bt_head.id)) self.assertContains(response, min_forms % 5) self.assertContains(response, total_forms % 8) def test_inline_nonauto_noneditable_pk(self): - response = self.client.get('/admin/admin_inlines/author/add/') + response = self.client.get(reverse('admin:admin_inlines_author_add')) self.assertContains(response, '', html=True) @@ -290,7 +297,7 @@ def test_inline_nonauto_noneditable_pk(self): html=True) def test_inline_editable_pk(self): - response = self.client.get('/admin/admin_inlines/author/add/') + response = self.client.get(reverse('admin:admin_inlines_author_add')) self.assertContains(response, '', html=True, count=1) @@ -301,7 +308,7 @@ def test_inline_editable_pk(self): def test_stacked_inline_edit_form_contains_has_original_class(self): holder = Holder.objects.create(dummy=1) holder.inner_set.create(dummy=1) - response = self.client.get('/admin/admin_inlines/holder/%s/' % holder.pk) + response = self.client.get(reverse('admin:admin_inlines_holder_change', args=(holder.pk,))) self.assertContains( response, '