summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2013-07-22 19:04:25 +0100
committerAndrew Godwin <andrew@aeracode.org>2013-07-22 19:04:25 +0100
commit03ec3219a04093da6c2e82436cee5c451df4f8aa (patch)
tree69e3cc2857b7bc41f269363b0056b361129da118
parent3a6580e485c11fc5502ffd1bb53347459db43421 (diff)
parent29a09b3638e625458b5e07ed0956451432421acf (diff)
Merge branch 'master' into schema-alteration
Conflicts: django/db/backends/mysql/introspection.py django/db/backends/oracle/creation.py django/db/backends/postgresql_psycopg2/creation.py django/db/models/base.py django/db/models/loading.py
-rw-r--r--AUTHORS3
-rw-r--r--django/conf/global_settings.py1
-rw-r--r--django/conf/locale/__init__.py8
-rw-r--r--django/conf/locale/en/LC_MESSAGES/django.po15
-rw-r--r--django/contrib/admin/helpers.py8
-rw-r--r--django/contrib/admin/options.py6
-rw-r--r--django/contrib/admin/sites.py1
-rw-r--r--django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js86
-rw-r--r--django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js8
-rw-r--r--django/contrib/admin/templates/admin/app_index.html2
-rw-r--r--django/contrib/admin/templates/admin/base.html1
-rw-r--r--django/contrib/admin/templates/admin/change_form.html2
-rw-r--r--django/contrib/admin/templates/admin/change_list.html2
-rw-r--r--django/contrib/admin/templates/admin/delete_confirmation.html2
-rw-r--r--django/contrib/admin/templates/admin/delete_selected_confirmation.html2
-rw-r--r--django/contrib/admin/templates/admin/edit_inline/stacked.html2
-rw-r--r--django/contrib/admin/templates/admin/edit_inline/tabular.html2
-rw-r--r--django/contrib/admin/templatetags/admin_urls.py2
-rw-r--r--django/contrib/auth/admin.py5
-rw-r--r--django/contrib/auth/backends.py2
-rw-r--r--django/contrib/auth/hashers.py5
-rw-r--r--django/contrib/auth/tests/test_hashers.py7
-rw-r--r--django/contrib/contenttypes/generic.py4
-rw-r--r--django/contrib/gis/db/backends/oracle/operations.py2
-rw-r--r--django/contrib/gis/db/backends/postgis/operations.py4
-rw-r--r--django/contrib/gis/db/backends/spatialite/operations.py3
-rw-r--r--django/contrib/gis/db/models/query.py2
-rw-r--r--django/contrib/gis/gdal/geometries.py8
-rw-r--r--django/contrib/gis/management/commands/ogrinspect.py2
-rw-r--r--django/contrib/gis/maps/google/gmap.py3
-rw-r--r--django/contrib/gis/tests/__init__.py30
-rw-r--r--django/contrib/gis/tests/geoadmin/tests.py21
-rw-r--r--django/contrib/gis/tests/geoapp/test_sitemaps.py8
-rw-r--r--django/contrib/gis/tests/geoapp/tests.py15
-rw-r--r--django/contrib/gis/tests/test_spatialrefsys.py2
-rw-r--r--django/contrib/gis/utils/ogrinspect.py5
-rw-r--r--django/contrib/syndication/views.py12
-rw-r--r--django/core/cache/backends/locmem.py51
-rw-r--r--django/core/cache/backends/memcached.py2
-rw-r--r--django/core/files/uploadedfile.py13
-rw-r--r--django/core/files/uploadhandler.py9
-rw-r--r--django/core/mail/backends/console.py4
-rw-r--r--django/core/mail/backends/dummy.py2
-rw-r--r--django/core/mail/backends/locmem.py4
-rw-r--r--django/core/mail/backends/smtp.py38
-rw-r--r--django/core/mail/message.py4
-rw-r--r--django/core/signing.py4
-rw-r--r--django/core/validators.py2
-rw-r--r--django/db/__init__.py29
-rw-r--r--django/db/backends/__init__.py5
-rw-r--r--django/db/backends/creation.py7
-rw-r--r--django/db/backends/dummy/base.py13
-rw-r--r--django/db/backends/mysql/base.py11
-rw-r--r--django/db/backends/mysql/client.py2
-rw-r--r--django/db/backends/mysql/compiler.py6
-rw-r--r--django/db/backends/mysql/creation.py37
-rw-r--r--django/db/backends/mysql/introspection.py1
-rw-r--r--django/db/backends/mysql/validation.py1
-rw-r--r--django/db/backends/oracle/base.py22
-rw-r--r--django/db/backends/oracle/client.py2
-rw-r--r--django/db/backends/oracle/compiler.py5
-rw-r--r--django/db/backends/oracle/creation.py52
-rw-r--r--django/db/backends/oracle/introspection.py15
-rw-r--r--django/db/backends/postgresql_psycopg2/base.py10
-rw-r--r--django/db/backends/postgresql_psycopg2/client.py2
-rw-r--r--django/db/backends/postgresql_psycopg2/creation.py38
-rw-r--r--django/db/backends/postgresql_psycopg2/introspection.py6
-rw-r--r--django/db/backends/postgresql_psycopg2/operations.py6
-rw-r--r--django/db/backends/postgresql_psycopg2/version.py3
-rw-r--r--django/db/backends/sqlite3/base.py17
-rw-r--r--django/db/backends/sqlite3/client.py2
-rw-r--r--django/db/backends/sqlite3/creation.py49
-rw-r--r--django/db/backends/sqlite3/introspection.py13
-rw-r--r--django/db/backends/util.py19
-rw-r--r--django/db/models/__init__.py1
-rw-r--r--django/db/models/aggregates.py9
-rw-r--r--django/db/models/base.py26
-rw-r--r--django/db/models/expressions.py3
-rw-r--r--django/db/models/fields/__init__.py57
-rw-r--r--django/db/models/fields/files.py7
-rw-r--r--django/db/models/fields/proxy.py1
-rw-r--r--django/db/models/fields/related.py19
-rw-r--r--django/db/models/fields/subclassing.py3
-rw-r--r--django/db/models/loading.py8
-rw-r--r--django/db/models/manager.py6
-rw-r--r--django/db/models/query.py126
-rw-r--r--django/db/models/query_utils.py6
-rw-r--r--django/db/models/related.py1
-rw-r--r--django/db/models/sql/aggregates.py7
-rw-r--r--django/db/models/sql/compiler.py17
-rw-r--r--django/db/models/sql/datastructures.py7
-rw-r--r--django/db/models/sql/expressions.py4
-rw-r--r--django/db/models/sql/query.py14
-rw-r--r--django/db/models/sql/subqueries.py11
-rw-r--r--django/db/models/sql/where.py13
-rw-r--r--django/db/transaction.py26
-rw-r--r--django/forms/fields.py12
-rw-r--r--django/forms/forms.py9
-rw-r--r--django/forms/models.py2
-rw-r--r--django/forms/util.py7
-rw-r--r--django/http/multipartparser.py12
-rw-r--r--django/template/context.py23
-rw-r--r--django/template/defaulttags.py136
-rw-r--r--django/template/loader.py5
-rw-r--r--django/template/loader_tags.py38
-rw-r--r--django/test/client.py6
-rw-r--r--django/test/runner.py8
-rw-r--r--django/test/signals.py13
-rw-r--r--django/test/testcases.py118
-rw-r--r--django/test/utils.py4
-rw-r--r--django/utils/feedgenerator.py34
-rw-r--r--django/utils/functional.py5
-rw-r--r--django/utils/image.py4
-rw-r--r--django/utils/itercompat.py14
-rw-r--r--django/utils/timesince.py3
-rw-r--r--docs/faq/admin.txt2
-rw-r--r--docs/howto/custom-model-fields.txt31
-rw-r--r--docs/howto/deployment/checklist.txt6
-rw-r--r--docs/howto/deployment/wsgi/uwsgi.txt2
-rw-r--r--docs/howto/jython.txt9
-rw-r--r--docs/howto/upgrade-version.txt8
-rw-r--r--docs/internals/contributing/triaging-tickets.txt3
-rw-r--r--docs/internals/contributing/writing-code/coding-style.txt5
-rw-r--r--docs/internals/contributing/writing-code/unit-tests.txt23
-rw-r--r--docs/internals/security.txt12
-rw-r--r--docs/intro/overview.txt2
-rw-r--r--docs/intro/reusable-apps.txt4
-rw-r--r--docs/intro/tutorial01.txt4
-rw-r--r--docs/intro/tutorial03.txt2
-rw-r--r--docs/ref/class-based-views/generic-display.txt2
-rw-r--r--docs/ref/contrib/admin/actions.txt1
-rw-r--r--docs/ref/contrib/admin/index.txt26
-rw-r--r--docs/ref/contrib/contenttypes.txt1
-rw-r--r--docs/ref/contrib/gis/commands.txt4
-rw-r--r--docs/ref/contrib/gis/gdal.txt2
-rw-r--r--docs/ref/contrib/gis/install/geolibs.txt20
-rw-r--r--docs/ref/contrib/gis/install/index.txt9
-rw-r--r--docs/ref/contrib/gis/install/spatialite.txt50
-rw-r--r--docs/ref/contrib/gis/layermapping.txt2
-rw-r--r--docs/ref/contrib/gis/tutorial.txt1
-rw-r--r--docs/ref/contrib/syndication.txt26
-rw-r--r--docs/ref/databases.txt12
-rw-r--r--docs/ref/django-admin.txt8
-rw-r--r--docs/ref/forms/api.txt20
-rw-r--r--docs/ref/forms/fields.txt12
-rw-r--r--docs/ref/forms/models.txt4
-rw-r--r--docs/ref/forms/widgets.txt9
-rw-r--r--docs/ref/models/fields.txt8
-rw-r--r--docs/ref/models/instances.txt6
-rw-r--r--docs/ref/models/options.txt11
-rw-r--r--docs/ref/models/querysets.txt91
-rw-r--r--docs/ref/models/relations.txt16
-rw-r--r--docs/ref/request-response.txt11
-rw-r--r--docs/ref/settings.txt22
-rw-r--r--docs/ref/signals.txt8
-rw-r--r--docs/ref/templates/api.txt44
-rw-r--r--docs/ref/unicode.txt7
-rw-r--r--docs/ref/utils.txt23
-rw-r--r--docs/releases/1.3-beta-1.txt2
-rw-r--r--docs/releases/1.3.txt9
-rw-r--r--docs/releases/1.6.txt16
-rw-r--r--docs/releases/1.7.txt63
-rw-r--r--docs/releases/index.txt7
-rw-r--r--docs/topics/auth/customizing.txt8
-rw-r--r--docs/topics/auth/default.txt5
-rw-r--r--docs/topics/class-based-views/generic-display.txt2
-rw-r--r--docs/topics/db/examples/many_to_many.txt2
-rw-r--r--docs/topics/db/examples/many_to_one.txt8
-rw-r--r--docs/topics/db/examples/one_to_one.txt3
-rw-r--r--docs/topics/db/models.txt7
-rw-r--r--docs/topics/db/queries.txt7
-rw-r--r--docs/topics/email.txt7
-rw-r--r--docs/topics/forms/formsets.txt13
-rw-r--r--docs/topics/forms/index.txt4
-rw-r--r--docs/topics/forms/media.txt178
-rw-r--r--docs/topics/forms/modelforms.txt153
-rw-r--r--docs/topics/http/file-uploads.txt42
-rw-r--r--docs/topics/i18n/translation.txt29
-rw-r--r--docs/topics/localflavor.txt2
-rw-r--r--docs/topics/signing.txt26
-rw-r--r--docs/topics/testing/advanced.txt11
-rw-r--r--docs/topics/testing/overview.txt30
-rw-r--r--scripts/manage_translations.py14
-rw-r--r--tests/admin_inlines/admin.py20
-rw-r--r--tests/admin_inlines/models.py20
-rw-r--r--tests/admin_inlines/tests.py18
-rw-r--r--tests/admin_views/admin.py5
-rw-r--r--tests/admin_views/tests.py74
-rw-r--r--tests/admin_views/urls.py1
-rw-r--r--tests/admin_widgets/tests.py60
-rw-r--r--tests/backends/tests.py33
-rw-r--r--tests/basic/tests.py24
-rw-r--r--tests/db_backends/__init__.py0
-rw-r--r--tests/db_backends/tests.py36
-rw-r--r--tests/file_uploads/tests.py21
-rw-r--r--tests/file_uploads/urls.py1
-rw-r--r--tests/file_uploads/views.py13
-rw-r--r--tests/fixtures_model_package/tests.py12
-rw-r--r--tests/fixtures_regress/tests.py25
-rw-r--r--tests/forms_tests/tests/test_extra.py2
-rw-r--r--tests/forms_tests/tests/test_fields.py12
-rw-r--r--tests/forms_tests/views.py1
-rw-r--r--tests/get_or_create/tests.py65
-rw-r--r--tests/logging_tests/tests.py2
-rw-r--r--tests/mail/tests.py55
-rw-r--r--tests/model_fields/tests.py13
-rw-r--r--tests/model_formsets/tests.py2
-rw-r--r--tests/model_package/models/article.py3
-rw-r--r--tests/model_package/models/publication.py3
-rw-r--r--tests/model_package/tests.py3
-rw-r--r--tests/queries/tests.py7
-rw-r--r--tests/requests/tests.py419
-rw-r--r--tests/requirements/base.txt6
-rw-r--r--tests/requirements/mysql.txt1
-rw-r--r--tests/requirements/oracle.txt1
-rw-r--r--tests/requirements/postgres.txt1
-rw-r--r--tests/requirements/py2.txt2
-rw-r--r--tests/requirements/py3.txt2
-rwxr-xr-xtests/runtests.py23
-rw-r--r--tests/select_for_update/tests.py14
-rw-r--r--tests/settings_tests/tests.py23
-rw-r--r--tests/syndication/feeds.py20
-rw-r--r--tests/syndication/fixtures/feeddata.json21
-rw-r--r--tests/syndication/models.py6
-rw-r--r--tests/syndication/tests.py78
-rw-r--r--tests/syndication/urls.py1
-rw-r--r--tests/template_tests/test_context.py9
-rw-r--r--tests/test_runner/tests.py61
-rw-r--r--tests/test_utils/tests.py28
-rw-r--r--tests/transactions_regress/tests.py7
-rw-r--r--tests/validators/tests.py1
231 files changed, 2724 insertions, 1372 deletions
diff --git a/AUTHORS b/AUTHORS
index ba1f4036e9..a7f12d6e48 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -204,6 +204,7 @@ answer newbie questions, and generally made Django that much better:
Clint Ecker
Nick Efford <nick@efford.org>
Marc Egli <frog32@me.com>
+ Matt Deacalion Stevens <matt@dirtymonkey.co.uk>
eibaan@gmail.com
David Eklund
Julia Elman
@@ -530,6 +531,7 @@ answer newbie questions, and generally made Django that much better:
Leo Shklovskii
jason.sidabras@gmail.com
Mikołaj Siedlarek <mikolaj.siedlarek@gmail.com>
+ Karol Sikora <elektrrrus@gmail.com>
Brenton Simpson <http://theillustratedlife.com>
Jozko Skrablin <jozko.skrablin@gmail.com>
Ben Slavin <benjamin.slavin@gmail.com>
@@ -542,6 +544,7 @@ answer newbie questions, and generally made Django that much better:
George Song <george@damacy.net>
sopel
Leo Soto <leo.soto@gmail.com>
+ Thomas Sorrel
Wiliam Alves de Souza <wiliamsouza83@gmail.com>
Don Spaulding <donspauldingii@gmail.com>
Calvin Spealman <ironfroggy@gmail.com>
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 7882b21b9b..364aa10320 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -184,6 +184,7 @@ EMAIL_PORT = 25
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False
+EMAIL_USE_SSL = False
# List of strings representing installed apps.
INSTALLED_APPS = ()
diff --git a/django/conf/locale/__init__.py b/django/conf/locale/__init__.py
index df6ab07663..59927b2397 100644
--- a/django/conf/locale/__init__.py
+++ b/django/conf/locale/__init__.py
@@ -279,7 +279,7 @@ LANG_INFO = {
'bidi': False,
'code': 'lt',
'name': 'Lithuanian',
- 'name_local': 'Lithuanian',
+ 'name_local': 'Lietuviškai',
},
'lv': {
'bidi': False,
@@ -399,7 +399,7 @@ LANG_INFO = {
'bidi': False,
'code': 'sq',
'name': 'Albanian',
- 'name_local': 'Albanian',
+ 'name_local': 'shqip',
},
'sr': {
'bidi': False,
@@ -441,7 +441,7 @@ LANG_INFO = {
'bidi': False,
'code': 'th',
'name': 'Thai',
- 'name_local': 'Thai',
+ 'name_local': 'ภาษาไทย',
},
'tr': {
'bidi': False,
@@ -477,7 +477,7 @@ LANG_INFO = {
'bidi': False,
'code': 'vi',
'name': 'Vietnamese',
- 'name_local': 'Vietnamese',
+ 'name_local': 'Tiếng Việt',
},
'zh-cn': {
'bidi': False,
diff --git a/django/conf/locale/en/LC_MESSAGES/django.po b/django/conf/locale/en/LC_MESSAGES/django.po
index 371f0af2ab..f8069f07d2 100644
--- a/django/conf/locale/en/LC_MESSAGES/django.po
+++ b/django/conf/locale/en/LC_MESSAGES/django.po
@@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-05-25 14:27+0200\n"
+"POT-Creation-Date: 2013-06-11 18:44+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@@ -699,11 +699,22 @@ msgstr ""
msgid "Enter a list of values."
msgstr ""
-#: forms/forms.py:158
+#. Translators: This is the default suffix added to form field labels
+#: forms/forms.py:90
+msgid ":"
+msgstr ""
+
+#: forms/forms.py:159
#, 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:525
+msgid ":?.!"
+msgstr ""
+
#: forms/formsets.py:310
#, python-format
msgid "Please submit %d or fewer forms."
diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py
index 320d3267a7..3ffb85e6c6 100644
--- a/django/contrib/admin/helpers.py
+++ b/django/contrib/admin/helpers.py
@@ -266,10 +266,12 @@ class InlineAdminForm(AdminForm):
yield InlineFieldset(self.formset, self.form, name,
self.readonly_fields, model_admin=self.model_admin, **options)
- def has_auto_field(self):
- if self.form._meta.model._meta.has_auto_field:
+ def needs_explicit_pk_field(self):
+ # Auto fields are editable (oddly), so need to check for auto or non-editable pk
+ if self.form._meta.model._meta.has_auto_field or not self.form._meta.model._meta.pk.editable:
return True
- # Also search any parents for an auto field.
+ # Also search any parents for an auto field. (The pk info is propagated to child
+ # models so that does not need to be checked in parents.)
for parent in self.form._meta.model._meta.get_parent_list():
if parent._meta.has_auto_field:
return True
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 9c92f7ae9c..afc7cfc5bd 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -771,12 +771,10 @@ class ModelAdmin(BaseModelAdmin):
Returns the preserved filters querystring.
"""
- # FIXME: We can remove that getattr as soon as #20619 is fixed.
- match = getattr(request, 'resolver_match', None)
-
+ match = request.resolver_match
if self.preserve_filters and match:
opts = self.model._meta
- current_url = '%s:%s' % (match.namespace, match.url_name)
+ current_url = '%s:%s' % (match.app_name, match.url_name)
changelist_url = 'admin:%s_%s_changelist' % (opts.app_label, opts.model_name)
if current_url == changelist_url:
preserved_filters = request.GET.urlencode()
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index e0f43dfbfe..8abf49331b 100644
--- a/django/contrib/admin/sites.py
+++ b/django/contrib/admin/sites.py
@@ -439,6 +439,7 @@ class AdminSite(object):
context = {
'title': _('%s administration') % capfirst(app_label),
'app_list': [app_dict],
+ 'app_label': app_label,
}
context.update(extra_context or {})
diff --git a/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js b/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js
index 671af9bd78..aa12f8cf01 100644
--- a/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js
+++ b/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js
@@ -14,6 +14,8 @@ var DateTimeShortcuts = {
clockDivName: 'clockbox', // name of clock <div> that gets toggled
clockLinkName: 'clocklink', // name of the link that is used to toggle
shortCutsClass: 'datetimeshortcuts', // class of the clock and cal shortcuts
+ timezoneWarningClass: 'timezonewarning', // class of the warning for timezone mismatch
+ timezoneOffset: 0,
admin_media_prefix: '',
init: function() {
// Get admin_media_prefix by grabbing it off the window object. It's
@@ -26,17 +28,77 @@ var DateTimeShortcuts = {
DateTimeShortcuts.admin_media_prefix = '/missing-admin-media-prefix/';
}
+ if (window.__admin_utc_offset__ != undefined) {
+ var serverOffset = window.__admin_utc_offset__;
+ var localOffset = new Date().getTimezoneOffset() * -60;
+ DateTimeShortcuts.timezoneOffset = localOffset - serverOffset;
+ }
+
var inputs = document.getElementsByTagName('input');
for (i=0; i<inputs.length; i++) {
var inp = inputs[i];
if (inp.getAttribute('type') == 'text' && inp.className.match(/vTimeField/)) {
DateTimeShortcuts.addClock(inp);
+ DateTimeShortcuts.addTimezoneWarning(inp);
}
else if (inp.getAttribute('type') == 'text' && inp.className.match(/vDateField/)) {
DateTimeShortcuts.addCalendar(inp);
+ DateTimeShortcuts.addTimezoneWarning(inp);
}
}
},
+ // Return the current time while accounting for the server timezone.
+ now: function() {
+ if (window.__admin_utc_offset__ != undefined) {
+ var serverOffset = window.__admin_utc_offset__;
+ var localNow = new Date();
+ var localOffset = localNow.getTimezoneOffset() * -60;
+ localNow.setTime(localNow.getTime() + 1000 * (serverOffset - localOffset));
+ return localNow;
+ } else {
+ return new Date();
+ }
+ },
+ // Add a warning when the time zone in the browser and backend do not match.
+ addTimezoneWarning: function(inp) {
+ var $ = django.jQuery;
+ var warningClass = DateTimeShortcuts.timezoneWarningClass;
+ var timezoneOffset = DateTimeShortcuts.timezoneOffset / 3600;
+
+ // Only warn if there is a time zone mismatch.
+ if (!timezoneOffset)
+ return;
+
+ // Check if warning is already there.
+ if ($(inp).siblings('.' + warningClass).length)
+ return;
+
+ var message;
+ if (timezoneOffset > 0) {
+ message = ngettext(
+ 'Note: You are %s hour ahead of server time.',
+ 'Note: You are %s hours ahead of server time.',
+ timezoneOffset
+ );
+ }
+ else {
+ timezoneOffset *= -1
+ message = ngettext(
+ 'Note: You are %s hour behind server time.',
+ 'Note: You are %s hours behind server time.',
+ timezoneOffset
+ );
+ }
+ message = interpolate(message, [timezoneOffset]);
+
+ var $warning = $('<span>');
+ $warning.attr('class', warningClass);
+ $warning.text(message);
+
+ $(inp).parent()
+ .append($('<br>'))
+ .append($warning)
+ },
// Add clock widget to a given field
addClock: function(inp) {
var num = DateTimeShortcuts.clockInputs.length;
@@ -48,7 +110,7 @@ var DateTimeShortcuts = {
shortcuts_span.className = DateTimeShortcuts.shortCutsClass;
inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
var now_link = document.createElement('a');
- now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().strftime('" + get_format('TIME_INPUT_FORMATS')[0] + "'));");
+ now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", -1);");
now_link.appendChild(document.createTextNode(gettext('Now')));
var clock_link = document.createElement('a');
clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');');
@@ -84,11 +146,10 @@ var DateTimeShortcuts = {
quickElement('h2', clock_box, gettext('Choose a time'));
var time_list = quickElement('ul', clock_box, '');
time_list.className = 'timelist';
- var time_format = get_format('TIME_INPUT_FORMATS')[0];
- quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().strftime('" + time_format + "'));");
- quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,0,0,0,0).strftime('" + time_format + "'));");
- quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,6,0,0,0).strftime('" + time_format + "'));");
- quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,12,0,0,0).strftime('" + time_format + "'));");
+ quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", -1);");
+ quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", 0);");
+ quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", 6);");
+ quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", 12);");
var cancel_p = quickElement('p', clock_box, '');
cancel_p.className = 'calendar-cancel';
@@ -128,7 +189,14 @@ var DateTimeShortcuts = {
removeEvent(document, 'click', DateTimeShortcuts.dismissClockFunc[num]);
},
handleClockQuicklink: function(num, val) {
- DateTimeShortcuts.clockInputs[num].value = val;
+ var d;
+ if (val == -1) {
+ d = DateTimeShortcuts.now();
+ }
+ else {
+ d = new Date(1970, 1, 1, val, 0, 0, 0)
+ }
+ DateTimeShortcuts.clockInputs[num].value = d.strftime(get_format('TIME_INPUT_FORMATS')[0]);
DateTimeShortcuts.clockInputs[num].focus();
DateTimeShortcuts.dismissClock(num);
},
@@ -258,7 +326,7 @@ var DateTimeShortcuts = {
DateTimeShortcuts.calendars[num].drawNextMonth();
},
handleCalendarCallback: function(num) {
- format = get_format('DATE_INPUT_FORMATS')[0];
+ var format = get_format('DATE_INPUT_FORMATS')[0];
// the format needs to be escaped a little
format = format.replace('\\', '\\\\');
format = format.replace('\r', '\\r');
@@ -276,7 +344,7 @@ var DateTimeShortcuts = {
").style.display='none';}"].join('');
},
handleCalendarQuickLink: function(num, offset) {
- var d = new Date();
+ var d = DateTimeShortcuts.now();
d.setDate(d.getDate() + offset)
DateTimeShortcuts.calendarInputs[num].value = d.strftime(get_format('DATE_INPUT_FORMATS')[0]);
DateTimeShortcuts.calendarInputs[num].focus();
diff --git a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
index 6d1748663e..c82c8c05cb 100644
--- a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
+++ b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
@@ -55,7 +55,7 @@ function dismissRelatedLookupPopup(win, chosenId) {
function showAddAnotherPopup(triggeringLink) {
var name = triggeringLink.id.replace(/^add_/, '');
name = id_to_windowname(name);
- href = triggeringLink.href
+ var href = triggeringLink.href;
if (href.indexOf('?') == -1) {
href += '?_popup=1';
} else {
@@ -73,10 +73,11 @@ function dismissAddAnotherPopup(win, newId, newRepr) {
newRepr = html_unescape(newRepr);
var name = windowname_to_id(win.name);
var elem = document.getElementById(name);
+ var o;
if (elem) {
var elemName = elem.nodeName.toUpperCase();
if (elemName == 'SELECT') {
- var o = new Option(newRepr, newId);
+ o = new Option(newRepr, newId);
elem.options[elem.options.length] = o;
o.selected = true;
} else if (elemName == 'INPUT') {
@@ -88,8 +89,7 @@ function dismissAddAnotherPopup(win, newId, newRepr) {
}
} else {
var toId = name + "_to";
- elem = document.getElementById(toId);
- var o = new Option(newRepr, newId);
+ o = new Option(newRepr, newId);
SelectBox.add_to_cache(toId, o);
SelectBox.redisplay(toId);
}
diff --git a/django/contrib/admin/templates/admin/app_index.html b/django/contrib/admin/templates/admin/app_index.html
index 9f65357292..7aff935126 100644
--- a/django/contrib/admin/templates/admin/app_index.html
+++ b/django/contrib/admin/templates/admin/app_index.html
@@ -1,6 +1,8 @@
{% extends "admin/index.html" %}
{% load i18n %}
+{% block bodyclass %}app-{{ app_label }} {{ block.super }}{% endblock %}
+
{% if not is_popup %}
{% block breadcrumbs %}
<div class="breadcrumbs">
diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html
index 63d4419fae..9c79a4e698 100644
--- a/django/contrib/admin/templates/admin/base.html
+++ b/django/contrib/admin/templates/admin/base.html
@@ -7,6 +7,7 @@
<!--[if lte IE 7]><link rel="stylesheet" type="text/css" href="{% block stylesheet_ie %}{% static "admin/css/ie.css" %}{% endblock %}" /><![endif]-->
{% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}" />{% endif %}
<script type="text/javascript">window.__admin_media_prefix__ = "{% filter escapejs %}{% static "admin/" %}{% endfilter %}";</script>
+<script type="text/javascript">window.__admin_utc_offset__ = "{% filter escapejs %}{% now "Z" %}{% endfilter %}";</script>
{% block extrahead %}{% endblock %}
{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
</head>
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html
index d4935ed781..e35c7328f4 100644
--- a/django/contrib/admin/templates/admin/change_form.html
+++ b/django/contrib/admin/templates/admin/change_form.html
@@ -10,7 +10,7 @@
{% block coltype %}colM{% endblock %}
-{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
+{% block bodyclass %}app-{{ opts.app_label }} model-{{ opts.object_name.lower }} change-form{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html
index 15583d1a8c..ce5c5b37a4 100644
--- a/django/contrib/admin/templates/admin/change_list.html
+++ b/django/contrib/admin/templates/admin/change_list.html
@@ -32,7 +32,7 @@
{% endif %}{% endif %}
{% endblock %}
-{% block bodyclass %}change-list{% endblock %}
+{% block bodyclass %}app-{{ opts.app_label }} model-{{ opts.object_name.lower }} change-list{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html
index 3ed60807b7..2e94b61485 100644
--- a/django/contrib/admin/templates/admin/delete_confirmation.html
+++ b/django/contrib/admin/templates/admin/delete_confirmation.html
@@ -1,6 +1,8 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls %}
+{% block bodyclass %}app-{{ opts.app_label }} model-{{ opts.object_name.lower }} delete-confirmation{% endblock %}
+
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
diff --git a/django/contrib/admin/templates/admin/delete_selected_confirmation.html b/django/contrib/admin/templates/admin/delete_selected_confirmation.html
index 36527dcbf5..a80f4e4a54 100644
--- a/django/contrib/admin/templates/admin/delete_selected_confirmation.html
+++ b/django/contrib/admin/templates/admin/delete_selected_confirmation.html
@@ -1,6 +1,8 @@
{% extends "admin/base_site.html" %}
{% load i18n l10n admin_urls %}
+{% block bodyclass %}app-{{ opts.app_label }} model-{{ opts.object_name.lower }} delete-confirmation delete-selected-confirmation{% endblock %}
+
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html
index 2025dd8083..32917756b3 100644
--- a/django/contrib/admin/templates/admin/edit_inline/stacked.html
+++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html
@@ -13,7 +13,7 @@
{% for fieldset in inline_admin_form %}
{% include "admin/includes/fieldset.html" %}
{% endfor %}
- {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
+ {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
{{ inline_admin_form.fk_field.field }}
</div>{% endfor %}
</div>
diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html
index ca853bc989..e6097f0dce 100644
--- a/django/contrib/admin/templates/admin/edit_inline/tabular.html
+++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html
@@ -29,7 +29,7 @@
{% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %}
{% if inline_admin_form.show_url %}<a href="{% url 'admin:view_on_site' inline_admin_form.original_content_type_id inline_admin_form.original.pk %}">{% trans "View on site" %}</a>{% endif %}
</p>{% endif %}
- {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
+ {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
{{ inline_admin_form.fk_field.field }}
{% spaceless %}
{% for fieldset in inline_admin_form %}
diff --git a/django/contrib/admin/templatetags/admin_urls.py b/django/contrib/admin/templatetags/admin_urls.py
index 19da87d61f..5d9c5b5427 100644
--- a/django/contrib/admin/templatetags/admin_urls.py
+++ b/django/contrib/admin/templatetags/admin_urls.py
@@ -38,7 +38,7 @@ def add_preserved_filters(context, url, popup=False):
except Resolver404:
pass
else:
- current_url = '%s:%s' % (match.namespace, match.url_name)
+ current_url = '%s:%s' % (match.app_name, match.url_name)
changelist_url = 'admin:%s_%s_changelist' % (opts.app_label, opts.model_name)
if changelist_url == current_url and '_changelist_filters' in preserved_filters:
preserved_filters = dict(parse_qsl(preserved_filters['_changelist_filters']))
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
index 5a1db68613..ca660606e5 100644
--- a/django/contrib/auth/admin.py
+++ b/django/contrib/auth/admin.py
@@ -70,10 +70,7 @@ class UserAdmin(admin.ModelAdmin):
"""
defaults = {}
if obj is None:
- defaults.update({
- 'form': self.add_form,
- 'fields': admin.util.flatten_fieldsets(self.add_fieldsets),
- })
+ defaults['form'] = self.add_form
defaults.update(kwargs)
return super(UserAdmin, self).get_form(request, obj, **defaults)
diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py
index 05e9bfd721..6b31f72b03 100644
--- a/django/contrib/auth/backends.py
+++ b/django/contrib/auth/backends.py
@@ -5,7 +5,7 @@ from django.contrib.auth.models import Permission
class ModelBackend(object):
"""
- Authenticates against django.contrib.auth.models.User.
+ Authenticates against settings.AUTH_USER_MODEL.
"""
def authenticate(self, username=None, password=None, **kwargs):
diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py
index 87e4218a8f..7656b50437 100644
--- a/django/contrib/auth/hashers.py
+++ b/django/contrib/auth/hashers.py
@@ -22,6 +22,7 @@ UNUSABLE_PASSWORD_SUFFIX_LENGTH = 40 # number of random chars to add after UNUS
HASHERS = None # lazily loaded from PASSWORD_HASHERS
PREFERRED_HASHER = None # defaults to first item in PASSWORD_HASHERS
+
@receiver(setting_changed)
def reset_hashers(**kwargs):
if kwargs['setting'] == 'PASSWORD_HASHERS':
@@ -34,7 +35,7 @@ def is_password_usable(encoded):
if encoded is None or encoded.startswith(UNUSABLE_PASSWORD_PREFIX):
return False
try:
- hasher = identify_hasher(encoded)
+ identify_hasher(encoded)
except ValueError:
return False
return True
@@ -48,7 +49,7 @@ def check_password(password, encoded, setter=None, preferred='default'):
If setter is specified, it'll be called when you need to
regenerate the password.
"""
- if not is_password_usable(encoded):
+ if password is None or not is_password_usable(encoded):
return False
preferred = get_hasher(preferred)
diff --git a/django/contrib/auth/tests/test_hashers.py b/django/contrib/auth/tests/test_hashers.py
index 8e8119b741..4b90127c37 100644
--- a/django/contrib/auth/tests/test_hashers.py
+++ b/django/contrib/auth/tests/test_hashers.py
@@ -187,6 +187,13 @@ class TestUtilsHashPass(unittest.TestCase):
# This might fail one day due to a hash collision.
self.assertNotEqual(encoded, make_password(None), "Random password collision?")
+ def test_unspecified_password(self):
+ """
+ Makes sure specifying no plain password with a valid encoded password
+ returns `False`.
+ """
+ self.assertFalse(check_password(None, make_password('lètmein')))
+
def test_bad_algorithm(self):
with self.assertRaises(ValueError):
make_password('lètmein', hasher='lolcat')
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index 04d4894b8a..6f120f82e0 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -66,7 +66,7 @@ class GenericForeignKey(six.with_metaclass(RenameGenericForeignKeyMethods)):
if obj is not None:
return ContentType.objects.db_manager(obj._state.db).get_for_model(
obj, for_concrete_model=self.for_concrete_model)
- elif id:
+ elif id is not None:
return ContentType.objects.db_manager(using).get_for_id(id)
else:
# This should never happen. I love comments like this, don't you?
@@ -130,7 +130,7 @@ class GenericForeignKey(six.with_metaclass(RenameGenericForeignKeyMethods)):
# performance when dealing with GFKs in loops and such.
f = self.model._meta.get_field(self.ct_field)
ct_id = getattr(instance, f.get_attname(), None)
- if ct_id:
+ if ct_id is not None:
ct = self.get_content_type(id=ct_id, using=instance._state.db)
try:
rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field))
diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py
index 84217c331b..628018320b 100644
--- a/django/contrib/gis/db/backends/oracle/operations.py
+++ b/django/contrib/gis/db/backends/oracle/operations.py
@@ -78,7 +78,7 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
name = 'oracle'
oracle = True
- valid_aggregates = dict([(a, None) for a in ('Union', 'Extent')])
+ valid_aggregates = {'Union', 'Extent'}
Adapter = OracleSpatialAdapter
Adaptor = Adapter # Backwards-compatibility alias.
diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py
index 84dbda3239..3863bea992 100644
--- a/django/contrib/gis/db/backends/postgis/operations.py
+++ b/django/contrib/gis/db/backends/postgis/operations.py
@@ -56,6 +56,7 @@ class PostGISSphereDistance(PostGISDistance):
class PostGISRelate(PostGISFunctionParam):
"For PostGIS Relate(<geom>, <pattern>) calls."
pattern_regex = re.compile(r'^[012TF\*]{9}$')
+
def __init__(self, prefix, pattern):
if not self.pattern_regex.match(pattern):
raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
@@ -68,8 +69,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
postgis = True
geom_func_prefix = 'ST_'
version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
- valid_aggregates = dict([(k, None) for k in
- ('Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union')])
+ valid_aggregates = {'Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union'}
Adapter = PostGISAdapter
Adaptor = Adapter # Backwards-compatibility alias.
diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py
index 4281cafa23..3cb2004dfd 100644
--- a/django/contrib/gis/db/backends/spatialite/operations.py
+++ b/django/contrib/gis/db/backends/spatialite/operations.py
@@ -56,7 +56,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
name = 'spatialite'
spatialite = True
version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
- valid_aggregates = dict([(k, None) for k in ('Extent', 'Union')])
+ valid_aggregates = {'Extent', 'Union'}
Adapter = SpatiaLiteAdapter
Adaptor = Adapter # Backwards-compatibility alias.
@@ -169,6 +169,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
Checks if the given aggregate name is supported (that is, if it's
in `self.valid_aggregates`).
"""
+ super(SpatiaLiteOperations, self).check_aggregate_support(aggregate)
agg_name = aggregate.__class__.__name__
return agg_name in self.valid_aggregates
diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py
index c89912b2d9..4199d19a88 100644
--- a/django/contrib/gis/db/models/query.py
+++ b/django/contrib/gis/db/models/query.py
@@ -140,7 +140,7 @@ class GeoQuerySet(QuerySet):
Returns a GeoJSON representation of the geomtry field in a `geojson`
attribute on each element of the GeoQuerySet.
- The `crs` and `bbox` keywords may be set to True if the users wants
+ The `crs` and `bbox` keywords may be set to True if the user wants
the coordinate reference system and the bounding box to be included
in the GeoJSON representation of the geometry.
"""
diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py
index 0d75620b4e..d75e8cd288 100644
--- a/django/contrib/gis/gdal/geometries.py
+++ b/django/contrib/gis/gdal/geometries.py
@@ -198,14 +198,6 @@ class OGRGeometry(GDALBase):
def _get_coord_dim(self):
"Returns the coordinate dimension of the Geometry."
- if isinstance(self, GeometryCollection) and GDAL_VERSION < (1, 5, 2):
- # On GDAL versions prior to 1.5.2, there exists a bug in which
- # the coordinate dimension of geometry collections is always 2:
- # http://trac.osgeo.org/gdal/ticket/2334
- # Here we workaround by returning the coordinate dimension of the
- # first geometry in the collection instead.
- if len(self):
- return capi.get_coord_dim(capi.get_geom_ref(self.ptr, 0))
return capi.get_coord_dim(self.ptr)
def _set_coord_dim(self, dim):
diff --git a/django/contrib/gis/management/commands/ogrinspect.py b/django/contrib/gis/management/commands/ogrinspect.py
index 44513e3463..2a29fe672a 100644
--- a/django/contrib/gis/management/commands/ogrinspect.py
+++ b/django/contrib/gis/management/commands/ogrinspect.py
@@ -53,7 +53,7 @@ class Command(LabelCommand):
make_option('--multi-geom', action='store_true', dest='multi_geom', default=False,
help='Treat the geometry in the data source as a geometry collection.'),
make_option('--name-field', dest='name_field',
- help='Specifies a field name to return for the `__unicode__` function.'),
+ help='Specifies a field name to return for the `__unicode__`/`__str__` function.'),
make_option('--no-imports', action='store_false', dest='imports', default=True,
help='Do not include `from django.contrib.gis.db import models` '
'statement.'),
diff --git a/django/contrib/gis/maps/google/gmap.py b/django/contrib/gis/maps/google/gmap.py
index 75b285ca76..ff6562696d 100644
--- a/django/contrib/gis/maps/google/gmap.py
+++ b/django/contrib/gis/maps/google/gmap.py
@@ -134,7 +134,8 @@ class GoogleMap(object):
@property
def scripts(self):
"Returns all <script></script> tags required with Google Maps JavaScript."
- return format_html('%s\n <script type="text/javascript">\n//<![CDATA[\n%s//]]>\n </script>', self.api_script, mark_safe(self.js))
+ return format_html('{0}\n <script type="text/javascript">\n//<![CDATA[\n{1}//]]>\n </script>',
+ self.api_script, mark_safe(self.js))
@property
def style(self):
diff --git a/django/contrib/gis/tests/__init__.py b/django/contrib/gis/tests/__init__.py
index 1703f3f1b3..e69de29bb2 100644
--- a/django/contrib/gis/tests/__init__.py
+++ b/django/contrib/gis/tests/__init__.py
@@ -1,30 +0,0 @@
-def geo_apps():
- """
- Returns a list of GeoDjango test applications that reside in
- `django.contrib.gis.tests` that can be used with the current
- database and the spatial libraries that are installed.
- """
- from django.db import connection
- from django.contrib.gis.geos import GEOS_PREPARE
- from django.contrib.gis.gdal import HAS_GDAL
-
- apps = ['geoapp', 'relatedapp']
-
- # No distance queries on MySQL.
- if not connection.ops.mysql:
- apps.append('distapp')
-
- # Test geography support with PostGIS 1.5+.
- if connection.ops.postgis and connection.ops.geography:
- apps.append('geogapp')
-
- # The following GeoDjango test apps depend on GDAL support.
- if HAS_GDAL:
- # Geographic admin, LayerMapping, and ogrinspect test apps
- # all require GDAL.
- apps.extend(['geoadmin', 'layermap', 'inspectapp'])
-
- # 3D apps use LayerMapping, which uses GDAL and require GEOS 3.1+.
- if connection.ops.postgis and GEOS_PREPARE:
- apps.append('geo3d')
- return [('django.contrib.gis.tests', app) for app in apps]
diff --git a/django/contrib/gis/tests/geoadmin/tests.py b/django/contrib/gis/tests/geoadmin/tests.py
index c34964a2da..df4158bb31 100644
--- a/django/contrib/gis/tests/geoadmin/tests.py
+++ b/django/contrib/gis/tests/geoadmin/tests.py
@@ -2,9 +2,10 @@ from __future__ import absolute_import
from unittest import skipUnless
-from django.test import TestCase
from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
+from django.test import TestCase
+from django.test.utils import override_settings
if HAS_GEOS and HAS_SPATIAL_DB:
from django.contrib.gis import admin
@@ -12,6 +13,8 @@ if HAS_GEOS and HAS_SPATIAL_DB:
from .models import City
+GOOGLE_MAPS_API_KEY = 'XXXX'
+
@skipUnless(HAS_GEOS and HAS_SPATIAL_DB, "Geos and spatial db are required.")
class GeoAdminTest(TestCase):
@@ -39,7 +42,9 @@ class GeoAdminTest(TestCase):
result)
def test_olwidget_has_changed(self):
- """ Check that changes are accurately noticed by OpenLayersWidget. """
+ """
+ Check that changes are accurately noticed by OpenLayersWidget.
+ """
geoadmin = admin.site._registry[City]
form = geoadmin.get_changelist_form(None)()
has_changed = form.fields['point']._has_changed
@@ -55,3 +60,15 @@ class GeoAdminTest(TestCase):
self.assertFalse(has_changed(initial, data_same))
self.assertFalse(has_changed(initial, data_almost_same))
self.assertTrue(has_changed(initial, data_changed))
+
+ @override_settings(GOOGLE_MAPS_API_KEY=GOOGLE_MAPS_API_KEY)
+ def test_google_map_scripts(self):
+ """
+ Testing GoogleMap.scripts() output. See #20773.
+ """
+ from django.contrib.gis.maps.google.gmap import GoogleMap
+
+ google_map = GoogleMap()
+ scripts = google_map.scripts
+ self.assertIn(GOOGLE_MAPS_API_KEY, scripts)
+ self.assertIn("new GMap2", scripts)
diff --git a/django/contrib/gis/tests/geoapp/test_sitemaps.py b/django/contrib/gis/tests/geoapp/test_sitemaps.py
index facd94c247..98cd8cc5ac 100644
--- a/django/contrib/gis/tests/geoapp/test_sitemaps.py
+++ b/django/contrib/gis/tests/geoapp/test_sitemaps.py
@@ -3,6 +3,7 @@ from __future__ import absolute_import
from io import BytesIO
from unittest import skipUnless
from xml.dom import minidom
+import os
import zipfile
from django.conf import settings
@@ -10,6 +11,7 @@ from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
from django.contrib.sites.models import Site
from django.test import TestCase
+from django.utils._os import upath
if HAS_GEOS:
from .models import City, Country
@@ -37,7 +39,11 @@ class GeoSitemapTest(TestCase):
def test_geositemap_index(self):
"Tests geographic sitemap index."
# Getting the geo index.
- doc = minidom.parseString(self.client.get('/sitemap.xml').content)
+ from django.contrib import sitemaps
+ template_dirs = settings.TEMPLATE_DIRS + (
+ os.path.join(os.path.dirname(upath(sitemaps.__file__)), 'templates'),)
+ with self.settings(TEMPLATE_DIRS=template_dirs):
+ doc = minidom.parseString(self.client.get('/sitemap.xml').content)
index = doc.firstChild
self.assertEqual(index.getAttribute('xmlns'), 'http://www.sitemaps.org/schemas/sitemap/0.9')
self.assertEqual(3, len(index.getElementsByTagName('sitemap')))
diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py
index 487f879674..eabc5958c0 100644
--- a/django/contrib/gis/tests/geoapp/tests.py
+++ b/django/contrib/gis/tests/geoapp/tests.py
@@ -8,7 +8,7 @@ from django.db import connection
from django.contrib.gis import gdal
from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.tests.utils import (
- no_mysql, no_oracle, no_spatialite,
+ HAS_SPATIAL_DB, no_mysql, no_oracle, no_spatialite,
mysql, oracle, postgis, spatialite)
from django.test import TestCase
from django.utils import six
@@ -28,7 +28,7 @@ def postgis_bug_version():
return spatial_version and (2, 0, 0) <= spatial_version <= (2, 0, 1)
-@skipUnless(HAS_GEOS and postgis, "Geos and postgis are required.")
+@skipUnless(HAS_GEOS and HAS_SPATIAL_DB, "Geos and spatial db are required.")
class GeoModelTest(TestCase):
def test_fixtures(self):
@@ -203,7 +203,7 @@ class GeoModelTest(TestCase):
self.assertTrue(isinstance(cities2[0].point, Point))
-@skipUnless(HAS_GEOS and postgis, "Geos and postgis are required.")
+@skipUnless(HAS_GEOS and HAS_SPATIAL_DB, "Geos and spatial db are required.")
class GeoLookupTest(TestCase):
@no_mysql
@@ -389,7 +389,7 @@ class GeoLookupTest(TestCase):
self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name)
-@skipUnless(HAS_GEOS and postgis, "Geos and postgis are required.")
+@skipUnless(HAS_GEOS and HAS_SPATIAL_DB, "Geos and spatial db are required.")
class GeoQuerySetTest(TestCase):
# Please keep the tests in GeoQuerySet method's alphabetic order
@@ -433,6 +433,13 @@ class GeoQuerySetTest(TestCase):
self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
self.assertEqual(c.mpoly.union(geom), c.union)
+ @skipUnless(getattr(connection.ops, 'envelope', False), 'Database does not support envelope operation')
+ def test_envelope(self):
+ "Testing the `envelope` GeoQuerySet method."
+ countries = Country.objects.all().envelope()
+ for country in countries:
+ self.assertIsInstance(country.envelope, Polygon)
+
@no_mysql
@no_spatialite # SpatiaLite does not have an Extent function
def test_extent(self):
diff --git a/django/contrib/gis/tests/test_spatialrefsys.py b/django/contrib/gis/tests/test_spatialrefsys.py
index 8daa6acc23..5bb6a70206 100644
--- a/django/contrib/gis/tests/test_spatialrefsys.py
+++ b/django/contrib/gis/tests/test_spatialrefsys.py
@@ -23,7 +23,7 @@ test_srs = ({'srid' : 4326,
'auth_srid' : 32140,
'srtext' : 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980"',
'proj4_re' : r'\+proj=lcc \+lat_1=30.28333333333333 \+lat_2=28.38333333333333 \+lat_0=27.83333333333333 '
- r'\+lon_0=-99 \+x_0=600000 \+y_0=4000000 \+ellps=GRS80 '
+ r'\+lon_0=-99 \+x_0=600000 \+y_0=4000000 (\+ellps=GRS80 )?'
r'(\+datum=NAD83 |\+towgs84=0,0,0,0,0,0,0)?\+units=m \+no_defs ',
'spheroid' : 'GRS 1980', 'name' : 'NAD83 / Texas South Central',
'geographic' : False, 'projected' : True, 'spatialite' : False,
diff --git a/django/contrib/gis/utils/ogrinspect.py b/django/contrib/gis/utils/ogrinspect.py
index 08d3b40397..b7cfafd49d 100644
--- a/django/contrib/gis/utils/ogrinspect.py
+++ b/django/contrib/gis/utils/ogrinspect.py
@@ -89,7 +89,7 @@ def ogrinspect(*args, **kwargs):
`multi_geom` => Boolean (default: False) - specify as multigeometry.
`name_field` => String - specifies a field name to return for the
- `__unicode__` function (which will be generated if specified).
+ `__unicode__`/`__str__` function (which will be generated if specified).
`imports` => Boolean (default: True) - set to False to omit the
`from django.contrib.gis.db import models` code from the
@@ -221,4 +221,5 @@ def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=Non
if name_field:
yield ''
- yield ' def __str__(self): return self.%s' % name_field
+ yield ' def __%s__(self): return self.%s' % (
+ 'str' if six.PY3 else 'unicode', name_field)
diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py
index 3bfba3ba08..cec204dc92 100644
--- a/django/contrib/syndication/views.py
+++ b/django/contrib/syndication/views.py
@@ -43,9 +43,9 @@ class Feed(object):
raise Http404('Feed object does not exist.')
feedgen = self.get_feed(obj, request)
response = HttpResponse(content_type=feedgen.mime_type)
- if hasattr(self, 'item_pubdate'):
- # if item_pubdate is defined for the feed, set header so as
- # ConditionalGetMiddleware is able to send 304 NOT MODIFIED
+ if hasattr(self, 'item_pubdate') or hasattr(self, 'item_updateddate'):
+ # if item_pubdate or item_updateddate is defined for the feed, set
+ # header so as ConditionalGetMiddleware is able to send 304 NOT MODIFIED
response['Last-Modified'] = http_date(
timegm(feedgen.latest_post_date().utctimetuple()))
feedgen.write(response, 'utf-8')
@@ -191,6 +191,11 @@ class Feed(object):
ltz = tzinfo.LocalTimezone(pubdate)
pubdate = pubdate.replace(tzinfo=ltz)
+ updateddate = self.__get_dynamic_attr('item_updateddate', item)
+ if updateddate and is_naive(updateddate):
+ ltz = tzinfo.LocalTimezone(updateddate)
+ updateddate = updateddate.replace(tzinfo=ltz)
+
feed.add_item(
title = title,
link = link,
@@ -200,6 +205,7 @@ class Feed(object):
'item_guid_is_permalink', item),
enclosure = enc,
pubdate = pubdate,
+ updateddate = updateddate,
author_name = author_name,
author_email = author_email,
author_link = author_link,
diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py
index 1fa17052fd..44d7db62c3 100644
--- a/django/core/cache/backends/locmem.py
+++ b/django/core/cache/backends/locmem.py
@@ -9,16 +9,17 @@ except ImportError:
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
from django.utils.synch import RWLock
+
# Global in-memory store of cache data. Keyed by name, to provide
# multiple named local memory caches.
_caches = {}
_expire_info = {}
_locks = {}
+
class LocMemCache(BaseCache):
def __init__(self, name, params):
BaseCache.__init__(self, params)
- global _caches, _expire_info, _locks
self._cache = _caches.setdefault(name, {})
self._expire_info = _expire_info.setdefault(name, {})
self._lock = _locks.setdefault(name, RWLock())
@@ -26,28 +27,31 @@ class LocMemCache(BaseCache):
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
+ try:
+ pickled = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
+ except pickle.PickleError:
+ return False
with self._lock.writer():
exp = self._expire_info.get(key)
if exp is None or exp <= time.time():
- try:
- pickled = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
- self._set(key, pickled, timeout)
- return True
- except pickle.PickleError:
- pass
+ self._set(key, pickled, timeout)
+ return True
return False
def get(self, key, default=None, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
+ pickled = None
with self._lock.reader():
exp = self._expire_info.get(key, 0)
if exp is None or exp > time.time():
- try:
- pickled = self._cache[key]
- return pickle.loads(pickled)
- except pickle.PickleError:
- return default
+ pickled = self._cache[key]
+ if pickled is not None:
+ try:
+ return pickle.loads(pickled)
+ except pickle.PickleError:
+ return default
+
with self._lock.writer():
try:
del self._cache[key]
@@ -68,12 +72,13 @@ class LocMemCache(BaseCache):
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
- with self._lock.writer():
- try:
- pickled = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
+ try:
+ pickled = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
+ except pickle.PickleError:
+ pass
+ else:
+ with self._lock.writer():
self._set(key, pickled, timeout)
- except pickle.PickleError:
- pass
def incr(self, key, delta=1, version=None):
value = self.get(key, version=version)
@@ -81,12 +86,13 @@ class LocMemCache(BaseCache):
raise ValueError("Key '%s' not found" % key)
new_value = value + delta
key = self.make_key(key, version=version)
- with self._lock.writer():
- try:
- pickled = pickle.dumps(new_value, pickle.HIGHEST_PROTOCOL)
+ try:
+ pickled = pickle.dumps(new_value, pickle.HIGHEST_PROTOCOL)
+ except pickle.PickleError:
+ pass
+ else:
+ with self._lock.writer():
self._cache[key] = pickled
- except pickle.PickleError:
- pass
return new_value
def has_key(self, key, version=None):
@@ -135,6 +141,7 @@ class LocMemCache(BaseCache):
self._cache.clear()
self._expire_info.clear()
+
# For backwards compatibility
class CacheClass(LocMemCache):
pass
diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py
index 64d1c41dc5..19e8b02f74 100644
--- a/django/core/cache/backends/memcached.py
+++ b/django/core/cache/backends/memcached.py
@@ -86,7 +86,7 @@ class BaseMemcachedCache(BaseCache):
self._cache.delete(key)
def get_many(self, keys, version=None):
- new_keys = map(lambda x: self.make_key(x, version=version), keys)
+ new_keys = [self.make_key(x, version=version) for x in keys]
ret = self._cache.get_multi(new_keys)
if ret:
_ = {}
diff --git a/django/core/files/uploadedfile.py b/django/core/files/uploadedfile.py
index 39b99ff78f..9f948ca03b 100644
--- a/django/core/files/uploadedfile.py
+++ b/django/core/files/uploadedfile.py
@@ -23,11 +23,12 @@ class UploadedFile(File):
"""
DEFAULT_CHUNK_SIZE = 64 * 2**10
- def __init__(self, file=None, name=None, content_type=None, size=None, charset=None):
+ def __init__(self, file=None, name=None, content_type=None, size=None, charset=None, content_type_extra=None):
super(UploadedFile, self).__init__(file, name)
self.size = size
self.content_type = content_type
self.charset = charset
+ self.content_type_extra = content_type_extra
def __repr__(self):
return force_str("<%s: %s (%s)>" % (
@@ -55,13 +56,13 @@ class TemporaryUploadedFile(UploadedFile):
"""
A file uploaded to a temporary location (i.e. stream-to-disk).
"""
- def __init__(self, name, content_type, size, charset):
+ def __init__(self, name, content_type, size, charset, content_type_extra):
if settings.FILE_UPLOAD_TEMP_DIR:
file = tempfile.NamedTemporaryFile(suffix='.upload',
dir=settings.FILE_UPLOAD_TEMP_DIR)
else:
file = tempfile.NamedTemporaryFile(suffix='.upload')
- super(TemporaryUploadedFile, self).__init__(file, name, content_type, size, charset)
+ super(TemporaryUploadedFile, self).__init__(file, name, content_type, size, charset, content_type_extra)
def temporary_file_path(self):
"""
@@ -83,8 +84,8 @@ class InMemoryUploadedFile(UploadedFile):
"""
A file uploaded into memory (i.e. stream-to-memory).
"""
- def __init__(self, file, field_name, name, content_type, size, charset):
- super(InMemoryUploadedFile, self).__init__(file, name, content_type, size, charset)
+ def __init__(self, file, field_name, name, content_type, size, charset, content_type_extra):
+ super(InMemoryUploadedFile, self).__init__(file, name, content_type, size, charset, content_type_extra)
self.field_name = field_name
def open(self, mode=None):
@@ -109,7 +110,7 @@ class SimpleUploadedFile(InMemoryUploadedFile):
def __init__(self, name, content, content_type='text/plain'):
content = content or b''
super(SimpleUploadedFile, self).__init__(BytesIO(content), None, name,
- content_type, len(content), None)
+ content_type, len(content), None, None)
def from_dict(cls, file_dict):
"""
diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py
index f5e95cf2fd..6739b26e0c 100644
--- a/django/core/files/uploadhandler.py
+++ b/django/core/files/uploadhandler.py
@@ -64,6 +64,7 @@ class FileUploadHandler(object):
self.content_type = None
self.content_length = None
self.charset = None
+ self.content_type_extra = None
self.request = request
def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
@@ -84,7 +85,7 @@ class FileUploadHandler(object):
"""
pass
- def new_file(self, field_name, file_name, content_type, content_length, charset=None):
+ def new_file(self, field_name, file_name, content_type, content_length, charset=None, content_type_extra=None):
"""
Signal that a new file has been started.
@@ -96,6 +97,7 @@ class FileUploadHandler(object):
self.content_type = content_type
self.content_length = content_length
self.charset = charset
+ self.content_type_extra = content_type_extra
def receive_data_chunk(self, raw_data, start):
"""
@@ -132,7 +134,7 @@ class TemporaryFileUploadHandler(FileUploadHandler):
Create the file object to append to as data is coming in.
"""
super(TemporaryFileUploadHandler, self).new_file(file_name, *args, **kwargs)
- self.file = TemporaryUploadedFile(self.file_name, self.content_type, 0, self.charset)
+ self.file = TemporaryUploadedFile(self.file_name, self.content_type, 0, self.charset, self.content_type_extra)
def receive_data_chunk(self, raw_data, start):
self.file.write(raw_data)
@@ -187,7 +189,8 @@ class MemoryFileUploadHandler(FileUploadHandler):
name = self.file_name,
content_type = self.content_type,
size = file_size,
- charset = self.charset
+ charset = self.charset,
+ content_type_extra = self.content_type_extra
)
diff --git a/django/core/mail/backends/console.py b/django/core/mail/backends/console.py
index ea0cb5d9ad..389e06b37f 100644
--- a/django/core/mail/backends/console.py
+++ b/django/core/mail/backends/console.py
@@ -16,6 +16,7 @@ class EmailBackend(BaseEmailBackend):
"""Write all messages to the stream in a thread-safe way."""
if not email_messages:
return
+ msg_count = 0
with self._lock:
try:
stream_created = self.open()
@@ -24,9 +25,10 @@ class EmailBackend(BaseEmailBackend):
self.stream.write('-' * 79)
self.stream.write('\n')
self.stream.flush() # flush after each message
+ msg_count += 1
if stream_created:
self.close()
except:
if not self.fail_silently:
raise
- return len(email_messages)
+ return msg_count
diff --git a/django/core/mail/backends/dummy.py b/django/core/mail/backends/dummy.py
index 273aa0d88e..7ae4878f13 100644
--- a/django/core/mail/backends/dummy.py
+++ b/django/core/mail/backends/dummy.py
@@ -6,4 +6,4 @@ from django.core.mail.backends.base import BaseEmailBackend
class EmailBackend(BaseEmailBackend):
def send_messages(self, email_messages):
- return len(email_messages)
+ return len(list(email_messages))
diff --git a/django/core/mail/backends/locmem.py b/django/core/mail/backends/locmem.py
index 6826d09ee5..8e893af358 100644
--- a/django/core/mail/backends/locmem.py
+++ b/django/core/mail/backends/locmem.py
@@ -20,7 +20,9 @@ class EmailBackend(BaseEmailBackend):
def send_messages(self, messages):
"""Redirect messages to the dummy outbox"""
+ msg_count = 0
for message in messages: # .message() triggers header validation
message.message()
+ msg_count += 1
mail.outbox.extend(messages)
- return len(messages)
+ return msg_count
diff --git a/django/core/mail/backends/smtp.py b/django/core/mail/backends/smtp.py
index e456b7864e..c7f4a1a801 100644
--- a/django/core/mail/backends/smtp.py
+++ b/django/core/mail/backends/smtp.py
@@ -15,22 +15,18 @@ class EmailBackend(BaseEmailBackend):
A wrapper that manages the SMTP network connection.
"""
def __init__(self, host=None, port=None, username=None, password=None,
- use_tls=None, fail_silently=False, **kwargs):
+ use_tls=None, fail_silently=False, use_ssl=None, **kwargs):
super(EmailBackend, self).__init__(fail_silently=fail_silently)
self.host = host or settings.EMAIL_HOST
self.port = port or settings.EMAIL_PORT
- if username is None:
- self.username = settings.EMAIL_HOST_USER
- else:
- self.username = username
- if password is None:
- self.password = settings.EMAIL_HOST_PASSWORD
- else:
- self.password = password
- if use_tls is None:
- self.use_tls = settings.EMAIL_USE_TLS
- else:
- self.use_tls = use_tls
+ self.username = settings.EMAIL_HOST_USER if username is None else username
+ self.password = settings.EMAIL_HOST_PASSWORD if password is None else password
+ self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls
+ self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl
+ if self.use_ssl and self.use_tls:
+ raise ValueError(
+ "EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
+ "one of those settings to True.")
self.connection = None
self._lock = threading.RLock()
@@ -45,12 +41,18 @@ class EmailBackend(BaseEmailBackend):
try:
# If local_hostname is not specified, socket.getfqdn() gets used.
# For performance, we use the cached FQDN for local_hostname.
- self.connection = smtplib.SMTP(self.host, self.port,
+ if self.use_ssl:
+ self.connection = smtplib.SMTP_SSL(self.host, self.port,
local_hostname=DNS_NAME.get_fqdn())
- if self.use_tls:
- self.connection.ehlo()
- self.connection.starttls()
- self.connection.ehlo()
+ else:
+ self.connection = smtplib.SMTP(self.host, self.port,
+ local_hostname=DNS_NAME.get_fqdn())
+ # TLS/SSL are mutually exclusive, so only attempt TLS over
+ # non-secure connections.
+ if self.use_tls:
+ self.connection.ehlo()
+ self.connection.starttls()
+ self.connection.ehlo()
if self.username and self.password:
self.connection.login(self.username, self.password)
return True
diff --git a/django/core/mail/message.py b/django/core/mail/message.py
index 98ab3c9075..db9023a0bb 100644
--- a/django/core/mail/message.py
+++ b/django/core/mail/message.py
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
import mimetypes
import os
import random
-import sys
import time
from email import charset as Charset, encoders as Encoders
from email.generator import Generator
@@ -139,9 +138,6 @@ class SafeMIMEText(MIMEText):
"""
fp = six.StringIO()
g = Generator(fp, mangle_from_ = False)
- if sys.version_info < (2, 6, 6) and isinstance(self._payload, six.text_type):
- # Workaround for http://bugs.python.org/issue1368247
- self._payload = self._payload.encode(self._charset.output_charset)
g.flatten(self, unixfrom=unixfrom)
return fp.getvalue()
diff --git a/django/core/signing.py b/django/core/signing.py
index bbe53aafe3..c3b2c3ed36 100644
--- a/django/core/signing.py
+++ b/django/core/signing.py
@@ -183,6 +183,10 @@ class TimestampSigner(Signer):
return super(TimestampSigner, self).sign(value)
def unsign(self, value, max_age=None):
+ """
+ Retrieve original value and check it wasn't signed more
+ than max_age seconds ago.
+ """
result = super(TimestampSigner, self).unsign(value)
value, timestamp = result.rsplit(self.sep, 1)
timestamp = baseconv.base62.decode(timestamp)
diff --git a/django/core/validators.py b/django/core/validators.py
index 200d28fe02..aa417ed099 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -87,7 +87,7 @@ class EmailValidator(object):
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"$)', # quoted-string
re.IGNORECASE)
domain_regex = re.compile(
- r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?$)' # domain
+ r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,})\.?$' # domain
# literal form, ipv4 address (SMTP 4.1.3)
r'|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$',
re.IGNORECASE)
diff --git a/django/db/__init__.py b/django/db/__init__.py
index 0d7fbe2a34..e1af430dc2 100644
--- a/django/db/__init__.py
+++ b/django/db/__init__.py
@@ -1,20 +1,25 @@
import warnings
from django.core import signals
-from django.db.utils import (DEFAULT_DB_ALIAS,
- DataError, OperationalError, IntegrityError, InternalError,
- ProgrammingError, NotSupportedError, DatabaseError,
- InterfaceError, Error,
- load_backend, ConnectionHandler, ConnectionRouter)
+from django.db.utils import (DEFAULT_DB_ALIAS, DataError, OperationalError,
+ IntegrityError, InternalError, ProgrammingError, NotSupportedError,
+ DatabaseError, InterfaceError, Error, load_backend,
+ ConnectionHandler, ConnectionRouter)
from django.utils.functional import cached_property
-__all__ = ('backend', 'connection', 'connections', 'router', 'DatabaseError',
- 'IntegrityError', 'DEFAULT_DB_ALIAS')
+
+__all__ = [
+ 'backend', 'connection', 'connections', 'router', 'DatabaseError',
+ 'IntegrityError', 'InternalError', 'ProgrammingError', 'DataError',
+ 'NotSupportedError', 'Error', 'InterfaceError', 'OperationalError',
+ 'DEFAULT_DB_ALIAS'
+]
connections = ConnectionHandler()
router = ConnectionRouter()
+
# `connection`, `DatabaseError` and `IntegrityError` are convenient aliases
# for backend bits.
@@ -39,8 +44,15 @@ class DefaultConnectionProxy(object):
def __delattr__(self, name):
return delattr(connections[DEFAULT_DB_ALIAS], name)
+ def __eq__(self, other):
+ return connections[DEFAULT_DB_ALIAS] == other
+
+ def __ne__(self, other):
+ return connections[DEFAULT_DB_ALIAS] != other
+
connection = DefaultConnectionProxy()
+
class DefaultBackendProxy(object):
"""
Temporary proxy class used during deprecation period of the `backend` module
@@ -63,6 +75,7 @@ class DefaultBackendProxy(object):
backend = DefaultBackendProxy()
+
def close_connection(**kwargs):
warnings.warn(
"close_connection is superseded by close_old_connections.",
@@ -76,12 +89,14 @@ def close_connection(**kwargs):
transaction.abort(conn)
connections[conn].close()
+
# Register an event to reset saved queries when a Django request is started.
def reset_queries(**kwargs):
for conn in connections.all():
conn.queries = []
signals.request_started.connect(reset_queries)
+
# Register an event to reset transaction state and close connections past
# their lifetime. NB: abort() doesn't do anything outside of a transaction.
def close_old_connections(**kwargs):
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 451cf4baf1..80e66b3ad4 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -70,7 +70,9 @@ class BaseDatabaseWrapper(object):
self._thread_ident = thread.get_ident()
def __eq__(self, other):
- return self.alias == other.alias
+ if isinstance(other, BaseDatabaseWrapper):
+ return self.alias == other.alias
+ return NotImplemented
def __ne__(self, other):
return not self == other
@@ -1184,6 +1186,7 @@ FieldInfo = namedtuple('FieldInfo',
'name type_code display_size internal_size precision scale null_ok'
)
+
class BaseDatabaseIntrospection(object):
"""
This class encapsulates all backend-specific introspection utilities
diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
index 2767fcb48d..2ebbbbf2d5 100644
--- a/django/db/backends/creation.py
+++ b/django/db/backends/creation.py
@@ -253,12 +253,13 @@ class BaseDatabaseCreation(object):
r_col = model._meta.get_field(f.rel.field_name).column
r_name = '%s_refs_%s_%s' % (
col, r_col, self._digest(table, r_table))
- output.append('%s %s %s %s;' % \
- (style.SQL_KEYWORD('ALTER TABLE'),
+ output.append('%s %s %s %s;' % (
+ style.SQL_KEYWORD('ALTER TABLE'),
style.SQL_TABLE(qn(table)),
style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()),
style.SQL_FIELD(qn(truncate_name(
- r_name, self.connection.ops.max_name_length())))))
+ r_name, self.connection.ops.max_name_length())))
+ ))
del references_to_delete[model]
return output
diff --git a/django/db/backends/dummy/base.py b/django/db/backends/dummy/base.py
index 9a220ffd8b..3e6d3e4c5a 100644
--- a/django/db/backends/dummy/base.py
+++ b/django/db/backends/dummy/base.py
@@ -8,33 +8,43 @@ ImproperlyConfigured.
"""
from django.core.exceptions import ImproperlyConfigured
-from django.db.backends import *
+from django.db.backends import (BaseDatabaseOperations, BaseDatabaseClient,
+ BaseDatabaseIntrospection, BaseDatabaseWrapper, BaseDatabaseFeatures,
+ BaseDatabaseValidation)
from django.db.backends.creation import BaseDatabaseCreation
+
def complain(*args, **kwargs):
raise ImproperlyConfigured("settings.DATABASES is improperly configured. "
"Please supply the ENGINE value. Check "
"settings documentation for more details.")
+
def ignore(*args, **kwargs):
pass
+
class DatabaseError(Exception):
pass
+
class IntegrityError(DatabaseError):
pass
+
class DatabaseOperations(BaseDatabaseOperations):
quote_name = complain
+
class DatabaseClient(BaseDatabaseClient):
runshell = complain
+
class DatabaseCreation(BaseDatabaseCreation):
create_test_db = ignore
destroy_test_db = ignore
+
class DatabaseIntrospection(BaseDatabaseIntrospection):
get_table_list = complain
get_table_description = complain
@@ -42,6 +52,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
get_indexes = complain
get_key_columns = complain
+
class DatabaseWrapper(BaseDatabaseWrapper):
operators = {}
# Override the base class implementations with null
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index 8a689f66ed..15eacec679 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -37,7 +37,8 @@ except ImportError:
from django.conf import settings
from django.db import utils
-from django.db.backends import *
+from django.db.backends import (util, BaseDatabaseFeatures,
+ BaseDatabaseOperations, BaseDatabaseWrapper)
from django.db.backends.mysql.client import DatabaseClient
from django.db.backends.mysql.creation import DatabaseCreation
from django.db.backends.mysql.introspection import DatabaseIntrospection
@@ -60,6 +61,7 @@ IntegrityError = Database.IntegrityError
# It's impossible to import datetime_or_None directly from MySQLdb.times
parse_datetime = conversions[FIELD_TYPE.DATETIME]
+
def parse_datetime_with_timezone_support(value):
dt = parse_datetime(value)
# Confirm that dt is naive before overwriting its tzinfo.
@@ -67,6 +69,7 @@ def parse_datetime_with_timezone_support(value):
dt = dt.replace(tzinfo=timezone.utc)
return dt
+
def adapt_datetime_with_timezone_support(value, conv):
# Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL.
if settings.USE_TZ:
@@ -101,6 +104,7 @@ django_conversions.update({
# http://dev.mysql.com/doc/refman/5.0/en/news.html .
server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
+
# MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
# MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
# point is to raise Warnings as exceptions, this can be done with the Python
@@ -151,6 +155,7 @@ class CursorWrapper(object):
def __iter__(self):
return iter(self.cursor)
+
class DatabaseFeatures(BaseDatabaseFeatures):
empty_fetchmany_value = ()
update_can_self_select = False
@@ -208,6 +213,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
cursor.execute("SELECT 1 FROM mysql.time_zone LIMIT 1")
return cursor.fetchone() is not None
+
class DatabaseOperations(BaseDatabaseOperations):
compiler_module = "django.db.backends.mysql.compiler"
@@ -323,7 +329,7 @@ class DatabaseOperations(BaseDatabaseOperations):
# Truncate already resets the AUTO_INCREMENT field from
# MySQL version 5.0.13 onwards. Refs #16961.
if self.connection.mysql_version < (5, 0, 13):
- return ["%s %s %s %s %s;" % \
+ return ["%s %s %s %s %s;" %
(style.SQL_KEYWORD('ALTER'),
style.SQL_KEYWORD('TABLE'),
style.SQL_TABLE(self.quote_name(sequence['table'])),
@@ -377,6 +383,7 @@ class DatabaseOperations(BaseDatabaseOperations):
items_sql = "(%s)" % ", ".join(["%s"] * len(fields))
return "VALUES " + ", ".join([items_sql] * num_values)
+
class DatabaseWrapper(BaseDatabaseWrapper):
vendor = 'mysql'
operators = {
diff --git a/django/db/backends/mysql/client.py b/django/db/backends/mysql/client.py
index 1cf8ceef9c..8364c7b6e6 100644
--- a/django/db/backends/mysql/client.py
+++ b/django/db/backends/mysql/client.py
@@ -3,6 +3,7 @@ import sys
from django.db.backends import BaseDatabaseClient
+
class DatabaseClient(BaseDatabaseClient):
executable_name = 'mysql'
@@ -37,4 +38,3 @@ class DatabaseClient(BaseDatabaseClient):
sys.exit(os.system(" ".join(args)))
else:
os.execvp(self.executable_name, args)
-
diff --git a/django/db/backends/mysql/compiler.py b/django/db/backends/mysql/compiler.py
index 4e033e3d93..b7d1d7b98d 100644
--- a/django/db/backends/mysql/compiler.py
+++ b/django/db/backends/mysql/compiler.py
@@ -22,20 +22,26 @@ class SQLCompiler(compiler.SQLCompiler):
sql, params = self.as_sql()
return '(%s) IN (%s)' % (', '.join(['%s.%s' % (qn(alias), qn2(column)) for column in columns]), sql), params
+
class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler):
pass
+
class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
pass
+
class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler):
pass
+
class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler):
pass
+
class SQLDateCompiler(compiler.SQLDateCompiler, SQLCompiler):
pass
+
class SQLDateTimeCompiler(compiler.SQLDateTimeCompiler, SQLCompiler):
pass
diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py
index 3a57c29479..b59b225f4f 100644
--- a/django/db/backends/mysql/creation.py
+++ b/django/db/backends/mysql/creation.py
@@ -1,34 +1,35 @@
from django.db.backends.creation import BaseDatabaseCreation
+
class DatabaseCreation(BaseDatabaseCreation):
# This dictionary maps Field objects to their associated MySQL column
# types, as strings. Column-type strings can contain format strings; they'll
# be interpolated against the values of Field.__dict__ before being output.
# If a column type is set to None, it won't be included in the output.
data_types = {
- 'AutoField': 'integer AUTO_INCREMENT',
- 'BinaryField': 'longblob',
- 'BooleanField': 'bool',
- 'CharField': 'varchar(%(max_length)s)',
+ 'AutoField': 'integer AUTO_INCREMENT',
+ 'BinaryField': 'longblob',
+ 'BooleanField': 'bool',
+ 'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
- 'DateField': 'date',
- 'DateTimeField': 'datetime',
- 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
- 'FileField': 'varchar(%(max_length)s)',
- 'FilePathField': 'varchar(%(max_length)s)',
- 'FloatField': 'double precision',
- 'IntegerField': 'integer',
- 'BigIntegerField': 'bigint',
- 'IPAddressField': 'char(15)',
+ 'DateField': 'date',
+ 'DateTimeField': 'datetime',
+ 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'FileField': 'varchar(%(max_length)s)',
+ 'FilePathField': 'varchar(%(max_length)s)',
+ 'FloatField': 'double precision',
+ 'IntegerField': 'integer',
+ 'BigIntegerField': 'bigint',
+ 'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
- 'NullBooleanField': 'bool',
- 'OneToOneField': 'integer',
+ 'NullBooleanField': 'bool',
+ 'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
- 'SlugField': 'varchar(%(max_length)s)',
+ 'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
- 'TextField': 'longtext',
- 'TimeField': 'time',
+ 'TextField': 'longtext',
+ 'TimeField': 'time',
}
def sql_table_creation_suffix(self):
diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
index 00c567a90b..e2d382b895 100644
--- a/django/db/backends/mysql/introspection.py
+++ b/django/db/backends/mysql/introspection.py
@@ -7,6 +7,7 @@ from django.utils.encoding import force_text
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
+
class DatabaseIntrospection(BaseDatabaseIntrospection):
data_types_reverse = {
FIELD_TYPE.BLOB: 'TextField',
diff --git a/django/db/backends/mysql/validation.py b/django/db/backends/mysql/validation.py
index 2ce957cce7..17b7cde756 100644
--- a/django/db/backends/mysql/validation.py
+++ b/django/db/backends/mysql/validation.py
@@ -1,5 +1,6 @@
from django.db.backends import BaseDatabaseValidation
+
class DatabaseValidation(BaseDatabaseValidation):
def validate_field(self, errors, opts, f):
"""
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 5fb806807d..63d38bdafd 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -7,11 +7,12 @@ from __future__ import unicode_literals
import decimal
import re
+import platform
import sys
import warnings
+
def _setup_environment(environ):
- import platform
# Cygwin requires some special voodoo to set the environment variables
# properly so that Oracle will see them.
if platform.system().upper().startswith('CYGWIN'):
@@ -91,6 +92,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_tablespaces = True
supports_sequence_reset = False
+
class DatabaseOperations(BaseDatabaseOperations):
compiler_module = "django.db.backends.oracle.compiler"
@@ -268,7 +270,7 @@ WHEN (new.%(col_name)s IS NULL)
# http://cx-oracle.sourceforge.net/html/cursor.html#Cursor.statement
# The DB API definition does not define this attribute.
statement = cursor.statement
- if not six.PY3 and not isinstance(statement, unicode):
+ if statement and not six.PY3 and not isinstance(statement, unicode):
statement = statement.decode('utf-8')
# Unlike Psycopg's `query` and MySQLdb`'s `_last_executed`, CxOracle's
# `statement` doesn't contain the query parameters. refs #20010.
@@ -309,7 +311,7 @@ WHEN (new.%(col_name)s IS NULL)
# Oracle puts the query text into a (query % args) construct, so % signs
# in names need to be escaped. The '%%' will be collapsed back to '%' at
# that stage so we aren't really making the name longer here.
- name = name.replace('%','%%')
+ name = name.replace('%', '%%')
return name.upper()
def random_function_sql(self):
@@ -763,7 +765,7 @@ class FormatStylePlaceholderCursor(object):
def _format_params(self, params):
try:
- return dict((k,OracleParam(v, self, True)) for k,v in params.items())
+ return dict((k, OracleParam(v, self, True)) for k, v in params.items())
except AttributeError:
return tuple([OracleParam(p, self, True) for p in params])
@@ -783,12 +785,12 @@ class FormatStylePlaceholderCursor(object):
for i, value in enumerate(params):
if value.input_size:
sizes[i] = value.input_size
- self.setinputsizes(*sizes)
+ self.setinputsizes(*sizes)
def _param_generator(self, params):
# Try dict handling; if that fails, treat as sequence
if hasattr(params, 'items'):
- return dict((k, v.force_bytes) for k,v in params.items())
+ return dict((k, v.force_bytes) for k, v in params.items())
else:
return [p.force_bytes for p in params]
@@ -804,14 +806,14 @@ class FormatStylePlaceholderCursor(object):
query = convert_unicode(query, self.charset)
elif hasattr(params, 'keys'):
# Handle params as dict
- args = dict((k, ":%s"%k) for k in params.keys())
+ args = dict((k, ":%s" % k) for k in params.keys())
query = convert_unicode(query % args, self.charset)
else:
# Handle params as sequence
args = [(':arg%d' % i) for i in range(len(params))]
query = convert_unicode(query % tuple(args), self.charset)
return query, self._format_params(params)
-
+
def execute(self, query, params=None):
query, params = self._fix_for_params(query, params)
self._guess_input_sizes([params])
@@ -830,9 +832,9 @@ class FormatStylePlaceholderCursor(object):
# uniform treatment for sequences and iterables
params_iter = iter(params)
query, firstparams = self._fix_for_params(query, next(params_iter))
- # we build a list of formatted params; as we're going to traverse it
+ # we build a list of formatted params; as we're going to traverse it
# more than once, we can't make it lazy by using a generator
- formatted = [firstparams]+[self._format_params(p) for p in params_iter]
+ formatted = [firstparams] + [self._format_params(p) for p in params_iter]
self._guess_input_sizes(formatted)
try:
return self.cursor.executemany(query,
diff --git a/django/db/backends/oracle/client.py b/django/db/backends/oracle/client.py
index ccc64ebffc..ac6b79041f 100644
--- a/django/db/backends/oracle/client.py
+++ b/django/db/backends/oracle/client.py
@@ -3,6 +3,7 @@ import sys
from django.db.backends import BaseDatabaseClient
+
class DatabaseClient(BaseDatabaseClient):
executable_name = 'sqlplus'
@@ -13,4 +14,3 @@ class DatabaseClient(BaseDatabaseClient):
sys.exit(os.system(" ".join(args)))
else:
os.execvp(self.executable_name, args)
-
diff --git a/django/db/backends/oracle/compiler.py b/django/db/backends/oracle/compiler.py
index cbee27951c..d2d4cd3ac9 100644
--- a/django/db/backends/oracle/compiler.py
+++ b/django/db/backends/oracle/compiler.py
@@ -60,17 +60,22 @@ class SQLCompiler(compiler.SQLCompiler):
class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler):
pass
+
class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
pass
+
class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler):
pass
+
class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler):
pass
+
class SQLDateCompiler(compiler.SQLDateCompiler, SQLCompiler):
pass
+
class SQLDateTimeCompiler(compiler.SQLDateTimeCompiler, SQLCompiler):
pass
diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py
index 7f1192ed8a..f7d43ae7b6 100644
--- a/django/db/backends/oracle/creation.py
+++ b/django/db/backends/oracle/creation.py
@@ -5,9 +5,11 @@ from django.conf import settings
from django.db.backends.creation import BaseDatabaseCreation
from django.utils.six.moves import input
+
TEST_DATABASE_PREFIX = 'test_'
PASSWORD = 'Im_a_lumberjack'
+
class DatabaseCreation(BaseDatabaseCreation):
# This dictionary maps Field objects to their associated Oracle column
# types, as strings. Column-type strings can contain format strings; they'll
@@ -18,30 +20,30 @@ class DatabaseCreation(BaseDatabaseCreation):
# output (the "qn_" prefix is stripped before the lookup is performed.
data_types = {
- 'AutoField': 'NUMBER(11)',
- 'BinaryField': 'BLOB',
- 'BooleanField': 'NUMBER(1)',
- 'CharField': 'NVARCHAR2(%(max_length)s)',
- 'CommaSeparatedIntegerField': 'VARCHAR2(%(max_length)s)',
- 'DateField': 'DATE',
- 'DateTimeField': 'TIMESTAMP',
- 'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)',
- 'FileField': 'NVARCHAR2(%(max_length)s)',
- 'FilePathField': 'NVARCHAR2(%(max_length)s)',
- 'FloatField': 'DOUBLE PRECISION',
- 'IntegerField': 'NUMBER(11)',
- 'BigIntegerField': 'NUMBER(19)',
- 'IPAddressField': 'VARCHAR2(15)',
- 'GenericIPAddressField': 'VARCHAR2(39)',
- 'NullBooleanField': 'NUMBER(1)',
- 'OneToOneField': 'NUMBER(11)',
- 'PositiveIntegerField': 'NUMBER(11)',
- 'PositiveSmallIntegerField': 'NUMBER(11)',
- 'SlugField': 'NVARCHAR2(%(max_length)s)',
- 'SmallIntegerField': 'NUMBER(11)',
- 'TextField': 'NCLOB',
- 'TimeField': 'TIMESTAMP',
- 'URLField': 'VARCHAR2(%(max_length)s)',
+ 'AutoField': 'NUMBER(11)',
+ 'BinaryField': 'BLOB',
+ 'BooleanField': 'NUMBER(1)',
+ 'CharField': 'NVARCHAR2(%(max_length)s)',
+ 'CommaSeparatedIntegerField': 'VARCHAR2(%(max_length)s)',
+ 'DateField': 'DATE',
+ 'DateTimeField': 'TIMESTAMP',
+ 'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)',
+ 'FileField': 'NVARCHAR2(%(max_length)s)',
+ 'FilePathField': 'NVARCHAR2(%(max_length)s)',
+ 'FloatField': 'DOUBLE PRECISION',
+ 'IntegerField': 'NUMBER(11)',
+ 'BigIntegerField': 'NUMBER(19)',
+ 'IPAddressField': 'VARCHAR2(15)',
+ 'GenericIPAddressField': 'VARCHAR2(39)',
+ 'NullBooleanField': 'NUMBER(1)',
+ 'OneToOneField': 'NUMBER(11)',
+ 'PositiveIntegerField': 'NUMBER(11)',
+ 'PositiveSmallIntegerField': 'NUMBER(11)',
+ 'SlugField': 'NVARCHAR2(%(max_length)s)',
+ 'SmallIntegerField': 'NUMBER(11)',
+ 'TextField': 'NCLOB',
+ 'TimeField': 'TIMESTAMP',
+ 'URLField': 'VARCHAR2(%(max_length)s)',
}
data_type_check_constraints = {
@@ -190,7 +192,7 @@ class DatabaseCreation(BaseDatabaseCreation):
statements = [
'DROP TABLESPACE %(tblspace)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
'DROP TABLESPACE %(tblspace_temp)s INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS',
- ]
+ ]
self._execute_statements(cursor, statements, parameters, verbosity)
def _destroy_test_user(self, cursor, parameters, verbosity):
diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py
index 361308a62c..a2fad92509 100644
--- a/django/db/backends/oracle/introspection.py
+++ b/django/db/backends/oracle/introspection.py
@@ -1,10 +1,13 @@
+import re
+
+import cx_Oracle
+
from django.db.backends import BaseDatabaseIntrospection, FieldInfo
from django.utils.encoding import force_text
-import cx_Oracle
-import re
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
+
class DatabaseIntrospection(BaseDatabaseIntrospection):
# Maps type objects to Django Field types.
data_types_reverse = {
@@ -95,11 +98,11 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
SELECT ccol.column_name, rcol.table_name AS referenced_table, rcol.column_name AS referenced_column
FROM user_constraints c
JOIN user_cons_columns ccol
- ON ccol.constraint_name = c.constraint_name
+ ON ccol.constraint_name = c.constraint_name
JOIN user_cons_columns rcol
- ON rcol.constraint_name = c.r_constraint_name
- WHERE c.table_name = %s AND c.constraint_type = 'R'""" , [table_name.upper()])
- return [tuple(cell.lower() for cell in row)
+ ON rcol.constraint_name = c.r_constraint_name
+ WHERE c.table_name = %s AND c.constraint_type = 'R'""", [table_name.upper()])
+ return [tuple(cell.lower() for cell in row)
for row in cursor.fetchall()]
def get_indexes(self, cursor, table_name):
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
index bf75459b4a..f283476429 100644
--- a/django/db/backends/postgresql_psycopg2/base.py
+++ b/django/db/backends/postgresql_psycopg2/base.py
@@ -6,7 +6,9 @@ Requires psycopg 2: http://initd.org/projects/psycopg2
import logging
import sys
-from django.db.backends import *
+from django.conf import settings
+from django.db.backends import (BaseDatabaseFeatures, BaseDatabaseWrapper,
+ BaseDatabaseValidation)
from django.db.backends.postgresql_psycopg2.operations import DatabaseOperations
from django.db.backends.postgresql_psycopg2.client import DatabaseClient
from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
@@ -34,11 +36,13 @@ psycopg2.extensions.register_adapter(SafeText, psycopg2.extensions.QuotedString)
logger = logging.getLogger('django.db.backends')
+
def utc_tzinfo_factory(offset):
if offset != 0:
raise AssertionError("database connection isn't set to UTC")
return utc
+
class DatabaseFeatures(BaseDatabaseFeatures):
needs_datetime_string_cast = False
can_return_id_from_insert = True
@@ -55,6 +59,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
can_rollback_ddl = True
supports_combined_alters = True
+
class DatabaseWrapper(BaseDatabaseWrapper):
vendor = 'postgresql'
operators = {
@@ -135,7 +140,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# Set the time zone in autocommit mode (see #17062)
self.set_autocommit(True)
self.connection.cursor().execute(
- self.ops.set_time_zone_sql(), [tz])
+ self.ops.set_time_zone_sql(), [tz]
+ )
self.connection.set_isolation_level(self.isolation_level)
def create_cursor(self):
diff --git a/django/db/backends/postgresql_psycopg2/client.py b/django/db/backends/postgresql_psycopg2/client.py
index a5c02969ea..23ac9f2975 100644
--- a/django/db/backends/postgresql_psycopg2/client.py
+++ b/django/db/backends/postgresql_psycopg2/client.py
@@ -3,6 +3,7 @@ import sys
from django.db.backends import BaseDatabaseClient
+
class DatabaseClient(BaseDatabaseClient):
executable_name = 'psql'
@@ -20,4 +21,3 @@ class DatabaseClient(BaseDatabaseClient):
sys.exit(os.system(" ".join(args)))
else:
os.execvp(self.executable_name, args)
-
diff --git a/django/db/backends/postgresql_psycopg2/creation.py b/django/db/backends/postgresql_psycopg2/creation.py
index 1bfda6354d..954e198764 100644
--- a/django/db/backends/postgresql_psycopg2/creation.py
+++ b/django/db/backends/postgresql_psycopg2/creation.py
@@ -8,29 +8,29 @@ class DatabaseCreation(BaseDatabaseCreation):
# be interpolated against the values of Field.__dict__ before being output.
# If a column type is set to None, it won't be included in the output.
data_types = {
- 'AutoField': 'serial',
- 'BinaryField': 'bytea',
- 'BooleanField': 'boolean',
- 'CharField': 'varchar(%(max_length)s)',
+ 'AutoField': 'serial',
+ 'BinaryField': 'bytea',
+ 'BooleanField': 'boolean',
+ 'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
- 'DateField': 'date',
- 'DateTimeField': 'timestamp with time zone',
- 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
- 'FileField': 'varchar(%(max_length)s)',
- 'FilePathField': 'varchar(%(max_length)s)',
- 'FloatField': 'double precision',
- 'IntegerField': 'integer',
- 'BigIntegerField': 'bigint',
- 'IPAddressField': 'inet',
+ 'DateField': 'date',
+ 'DateTimeField': 'timestamp with time zone',
+ 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'FileField': 'varchar(%(max_length)s)',
+ 'FilePathField': 'varchar(%(max_length)s)',
+ 'FloatField': 'double precision',
+ 'IntegerField': 'integer',
+ 'BigIntegerField': 'bigint',
+ 'IPAddressField': 'inet',
'GenericIPAddressField': 'inet',
- 'NullBooleanField': 'boolean',
- 'OneToOneField': 'integer',
+ 'NullBooleanField': 'boolean',
+ 'OneToOneField': 'integer',
'PositiveIntegerField': 'integer',
'PositiveSmallIntegerField': 'smallint',
- 'SlugField': 'varchar(%(max_length)s)',
+ 'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
- 'TextField': 'text',
- 'TimeField': 'time',
+ 'TextField': 'text',
+ 'TimeField': 'time',
}
data_type_check_constraints = {
@@ -59,7 +59,7 @@ class DatabaseCreation(BaseDatabaseCreation):
def get_index_sql(index_name, opclass=''):
return (style.SQL_KEYWORD('CREATE INDEX') + ' ' +
- style.SQL_TABLE(qn(truncate_name(index_name,self.connection.ops.max_name_length()))) + ' ' +
+ style.SQL_TABLE(qn(truncate_name(index_name, self.connection.ops.max_name_length()))) + ' ' +
style.SQL_KEYWORD('ON') + ' ' +
style.SQL_TABLE(qn(db_table)) + ' ' +
"(%s%s)" % (style.SQL_FIELD(qn(f.column)), opclass) +
diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py
index 4f77059376..a816bb34d8 100644
--- a/django/db/backends/postgresql_psycopg2/introspection.py
+++ b/django/db/backends/postgresql_psycopg2/introspection.py
@@ -25,7 +25,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
1266: 'TimeField',
1700: 'DecimalField',
}
-
+
def get_table_list(self, cursor):
"Returns a list of table names in the current database."
cursor.execute("""
@@ -47,7 +47,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
WHERE table_name = %s""", [table_name])
null_map = dict(cursor.fetchall())
cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
- return [FieldInfo(*((force_text(line[0]),) + line[1:6] + (null_map[force_text(line[0])]=='YES',)))
+ return [FieldInfo(*((force_text(line[0]),) + line[1:6] + (null_map[force_text(line[0])] == 'YES',)))
for line in cursor.description]
def get_relations(self, cursor, table_name):
@@ -81,7 +81,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
ON ccu.constraint_catalog = tc.constraint_catalog
AND ccu.constraint_schema = tc.constraint_schema
AND ccu.constraint_name = tc.constraint_name
- WHERE kcu.table_name = %s AND tc.constraint_type = 'FOREIGN KEY'""" , [table_name])
+ WHERE kcu.table_name = %s AND tc.constraint_type = 'FOREIGN KEY'""", [table_name])
key_columns.extend(cursor.fetchall())
return key_columns
diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py
index c5aab84693..cc78ffe449 100644
--- a/django/db/backends/postgresql_psycopg2/operations.py
+++ b/django/db/backends/postgresql_psycopg2/operations.py
@@ -135,7 +135,7 @@ class DatabaseOperations(BaseDatabaseOperations):
# This will be the case if it's an m2m using an autogenerated
# intermediate table (see BaseDatabaseIntrospection.sequence_list)
column_name = 'id'
- sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" % \
+ sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" %
(style.SQL_KEYWORD('SELECT'),
style.SQL_TABLE(self.quote_name(table_name)),
style.SQL_FIELD(column_name))
@@ -161,7 +161,7 @@ class DatabaseOperations(BaseDatabaseOperations):
for f in model._meta.local_fields:
if isinstance(f, models.AutoField):
- output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
+ output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" %
(style.SQL_KEYWORD('SELECT'),
style.SQL_TABLE(qn(model._meta.db_table)),
style.SQL_FIELD(f.column),
@@ -173,7 +173,7 @@ class DatabaseOperations(BaseDatabaseOperations):
break # Only one AutoField is allowed per model, so don't bother continuing.
for f in model._meta.many_to_many:
if not f.rel.through:
- output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
+ output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" %
(style.SQL_KEYWORD('SELECT'),
style.SQL_TABLE(qn(f.m2m_db_table())),
style.SQL_FIELD('id'),
diff --git a/django/db/backends/postgresql_psycopg2/version.py b/django/db/backends/postgresql_psycopg2/version.py
index 8ef516704e..dae94f2dac 100644
--- a/django/db/backends/postgresql_psycopg2/version.py
+++ b/django/db/backends/postgresql_psycopg2/version.py
@@ -19,7 +19,8 @@ def _parse_version(text):
try:
return int(major) * 10000 + int(major2) * 100 + int(minor)
except (ValueError, TypeError):
- return int(major) * 10000 + int(major2) * 100
+ return int(major) * 10000 + int(major2) * 100
+
def get_version(connection):
"""
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index e330082108..960b6def03 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -11,8 +11,10 @@ import decimal
import warnings
import re
+from django.conf import settings
from django.db import utils
-from django.db.backends import *
+from django.db.backends import (util, BaseDatabaseFeatures,
+ BaseDatabaseOperations, BaseDatabaseWrapper, BaseDatabaseValidation)
from django.db.backends.sqlite3.client import DatabaseClient
from django.db.backends.sqlite3.creation import DatabaseCreation
from django.db.backends.sqlite3.introspection import DatabaseIntrospection
@@ -43,6 +45,7 @@ except ImportError:
DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError
+
def parse_datetime_with_timezone_support(value):
dt = parse_datetime(value)
# Confirm that dt is naive before overwriting its tzinfo.
@@ -50,6 +53,7 @@ def parse_datetime_with_timezone_support(value):
dt = dt.replace(tzinfo=timezone.utc)
return dt
+
def adapt_datetime_with_timezone_support(value):
# Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL.
if settings.USE_TZ:
@@ -62,6 +66,7 @@ def adapt_datetime_with_timezone_support(value):
value = value.astimezone(timezone.utc).replace(tzinfo=None)
return value.isoformat(str(" "))
+
def decoder(conv_func):
""" The Python sqlite3 interface returns always byte strings.
This function converts the received value to a regular string before
@@ -82,6 +87,7 @@ Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal)
Database.register_adapter(str, lambda s: s.decode('utf-8'))
Database.register_adapter(SafeBytes, lambda s: s.decode('utf-8'))
+
class DatabaseFeatures(BaseDatabaseFeatures):
# SQLite cannot handle us only partially reading from a cursor's result set
# and then writing the same rows to the database in another cursor. This
@@ -127,6 +133,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
def has_zoneinfo_database(self):
return pytz is not None
+
class DatabaseOperations(BaseDatabaseOperations):
def bulk_batch_size(self, fields, objs):
"""
@@ -275,6 +282,7 @@ class DatabaseOperations(BaseDatabaseOperations):
res.extend(["UNION ALL SELECT %s" % ", ".join(["%s"] * len(fields))] * (num_values - 1))
return " ".join(res)
+
class DatabaseWrapper(BaseDatabaseWrapper):
vendor = 'sqlite'
# SQLite requires LIKE statements to include an ESCAPE clause if the value
@@ -433,6 +441,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
FORMAT_QMARK_REGEX = re.compile(r'(?<!%)%s')
+
class SQLiteCursorWrapper(Database.Cursor):
"""
Django uses "format" style placeholders, but pysqlite2 uses "qmark" style.
@@ -452,6 +461,7 @@ class SQLiteCursorWrapper(Database.Cursor):
def convert_query(self, query):
return FORMAT_QMARK_REGEX.sub('?', query).replace('%%', '%')
+
def _sqlite_date_extract(lookup_type, dt):
if dt is None:
return None
@@ -464,6 +474,7 @@ def _sqlite_date_extract(lookup_type, dt):
else:
return getattr(dt, lookup_type)
+
def _sqlite_date_trunc(lookup_type, dt):
try:
dt = util.typecast_timestamp(dt)
@@ -476,6 +487,7 @@ def _sqlite_date_trunc(lookup_type, dt):
elif lookup_type == 'day':
return "%i-%02i-%02i" % (dt.year, dt.month, dt.day)
+
def _sqlite_datetime_extract(lookup_type, dt, tzname):
if dt is None:
return None
@@ -490,6 +502,7 @@ def _sqlite_datetime_extract(lookup_type, dt, tzname):
else:
return getattr(dt, lookup_type)
+
def _sqlite_datetime_trunc(lookup_type, dt, tzname):
try:
dt = util.typecast_timestamp(dt)
@@ -510,6 +523,7 @@ def _sqlite_datetime_trunc(lookup_type, dt, tzname):
elif lookup_type == 'second':
return "%i-%02i-%02i %02i:%02i:%02i" % (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
+
def _sqlite_format_dtdelta(dt, conn, days, secs, usecs):
try:
dt = util.typecast_timestamp(dt)
@@ -524,5 +538,6 @@ def _sqlite_format_dtdelta(dt, conn, days, secs, usecs):
# It will be formatted as "%Y-%m-%d" or "%Y-%m-%d %H:%M:%S[.%f]"
return str(dt)
+
def _sqlite_regexp(re_pattern, re_string):
return bool(re.search(re_pattern, force_text(re_string))) if re_string is not None else False
diff --git a/django/db/backends/sqlite3/client.py b/django/db/backends/sqlite3/client.py
index 5b5b7326f2..6a3ad9e76f 100644
--- a/django/db/backends/sqlite3/client.py
+++ b/django/db/backends/sqlite3/client.py
@@ -3,6 +3,7 @@ import sys
from django.db.backends import BaseDatabaseClient
+
class DatabaseClient(BaseDatabaseClient):
executable_name = 'sqlite3'
@@ -13,4 +14,3 @@ class DatabaseClient(BaseDatabaseClient):
sys.exit(os.system(" ".join(args)))
else:
os.execvp(self.executable_name, args)
-
diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py
index a9fb273f7a..435cee3436 100644
--- a/django/db/backends/sqlite3/creation.py
+++ b/django/db/backends/sqlite3/creation.py
@@ -1,36 +1,38 @@
import os
import sys
+
from django.db.backends.creation import BaseDatabaseCreation
from django.utils.six.moves import input
+
class DatabaseCreation(BaseDatabaseCreation):
# SQLite doesn't actually support most of these types, but it "does the right
# thing" given more verbose field definitions, so leave them as is so that
# schema inspection is more useful.
data_types = {
- 'AutoField': 'integer',
- 'BinaryField': 'BLOB',
- 'BooleanField': 'bool',
- 'CharField': 'varchar(%(max_length)s)',
- 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
- 'DateField': 'date',
- 'DateTimeField': 'datetime',
- 'DecimalField': 'decimal',
- 'FileField': 'varchar(%(max_length)s)',
- 'FilePathField': 'varchar(%(max_length)s)',
- 'FloatField': 'real',
- 'IntegerField': 'integer',
- 'BigIntegerField': 'bigint',
- 'IPAddressField': 'char(15)',
- 'GenericIPAddressField': 'char(39)',
- 'NullBooleanField': 'bool',
- 'OneToOneField': 'integer',
- 'PositiveIntegerField': 'integer unsigned',
- 'PositiveSmallIntegerField': 'smallint unsigned',
- 'SlugField': 'varchar(%(max_length)s)',
- 'SmallIntegerField': 'smallint',
- 'TextField': 'text',
- 'TimeField': 'time',
+ 'AutoField': 'integer',
+ 'BinaryField': 'BLOB',
+ 'BooleanField': 'bool',
+ 'CharField': 'varchar(%(max_length)s)',
+ 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
+ 'DateField': 'date',
+ 'DateTimeField': 'datetime',
+ 'DecimalField': 'decimal',
+ 'FileField': 'varchar(%(max_length)s)',
+ 'FilePathField': 'varchar(%(max_length)s)',
+ 'FloatField': 'real',
+ 'IntegerField': 'integer',
+ 'BigIntegerField': 'bigint',
+ 'IPAddressField': 'char(15)',
+ 'GenericIPAddressField': 'char(39)',
+ 'NullBooleanField': 'bool',
+ 'OneToOneField': 'integer',
+ 'PositiveIntegerField': 'integer unsigned',
+ 'PositiveSmallIntegerField': 'smallint unsigned',
+ 'SlugField': 'varchar(%(max_length)s)',
+ 'SmallIntegerField': 'smallint',
+ 'TextField': 'text',
+ 'TimeField': 'time',
}
def sql_for_pending_references(self, model, style, pending_references):
@@ -80,7 +82,6 @@ class DatabaseCreation(BaseDatabaseCreation):
SQLite since the databases will be distinct despite having the same
TEST_NAME. See http://www.sqlite.org/inmemorydb.html
"""
- settings_dict = self.connection.settings_dict
test_dbname = self._get_test_db_name()
sig = [self.connection.settings_dict['NAME']]
if test_dbname == ':memory:':
diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py
index 799d6ec8fd..2e674bc05b 100644
--- a/django/db/backends/sqlite3/introspection.py
+++ b/django/db/backends/sqlite3/introspection.py
@@ -1,8 +1,11 @@
import re
+
from django.db.backends import BaseDatabaseIntrospection, FieldInfo
+
field_size_re = re.compile(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$')
+
def get_field_size(name):
""" Extract the size number from a "varchar(11)" type name """
m = field_size_re.search(name)
@@ -46,6 +49,7 @@ class FlexibleFieldLookupDict(object):
return ('CharField', {'max_length': size})
raise KeyError
+
class DatabaseIntrospection(BaseDatabaseIntrospection):
data_types_reverse = FlexibleFieldLookupDict()
@@ -76,7 +80,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
# Schema for this table
cursor.execute("SELECT sql FROM sqlite_master WHERE tbl_name = %s AND type = %s", [table_name, "table"])
results = cursor.fetchone()[0].strip()
- results = results[results.index('(')+1:results.rindex(')')]
+ results = results[results.index('(') + 1:results.rindex(')')]
# Walk through and look for references to other tables. SQLite doesn't
# really have enforced references, but since it echoes out the SQL used
@@ -96,8 +100,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
result = cursor.fetchall()[0]
other_table_results = result[0].strip()
li, ri = other_table_results.index('('), other_table_results.rindex(')')
- other_table_results = other_table_results[li+1:ri]
-
+ other_table_results = other_table_results[li + 1:ri]
for other_index, other_desc in enumerate(other_table_results.split(',')):
other_desc = other_desc.strip()
@@ -121,7 +124,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
# Schema for this table
cursor.execute("SELECT sql FROM sqlite_master WHERE tbl_name = %s AND type = %s", [table_name, "table"])
results = cursor.fetchone()[0].strip()
- results = results[results.index('(')+1:results.rindex(')')]
+ results = results[results.index('(') + 1:results.rindex(')')]
# Walk through and look for references to other tables. SQLite doesn't
# really have enforced references, but since it echoes out the SQL used
@@ -166,7 +169,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
# Don't use PRAGMA because that causes issues with some transactions
cursor.execute("SELECT sql FROM sqlite_master WHERE tbl_name = %s AND type = %s", [table_name, "table"])
results = cursor.fetchone()[0].strip()
- results = results[results.index('(')+1:results.rindex(')')]
+ results = results[results.index('(') + 1:results.rindex(')')]
for field_desc in results.split(','):
field_desc = field_desc.strip()
m = re.search('"(.*)".*PRIMARY KEY$', field_desc)
diff --git a/django/db/backends/util.py b/django/db/backends/util.py
index aa2601277a..f8157c50e7 100644
--- a/django/db/backends/util.py
+++ b/django/db/backends/util.py
@@ -85,20 +85,25 @@ class CursorDebugWrapper(CursorWrapper):
def typecast_date(s):
return datetime.date(*map(int, s.split('-'))) if s else None # returns None if s is null
+
def typecast_time(s): # does NOT store time zone information
- if not s: return None
+ if not s:
+ return None
hour, minutes, seconds = s.split(':')
if '.' in seconds: # check whether seconds have a fractional part
seconds, microseconds = seconds.split('.')
else:
microseconds = '0'
- return datetime.time(int(hour), int(minutes), int(seconds), int(float('.'+microseconds) * 1000000))
+ return datetime.time(int(hour), int(minutes), int(seconds), int(float('.' + microseconds) * 1000000))
+
def typecast_timestamp(s): # does NOT store time zone information
# "2005-07-29 15:48:00.590358-05"
# "2005-07-29 09:56:00-05"
- if not s: return None
- if not ' ' in s: return typecast_date(s)
+ if not s:
+ return None
+ if not ' ' in s:
+ return typecast_date(s)
d, t = s.split()
# Extract timezone information, if it exists. Currently we just throw
# it away, but in the future we may make use of it.
@@ -122,11 +127,13 @@ def typecast_timestamp(s): # does NOT store time zone information
int(times[0]), int(times[1]), int(seconds),
int((microseconds + '000000')[:6]), tzinfo)
+
def typecast_decimal(s):
if s is None or s == '':
return None
return decimal.Decimal(s)
+
###############################################
# Converters from Python to database (string) #
###############################################
@@ -136,6 +143,7 @@ def rev_typecast_decimal(d):
return None
return str(d)
+
def truncate_name(name, length=None, hash_len=4):
"""Shortens a string to a repeatable mangled version with the given length.
"""
@@ -143,7 +151,8 @@ def truncate_name(name, length=None, hash_len=4):
return name
hsh = hashlib.md5(force_bytes(name)).hexdigest()[:hash_len]
- return '%s%s' % (name[:length-hash_len], hsh)
+ return '%s%s' % (name[:length - hash_len], hsh)
+
def format_number(value, max_digits, decimal_places):
"""
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
index b5dd1a58bc..4d310e480b 100644
--- a/django/db/models/__init__.py
+++ b/django/db/models/__init__.py
@@ -26,6 +26,7 @@ def permalink(func):
(viewname, viewargs, viewkwargs)
"""
from django.core.urlresolvers import reverse
+
@wraps(func)
def inner(*args, **kwargs):
bits = func(*args, **kwargs)
diff --git a/django/db/models/aggregates.py b/django/db/models/aggregates.py
index b89db1c563..1db3890204 100644
--- a/django/db/models/aggregates.py
+++ b/django/db/models/aggregates.py
@@ -3,6 +3,7 @@ Classes to represent the definitions of aggregate functions.
"""
from django.db.models.constants import LOOKUP_SEP
+
def refs_aggregate(lookup_parts, aggregates):
"""
A little helper method to check if the lookup_parts contains references
@@ -15,6 +16,7 @@ def refs_aggregate(lookup_parts, aggregates):
return True
return False
+
class Aggregate(object):
"""
Default Aggregate definition.
@@ -58,23 +60,30 @@ class Aggregate(object):
aggregate = klass(col, source=source, is_summary=is_summary, **self.extra)
query.aggregates[alias] = aggregate
+
class Avg(Aggregate):
name = 'Avg'
+
class Count(Aggregate):
name = 'Count'
+
class Max(Aggregate):
name = 'Max'
+
class Min(Aggregate):
name = 'Min'
+
class StdDev(Aggregate):
name = 'StdDev'
+
class Sum(Aggregate):
name = 'Sum'
+
class Variance(Aggregate):
name = 'Variance'
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 17a4a14b8d..a2937f12c6 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -19,6 +19,7 @@ from django.db.models.query_utils import DeferredAttribute, deferred_class_facto
from django.db.models.deletion import Collector
from django.db.models.options import Options
from django.db.models import signals
+from django.db.models.loading import register_models, get_model, MODELS_MODULE_NAME
from django.utils.translation import ugettext_lazy as _
from django.utils.functional import curry
from django.utils.encoding import force_str, force_text
@@ -85,10 +86,22 @@ class ModelBase(type):
base_meta = getattr(new_class, '_meta', None)
if getattr(meta, 'app_label', None) is None:
- # Figure out the app_label by looking one level up.
+ # Figure out the app_label by looking one level up from the package
+ # or module named 'models'. If no such package or module exists,
+ # fall back to looking one level up from the module this model is
+ # defined in.
+
# For 'django.contrib.sites.models', this would be 'sites'.
+ # For 'geo.models.places' this would be 'geo'.
+
model_module = sys.modules[new_class.__module__]
- kwargs = {"app_label": model_module.__name__.split('.')[-2]}
+ package_components = model_module.__name__.split('.')
+ package_components.reverse() # find the last occurrence of 'models'
+ try:
+ app_label_index = package_components.index(MODELS_MODULE_NAME) + 1
+ except ValueError:
+ app_label_index = 1
+ kwargs = {"app_label": package_components[app_label_index]}
else:
kwargs = {}
@@ -225,9 +238,9 @@ class ModelBase(type):
# class
for field in base._meta.virtual_fields:
if base._meta.abstract and field.name in field_names:
- raise FieldError('Local field %r in class %r clashes '\
- 'with field of similar name from '\
- 'abstract base class %r' % \
+ raise FieldError('Local field %r in class %r clashes '
+ 'with field of similar name from '
+ 'abstract base class %r' %
(field.name, name, base.__name__))
new_class.add_to_class(field.name, copy.deepcopy(field))
@@ -1007,8 +1020,6 @@ def get_absolute_url(opts, func, self, *args, **kwargs):
# MISC #
########
-class Empty(object):
- pass
def simple_class_factory(model, attrs):
"""
@@ -1016,6 +1027,7 @@ def simple_class_factory(model, attrs):
"""
return model
+
def model_unpickle(model_id, attrs, factory):
"""
Used to unpickle Model subclasses with deferred fields.
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
index 6e0f3c434e..b4eea5fe71 100644
--- a/django/db/models/expressions.py
+++ b/django/db/models/expressions.py
@@ -4,6 +4,7 @@ from django.db.models.aggregates import refs_aggregate
from django.db.models.constants import LOOKUP_SEP
from django.utils import tree
+
class ExpressionNode(tree.Node):
"""
Base class for all query expressions.
@@ -128,6 +129,7 @@ class ExpressionNode(tree.Node):
"Use .bitand() and .bitor() for bitwise logical operations."
)
+
class F(ExpressionNode):
"""
An expression representing the value of the given field.
@@ -147,6 +149,7 @@ class F(ExpressionNode):
def evaluate(self, evaluator, qn, connection):
return evaluator.evaluate_leaf(self, qn, connection)
+
class DateModifierNode(ExpressionNode):
"""
Node that implements the following syntax:
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 9263f81184..0410815c92 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
+import collections
import copy
import datetime
import decimal
@@ -17,7 +18,6 @@ from django.core import exceptions, validators
from django.utils.datastructures import DictWrapper
from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.functional import curry, total_ordering
-from django.utils.itercompat import is_iterator
from django.utils.text import capfirst
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
@@ -25,9 +25,11 @@ from django.utils.encoding import smart_text, force_text, force_bytes
from django.utils.ipv6 import clean_ipv6_address
from django.utils import six
+
class Empty(object):
pass
+
class NOT_PROVIDED:
pass
@@ -35,12 +37,15 @@ class NOT_PROVIDED:
# of most "choices" lists.
BLANK_CHOICE_DASH = [("", "---------")]
+
def _load_field(app_label, model_name, field_name):
return get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0]
+
class FieldDoesNotExist(Exception):
pass
+
# A guide to Field parameters:
#
# * name: The name of the field specifed in the model.
@@ -61,6 +66,7 @@ def _empty(of_cls):
new.__class__ = of_cls
return new
+
@total_ordering
class Field(object):
"""Base class for all field types"""
@@ -464,12 +470,12 @@ class Field(object):
if hasattr(value, '_prepare'):
return value._prepare()
- if lookup_type in (
- 'iexact', 'contains', 'icontains',
- 'startswith', 'istartswith', 'endswith', 'iendswith',
- 'month', 'day', 'week_day', 'hour', 'minute', 'second',
- 'isnull', 'search', 'regex', 'iregex',
- ):
+ if lookup_type in {
+ 'iexact', 'contains', 'icontains',
+ 'startswith', 'istartswith', 'endswith', 'iendswith',
+ 'month', 'day', 'week_day', 'hour', 'minute', 'second',
+ 'isnull', 'search', 'regex', 'iregex',
+ }:
return value
elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
return self.get_prep_value(value)
@@ -599,7 +605,7 @@ class Field(object):
return bound_field_class(self, fieldmapping, original)
def _get_choices(self):
- if is_iterator(self._choices):
+ if isinstance(self._choices, collections.Iterator):
choices, self._choices = tee(self._choices)
return choices
else:
@@ -613,7 +619,7 @@ class Field(object):
if isinstance(value, (list, tuple)):
flat.extend(value)
else:
- flat.append((choice,value))
+ flat.append((choice, value))
return flat
flatchoices = property(_get_flatchoices)
@@ -732,6 +738,7 @@ class AutoField(Field):
def formfield(self, **kwargs):
return None
+
class BooleanField(Field):
empty_strings_allowed = False
default_error_messages = {
@@ -786,13 +793,13 @@ class BooleanField(Field):
if self.choices:
include_blank = (self.null or
not (self.has_default() or 'initial' in kwargs))
- defaults = {'choices': self.get_choices(
- include_blank=include_blank)}
+ defaults = {'choices': self.get_choices(include_blank=include_blank)}
else:
defaults = {'form_class': forms.BooleanField}
defaults.update(kwargs)
return super(BooleanField, self).formfield(**defaults)
+
class CharField(Field):
description = _("String (up to %(max_length)s)")
@@ -819,6 +826,7 @@ class CharField(Field):
defaults.update(kwargs)
return super(CharField, self).formfield(**defaults)
+
# TODO: Maybe move this into contrib, because it's specialized.
class CommaSeparatedIntegerField(CharField):
default_validators = [validators.validate_comma_separated_integer_list]
@@ -833,6 +841,7 @@ class CommaSeparatedIntegerField(CharField):
defaults.update(kwargs)
return super(CommaSeparatedIntegerField, self).formfield(**defaults)
+
class DateField(Field):
empty_strings_allowed = False
default_error_messages = {
@@ -905,7 +914,7 @@ class DateField(Field):
return super(DateField, self).pre_save(model_instance, add)
def contribute_to_class(self, cls, name):
- super(DateField,self).contribute_to_class(cls, name)
+ super(DateField, self).contribute_to_class(cls, name)
if not self.null:
setattr(cls, 'get_next_by_%s' % self.name,
curry(cls._get_next_or_previous_by_FIELD, field=self,
@@ -939,6 +948,7 @@ class DateField(Field):
defaults.update(kwargs)
return super(DateField, self).formfield(**defaults)
+
class DateTimeField(DateField):
empty_strings_allowed = False
default_error_messages = {
@@ -1045,6 +1055,7 @@ class DateTimeField(DateField):
defaults.update(kwargs)
return super(DateTimeField, self).formfield(**defaults)
+
class DecimalField(Field):
empty_strings_allowed = False
default_error_messages = {
@@ -1116,6 +1127,7 @@ class DecimalField(Field):
defaults.update(kwargs)
return super(DecimalField, self).formfield(**defaults)
+
class EmailField(CharField):
default_validators = [validators.validate_email]
description = _("Email address")
@@ -1142,13 +1154,14 @@ class EmailField(CharField):
defaults.update(kwargs)
return super(EmailField, self).formfield(**defaults)
+
class FilePathField(Field):
description = _("File path")
def __init__(self, verbose_name=None, name=None, path='', match=None,
recursive=False, allow_files=True, allow_folders=False, **kwargs):
self.path, self.match, self.recursive = path, match, recursive
- self.allow_files, self.allow_folders = allow_files, allow_folders
+ self.allow_files, self.allow_folders = allow_files, allow_folders
kwargs['max_length'] = kwargs.get('max_length', 100)
Field.__init__(self, verbose_name, name, **kwargs)
@@ -1183,6 +1196,7 @@ class FilePathField(Field):
def get_internal_type(self):
return "FilePathField"
+
class FloatField(Field):
empty_strings_allowed = False
default_error_messages = {
@@ -1215,6 +1229,7 @@ class FloatField(Field):
defaults.update(kwargs)
return super(FloatField, self).formfield(**defaults)
+
class IntegerField(Field):
empty_strings_allowed = False
default_error_messages = {
@@ -1253,6 +1268,7 @@ class IntegerField(Field):
defaults.update(kwargs)
return super(IntegerField, self).formfield(**defaults)
+
class BigIntegerField(IntegerField):
empty_strings_allowed = False
description = _("Big (8 byte) integer")
@@ -1267,6 +1283,7 @@ class BigIntegerField(IntegerField):
defaults.update(kwargs)
return super(BigIntegerField, self).formfield(**defaults)
+
class IPAddressField(Field):
empty_strings_allowed = False
description = _("IPv4 address")
@@ -1288,6 +1305,7 @@ class IPAddressField(Field):
defaults.update(kwargs)
return super(IPAddressField, self).formfield(**defaults)
+
class GenericIPAddressField(Field):
empty_strings_allowed = True
description = _("IP address")
@@ -1336,7 +1354,10 @@ class GenericIPAddressField(Field):
return value
def formfield(self, **kwargs):
- defaults = {'form_class': forms.GenericIPAddressField}
+ defaults = {
+ 'protocol': self.protocol,
+ 'form_class': forms.GenericIPAddressField,
+ }
defaults.update(kwargs)
return super(GenericIPAddressField, self).formfield(**defaults)
@@ -1403,6 +1424,7 @@ class NullBooleanField(Field):
defaults.update(kwargs)
return super(NullBooleanField, self).formfield(**defaults)
+
class PositiveIntegerField(IntegerField):
description = _("Positive integer")
@@ -1414,6 +1436,7 @@ class PositiveIntegerField(IntegerField):
defaults.update(kwargs)
return super(PositiveIntegerField, self).formfield(**defaults)
+
class PositiveSmallIntegerField(IntegerField):
description = _("Positive small integer")
@@ -1425,6 +1448,7 @@ class PositiveSmallIntegerField(IntegerField):
defaults.update(kwargs)
return super(PositiveSmallIntegerField, self).formfield(**defaults)
+
class SlugField(CharField):
default_validators = [validators.validate_slug]
description = _("Slug (up to %(max_length)s)")
@@ -1454,12 +1478,14 @@ class SlugField(CharField):
defaults.update(kwargs)
return super(SlugField, self).formfield(**defaults)
+
class SmallIntegerField(IntegerField):
description = _("Small integer")
def get_internal_type(self):
return "SmallIntegerField"
+
class TextField(Field):
description = _("Text")
@@ -1476,6 +1502,7 @@ class TextField(Field):
defaults.update(kwargs)
return super(TextField, self).formfield(**defaults)
+
class TimeField(Field):
empty_strings_allowed = False
default_error_messages = {
@@ -1559,6 +1586,7 @@ class TimeField(Field):
defaults.update(kwargs)
return super(TimeField, self).formfield(**defaults)
+
class URLField(CharField):
default_validators = [validators.URLValidator()]
description = _("URL")
@@ -1582,6 +1610,7 @@ class URLField(CharField):
defaults.update(kwargs)
return super(URLField, self).formfield(**defaults)
+
class BinaryField(Field):
description = _("Raw binary data")
empty_values = [None, b'']
diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py
index 7663c8ab90..311f74a905 100644
--- a/django/db/models/fields/files.py
+++ b/django/db/models/fields/files.py
@@ -11,6 +11,7 @@ from django.utils.encoding import force_str, force_text
from django.utils import six
from django.utils.translation import ugettext_lazy as _
+
class FieldFile(File):
def __init__(self, instance, field, name):
super(FieldFile, self).__init__(None, name)
@@ -135,6 +136,7 @@ class FieldFile(File):
# be restored later, by FileDescriptor below.
return {'name': self.name, 'closed': False, '_committed': True, '_file': None}
+
class FileDescriptor(object):
"""
The descriptor for the file attribute on the model instance. Returns a
@@ -205,6 +207,7 @@ class FileDescriptor(object):
def __set__(self, instance, value):
instance.__dict__[self.field.name] = value
+
class FileField(Field):
# The class to wrap instance attributes in. Accessing the file object off
@@ -300,6 +303,7 @@ class FileField(Field):
defaults.update(kwargs)
return super(FileField, self).formfield(**defaults)
+
class ImageFileDescriptor(FileDescriptor):
"""
Just like the FileDescriptor, but for ImageFields. The only difference is
@@ -321,14 +325,15 @@ class ImageFileDescriptor(FileDescriptor):
if previous_file is not None:
self.field.update_dimension_fields(instance, force=True)
-class ImageFieldFile(ImageFile, FieldFile):
+class ImageFieldFile(ImageFile, FieldFile):
def delete(self, save=True):
# Clear the image dimensions cache
if hasattr(self, '_dimensions_cache'):
del self._dimensions_cache
super(ImageFieldFile, self).delete(save)
+
class ImageField(FileField):
attr_class = ImageFieldFile
descriptor_class = ImageFileDescriptor
diff --git a/django/db/models/fields/proxy.py b/django/db/models/fields/proxy.py
index c0cc873f4c..29c782b59a 100644
--- a/django/db/models/fields/proxy.py
+++ b/django/db/models/fields/proxy.py
@@ -5,6 +5,7 @@ have the same attributes as fields sometimes (avoids a lot of special casing).
from django.db.models import fields
+
class OrderWrt(fields.IntegerField):
"""
A proxy for the _order database field that is used when
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 480c77ab77..5a7718e880 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -211,7 +211,7 @@ class SingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjectDescri
# If null=True, we can assign null here, but otherwise the value needs
# to be an instance of the related class.
- if value is None and self.related.field.null == False:
+ if value is None and self.related.field.null is False:
raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' %
(instance._meta.object_name, self.related.get_accessor_name()))
elif value is not None and not isinstance(value, self.related.model):
@@ -310,7 +310,7 @@ class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjec
def __set__(self, instance, value):
# If null=True, we can assign null here, but otherwise the value needs
# to be an instance of the related class.
- if value is None and self.field.null == False:
+ if value is None and self.field.null is False:
raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' %
(instance._meta.object_name, self.field.name))
elif value is not None and not isinstance(value, self.field.rel.to):
@@ -395,7 +395,7 @@ class ForeignRelatedObjectsDescriptor(object):
def __init__(self, instance):
super(RelatedManager, self).__init__()
self.instance = instance
- self.core_filters= {'%s__exact' % rel_field.name: instance}
+ self.core_filters = {'%s__exact' % rel_field.name: instance}
self.model = rel_model
def get_queryset(self):
@@ -510,7 +510,6 @@ def create_many_related_manager(superclass, rel):
"a many-to-many relationship can be used." %
instance.__class__.__name__)
-
def _get_fk_val(self, obj, field_name):
"""
Returns the correct value for this relationship's foreign key. This
@@ -821,6 +820,7 @@ class ReverseManyRelatedObjectsDescriptor(object):
manager.clear()
manager.add(*value)
+
class ForeignObjectRel(object):
def __init__(self, field, to, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None, related_query_name=None):
@@ -858,6 +858,7 @@ class ForeignObjectRel(object):
# example custom multicolumn joins currently have no remote field).
self.field_name = None
+
class ManyToOneRel(ForeignObjectRel):
def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None, related_query_name=None):
@@ -1125,7 +1126,7 @@ class ForeignKey(ForeignObject):
def __init__(self, to, to_field=None, rel_class=ManyToOneRel,
db_constraint=True, **kwargs):
try:
- to_name = to._meta.object_name.lower()
+ to._meta.object_name.lower()
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert isinstance(to, six.string_types), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
else:
@@ -1162,7 +1163,6 @@ class ForeignKey(ForeignObject):
if self.rel.on_delete is not CASCADE:
kwargs['on_delete'] = self.rel.on_delete
# Rel needs more work.
- rel = self.rel
if self.rel.field_name:
kwargs['to_field'] = self.rel.field_name
if isinstance(self.rel.to, six.string_types):
@@ -1193,8 +1193,8 @@ class ForeignKey(ForeignObject):
using = router.db_for_read(model_instance.__class__, instance=model_instance)
qs = self.rel.to._default_manager.using(using).filter(
- **{self.rel.field_name: value}
- )
+ **{self.rel.field_name: value}
+ )
qs = qs.complex_filter(self.rel.limit_choices_to)
if not qs.exists():
raise exceptions.ValidationError(
@@ -1222,7 +1222,7 @@ class ForeignKey(ForeignObject):
return field_default
def get_db_prep_save(self, value, connection):
- if value == '' or value == None:
+ if value == '' or value is None:
return None
else:
return self.related_field.get_db_prep_save(value,
@@ -1393,7 +1393,6 @@ class ManyToManyField(RelatedField):
if "help_text" in kwargs:
del kwargs['help_text']
# Rel needs more work.
- rel = self.rel
if isinstance(self.rel.to, six.string_types):
kwargs['to'] = self.rel.to
else:
diff --git a/django/db/models/fields/subclassing.py b/django/db/models/fields/subclassing.py
index e6153aefe0..591adb7121 100644
--- a/django/db/models/fields/subclassing.py
+++ b/django/db/models/fields/subclassing.py
@@ -7,6 +7,7 @@ to_python() and the other necessary methods and everything will work
seamlessly.
"""
+
class SubfieldBase(type):
"""
A metaclass for custom Field subclasses. This ensures the model's attribute
@@ -19,6 +20,7 @@ class SubfieldBase(type):
)
return new_class
+
class Creator(object):
"""
A placeholder class that provides a way to set the attribute on the model.
@@ -34,6 +36,7 @@ class Creator(object):
def __set__(self, obj, value):
obj.__dict__[self.field.name] = self.field.to_python(value)
+
def make_contrib(superclass, func=None):
"""
Returns a suitable contribute_to_class() method for the Field subclass.
diff --git a/django/db/models/loading.py b/django/db/models/loading.py
index c1e394327f..df3be5fe9b 100644
--- a/django/db/models/loading.py
+++ b/django/db/models/loading.py
@@ -15,9 +15,13 @@ import os
__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
'load_app', 'app_cache_ready')
+MODELS_MODULE_NAME = 'models'
+
+
class UnavailableApp(Exception):
pass
+
def _initialize():
"""
Returns a dictionary to be used as the initial value of the
@@ -118,12 +122,12 @@ class BaseAppCache(object):
self.nesting_level += 1
app_module = import_module(app_name)
try:
- models = import_module('.models', app_name)
+ models = import_module('.' + MODELS_MODULE_NAME, app_name)
except ImportError:
self.nesting_level -= 1
# If the app doesn't have a models module, we can just ignore the
# ImportError and return no models for it.
- if not module_has_submodule(app_module, 'models'):
+ if not module_has_submodule(app_module, MODELS_MODULE_NAME):
return None
# But if the app does have a models module, we need to figure out
# whether to suppress or propagate the error. If can_postpone is
diff --git a/django/db/models/manager.py b/django/db/models/manager.py
index 6817c9c8ee..b369aedb64 100644
--- a/django/db/models/manager.py
+++ b/django/db/models/manager.py
@@ -6,6 +6,7 @@ from django.db.models.fields import FieldDoesNotExist
from django.utils import six
from django.utils.deprecation import RenameMethodsBase
+
def ensure_default_manager(sender, **kwargs):
"""
Ensures that a Model subclass contains a default manager and sets the
@@ -153,6 +154,9 @@ class Manager(six.with_metaclass(RenameManagerMethods)):
def get_or_create(self, **kwargs):
return self.get_queryset().get_or_create(**kwargs)
+ def update_or_create(self, **kwargs):
+ return self.get_queryset().update_or_create(**kwargs)
+
def create(self, **kwargs):
return self.get_queryset().create(**kwargs)
@@ -245,7 +249,7 @@ class ManagerDescriptor(object):
self.manager = manager
def __get__(self, instance, type=None):
- if instance != None:
+ if instance is not None:
raise AttributeError("Manager isn't accessible via %s instances" % type.__name__)
return self.manager
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 34d8eaa6ff..811e917764 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -19,6 +19,10 @@ from django.utils.functional import partition
from django.utils import six
from django.utils import timezone
+# 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
@@ -297,6 +301,7 @@ class QuerySet(object):
clone = self.filter(*args, **kwargs)
if self.query.can_filter():
clone = clone.order_by()
+ clone = clone[:MAX_GET_RESULTS + 1]
num = len(clone)
if num == 1:
return clone._result_cache[0]
@@ -305,8 +310,11 @@ class QuerySet(object):
"%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))
+ "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
+ )
+ )
def create(self, **kwargs):
"""
@@ -351,42 +359,89 @@ class QuerySet(object):
if objs_with_pk:
self._batched_insert(objs_with_pk, fields, batch_size)
if objs_without_pk:
- fields= [f for f in fields if not isinstance(f, AutoField)]
+ fields = [f for f in fields if not isinstance(f, AutoField)]
self._batched_insert(objs_without_pk, fields, batch_size)
return objs
- def get_or_create(self, **kwargs):
+ def get_or_create(self, defaults=None, **kwargs):
"""
Looks up an object with the given kwargs, creating one if necessary.
Returns a tuple of (object, created), where created is a boolean
specifying whether an object was created.
"""
- defaults = kwargs.pop('defaults', {})
- lookup = kwargs.copy()
- for f in self.model._meta.fields:
- if f.attname in lookup:
- lookup[f.name] = lookup.pop(f.attname)
+ lookup, params, _ = self._extract_model_params(defaults, **kwargs)
try:
self._for_write = True
return self.get(**lookup), False
except self.model.DoesNotExist:
+ return self._create_object_from_params(lookup, params)
+
+ def update_or_create(self, defaults=None, **kwargs):
+ """
+ Looks up an object with the given kwargs, updating one with defaults
+ if it exists, otherwise creates a new one.
+ Returns a tuple (object, created), where created is a boolean
+ specifying whether an object was created.
+ """
+ lookup, params, filtered_defaults = self._extract_model_params(defaults, **kwargs)
+ try:
+ self._for_write = True
+ obj = self.get(**lookup)
+ except self.model.DoesNotExist:
+ obj, created = self._create_object_from_params(lookup, params)
+ if created:
+ return obj, created
+ for k, v in six.iteritems(filtered_defaults):
+ setattr(obj, k, v)
+ try:
+ sid = transaction.savepoint(using=self.db)
+ obj.save(update_fields=filtered_defaults.keys(), using=self.db)
+ transaction.savepoint_commit(sid, using=self.db)
+ return obj, False
+ except DatabaseError:
+ transaction.savepoint_rollback(sid, using=self.db)
+ six.reraise(sys.exc_info())
+
+ def _create_object_from_params(self, lookup, params):
+ """
+ Tries to create an object using passed params.
+ Used by get_or_create and update_or_create
+ """
+ try:
+ obj = self.model(**params)
+ sid = transaction.savepoint(using=self.db)
+ obj.save(force_insert=True, using=self.db)
+ transaction.savepoint_commit(sid, using=self.db)
+ return obj, True
+ except DatabaseError:
+ transaction.savepoint_rollback(sid, using=self.db)
+ exc_info = sys.exc_info()
try:
- params = dict((k, v) for k, v in kwargs.items() if LOOKUP_SEP not in k)
- params.update(defaults)
- obj = self.model(**params)
- sid = transaction.savepoint(using=self.db)
- obj.save(force_insert=True, using=self.db)
- transaction.savepoint_commit(sid, using=self.db)
- return obj, True
- except DatabaseError:
- transaction.savepoint_rollback(sid, using=self.db)
- exc_info = sys.exc_info()
- try:
- return self.get(**lookup), False
- except self.model.DoesNotExist:
- # Re-raise the DatabaseError with its original traceback.
- six.reraise(*exc_info)
+ return self.get(**lookup), False
+ except self.model.DoesNotExist:
+ # Re-raise the DatabaseError with its original traceback.
+ six.reraise(*exc_info)
+
+ def _extract_model_params(self, defaults, **kwargs):
+ """
+ Prepares `lookup` (kwargs that are valid model attributes), `params`
+ (for creating a model instance) and `filtered_defaults` (defaults
+ that are valid model attributes) based on given kwargs; for use by
+ get_or_create and update_or_create.
+ """
+ defaults = defaults or {}
+ filtered_defaults = {}
+ lookup = kwargs.copy()
+ for f in self.model._meta.fields:
+ # Filter out fields that don't belongs to the model.
+ if f.attname in lookup:
+ lookup[f.name] = lookup.pop(f.attname)
+ if f.attname in defaults:
+ filtered_defaults[f.name] = defaults.pop(f.attname)
+ params = dict((k, v) for k, v in kwargs.items() if LOOKUP_SEP not in k)
+ params.update(filtered_defaults)
+ return lookup, params, filtered_defaults
def _earliest_or_latest(self, field_name=None, direction="-"):
"""
@@ -638,7 +693,7 @@ class QuerySet(object):
obj.query.select_for_update_nowait = nowait
return obj
- def select_related(self, *fields, **kwargs):
+ def select_related(self, *fields):
"""
Returns a new QuerySet instance that will select related objects.
@@ -647,9 +702,6 @@ class QuerySet(object):
If select_related(None) is called, the list is cleared.
"""
- if kwargs:
- raise TypeError('Unexpected keyword arguments to select_related: %s'
- % (list(kwargs),))
obj = self._clone()
if fields == (None,):
obj.query.select_related = False
@@ -821,7 +873,7 @@ class QuerySet(object):
return
ops = connections[self.db].ops
batch_size = (batch_size or max(ops.bulk_batch_size(fields, objs), 1))
- for batch in [objs[i:i+batch_size]
+ for batch in [objs[i:i + batch_size]
for i in range(0, len(objs), batch_size)]:
self.model._base_manager._insert(batch, fields=fields,
using=self.db)
@@ -902,10 +954,12 @@ class QuerySet(object):
# empty" result.
value_annotation = True
+
class InstanceCheckMeta(type):
def __instancecheck__(self, instance):
return instance.query.is_empty()
+
class EmptyQuerySet(six.with_metaclass(InstanceCheckMeta)):
"""
Marker class usable for checking if a queryset is empty by .none():
@@ -915,6 +969,7 @@ class EmptyQuerySet(six.with_metaclass(InstanceCheckMeta)):
def __init__(self, *args, **kwargs):
raise TypeError("EmptyQuerySet can't be instantiated")
+
class ValuesQuerySet(QuerySet):
def __init__(self, *args, **kwargs):
super(ValuesQuerySet, self).__init__(*args, **kwargs)
@@ -1243,7 +1298,7 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
only_load.get(o.model), reverse=True):
next = requested[o.field.related_query_name()]
parent = klass if issubclass(o.model, klass) else None
- klass_info = get_klass_info(o.model, max_depth=max_depth, cur_depth=cur_depth+1,
+ klass_info = get_klass_info(o.model, max_depth=max_depth, cur_depth=cur_depth + 1,
requested=next, only_load=only_load, from_parent=parent)
reverse_related_fields.append((o.field, klass_info))
if field_names:
@@ -1254,7 +1309,7 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
return klass, field_names, field_count, related_fields, reverse_related_fields, pk_idx
-def get_cached_row(row, index_start, using, klass_info, offset=0,
+def get_cached_row(row, index_start, using, klass_info, offset=0,
parent_data=()):
"""
Helper function that recursively returns an object with the specified
@@ -1279,11 +1334,10 @@ def get_cached_row(row, index_start, using, klass_info, offset=0,
return None
klass, field_names, field_count, related_fields, reverse_related_fields, pk_idx = klass_info
-
- fields = row[index_start : index_start + field_count]
+ fields = row[index_start:index_start + field_count]
# If the pk column is None (or the Oracle equivalent ''), then the related
# object must be non-existent - set the relation to None.
- if fields[pk_idx] == None or fields[pk_idx] == '':
+ if fields[pk_idx] is None or fields[pk_idx] == '':
obj = None
elif field_names:
fields = list(fields)
@@ -1513,8 +1567,6 @@ def prefetch_related_objects(result_cache, related_lookups):
if len(result_cache) == 0:
return # nothing to do
- model = result_cache[0].__class__
-
# We need to be able to dynamically add to the list of prefetch_related
# lookups that we look up (see below). So we need some book keeping to
# ensure we don't do duplicate work.
@@ -1541,7 +1593,7 @@ def prefetch_related_objects(result_cache, related_lookups):
if len(obj_list) == 0:
break
- current_lookup = LOOKUP_SEP.join(attrs[0:level+1])
+ current_lookup = LOOKUP_SEP.join(attrs[:level + 1])
if current_lookup in done_queries:
# Skip any prefetching, and any object preparation
obj_list = done_queries[current_lookup]
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
index ee7a56a26c..2a92978beb 100644
--- a/django/db/models/query_utils.py
+++ b/django/db/models/query_utils.py
@@ -30,6 +30,7 @@ class QueryWrapper(object):
def as_sql(self, qn=None, connection=None):
return self.data
+
class Q(tree.Node):
"""
Encapsulates filters as objects that can then be combined logically (using
@@ -74,6 +75,7 @@ class Q(tree.Node):
clone.children.append(child)
return clone
+
class DeferredAttribute(object):
"""
A wrapper for a deferred-loading field. When the value is read from this
@@ -99,8 +101,7 @@ class DeferredAttribute(object):
try:
f = opts.get_field_by_name(self.field_name)[0]
except FieldDoesNotExist:
- f = [f for f in opts.fields
- if f.attname == self.field_name][0]
+ f = [f for f in opts.fields if f.attname == self.field_name][0]
name = f.name
# Let's see if the field is part of the parent chain. If so we
# might be able to reuse the already loaded value. Refs #18343.
@@ -174,6 +175,7 @@ def select_related_descend(field, restricted, requested, load_fields, reverse=Fa
return False
return True
+
# This function is needed because data descriptors must be defined on a class
# object, not an instance, to have any effect.
diff --git a/django/db/models/related.py b/django/db/models/related.py
index 4b00dd343b..ba2a90c545 100644
--- a/django/db/models/related.py
+++ b/django/db/models/related.py
@@ -10,6 +10,7 @@ PathInfo = namedtuple('PathInfo',
'from_opts to_opts target_fields join_field '
'm2m direct')
+
class RelatedObject(object):
def __init__(self, parent_model, model, field):
self.parent_model = parent_model
diff --git a/django/db/models/sql/aggregates.py b/django/db/models/sql/aggregates.py
index 2bd2b2f76f..9fc5fe8a5b 100644
--- a/django/db/models/sql/aggregates.py
+++ b/django/db/models/sql/aggregates.py
@@ -9,6 +9,7 @@ from django.db.models.fields import IntegerField, FloatField
ordinal_aggregate_field = IntegerField()
computed_aggregate_field = FloatField()
+
class Aggregate(object):
"""
Default SQL Aggregate.
@@ -93,6 +94,7 @@ class Avg(Aggregate):
is_computed = True
sql_function = 'AVG'
+
class Count(Aggregate):
is_ordinal = True
sql_function = 'COUNT'
@@ -101,12 +103,15 @@ class Count(Aggregate):
def __init__(self, col, distinct=False, **extra):
super(Count, self).__init__(col, distinct='DISTINCT ' if distinct else '', **extra)
+
class Max(Aggregate):
sql_function = 'MAX'
+
class Min(Aggregate):
sql_function = 'MIN'
+
class StdDev(Aggregate):
is_computed = True
@@ -114,9 +119,11 @@ class StdDev(Aggregate):
super(StdDev, self).__init__(col, **extra)
self.sql_function = 'STDDEV_SAMP' if sample else 'STDDEV_POP'
+
class Sum(Aggregate):
sql_function = 'SUM'
+
class Variance(Aggregate):
is_computed = True
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index f70750abed..e17cb3f616 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -688,7 +688,7 @@ class SQLCompiler(object):
# the relation, which is always nullable.
new_nullable = True
table = model._meta.db_table
- self.fill_related_selections(model._meta, table, cur_depth+1,
+ self.fill_related_selections(model._meta, table, cur_depth + 1,
next, restricted, new_nullable)
def deferred_to_columns(self):
@@ -750,6 +750,16 @@ class SQLCompiler(object):
yield row
+ def has_results(self):
+ """
+ Backends (e.g. NoSQL) can override this in order to use optimized
+ versions of "query has any results."
+ """
+ # This is always executed on a query clone, so we can modify self.query
+ self.query.add_extra({'a': 1}, None, None, None, None, None)
+ self.query.set_extra_mask(['a'])
+ return bool(self.execute_sql(SINGLE))
+
def execute_sql(self, result_type=MULTI):
"""
Run the query against the database and returns the result(s). The
@@ -915,6 +925,7 @@ class SQLDeleteCompiler(SQLCompiler):
result.append('WHERE %s' % where)
return ' '.join(result), tuple(params)
+
class SQLUpdateCompiler(SQLCompiler):
def as_sql(self):
"""
@@ -1029,6 +1040,7 @@ class SQLUpdateCompiler(SQLCompiler):
for alias in self.query.tables[1:]:
self.query.alias_refcount[alias] = 0
+
class SQLAggregateCompiler(SQLCompiler):
def as_sql(self, qn=None):
"""
@@ -1050,6 +1062,7 @@ class SQLAggregateCompiler(SQLCompiler):
params = params + self.query.sub_params
return sql, params
+
class SQLDateCompiler(SQLCompiler):
def results_iter(self):
"""
@@ -1075,6 +1088,7 @@ class SQLDateCompiler(SQLCompiler):
date = date.date()
yield date
+
class SQLDateTimeCompiler(SQLCompiler):
def results_iter(self):
"""
@@ -1107,6 +1121,7 @@ class SQLDateTimeCompiler(SQLCompiler):
datetime = timezone.make_aware(datetime, self.query.tzinfo)
yield datetime
+
def order_modified_iter(cursor, trim, sentinel):
"""
Yields blocks of rows from a cursor. We use this iterator in the special
diff --git a/django/db/models/sql/datastructures.py b/django/db/models/sql/datastructures.py
index daaabbe6da..76b3db5b35 100644
--- a/django/db/models/sql/datastructures.py
+++ b/django/db/models/sql/datastructures.py
@@ -3,9 +3,11 @@ Useful auxilliary data structures for query construction. Not useful outside
the SQL domain.
"""
+
class EmptyResultSet(Exception):
pass
+
class MultiJoin(Exception):
"""
Used by join construction code to indicate the point at which a
@@ -17,12 +19,10 @@ class MultiJoin(Exception):
# The path travelled, this includes the path to the multijoin.
self.names_with_path = path_with_names
+
class Empty(object):
pass
-class RawValue(object):
- def __init__(self, value):
- self.value = value
class Date(object):
"""
@@ -42,6 +42,7 @@ class Date(object):
col = self.col
return connection.ops.date_trunc_sql(self.lookup_type, col), []
+
class DateTime(object):
"""
Add a datetime selection column.
diff --git a/django/db/models/sql/expressions.py b/django/db/models/sql/expressions.py
index 62adf79d87..f9a8929974 100644
--- a/django/db/models/sql/expressions.py
+++ b/django/db/models/sql/expressions.py
@@ -1,7 +1,9 @@
+import copy
+
from django.core.exceptions import FieldError
from django.db.models.constants import LOOKUP_SEP
from django.db.models.fields import FieldDoesNotExist
-import copy
+
class SQLEvaluator(object):
def __init__(self, expression, query, allow_joins=True, reuse=None):
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 75e8e7540d..2a9b8ef826 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -401,12 +401,10 @@ class Query(object):
def has_results(self, using):
q = self.clone()
q.clear_select_clause()
- q.add_extra({'a': 1}, None, None, None, None, None)
- q.set_extra_mask(['a'])
q.clear_ordering(True)
q.set_limits(high=1)
compiler = q.get_compiler(using=using)
- return bool(compiler.execute_sql(SINGLE))
+ return compiler.has_results()
def combine(self, rhs, connector):
"""
@@ -615,7 +613,6 @@ class Query(object):
for model, values in six.iteritems(seen):
callback(target, model, values)
-
def deferred_to_columns_cb(self, target, model, fields):
"""
Callback used by deferred_to_columns(). The "target" parameter should
@@ -627,7 +624,6 @@ class Query(object):
for field in fields:
target[table].add(field.column)
-
def table_alias(self, table_name, create=False):
"""
Returns a table alias for the given table_name and whether this is a
@@ -955,7 +951,6 @@ class Query(object):
self.unref_alias(alias)
self.included_inherited_models = {}
-
def add_aggregate(self, aggregate, model, alias, is_summary):
"""
Adds a single aggregate expression to the Query
@@ -1780,7 +1775,7 @@ class Query(object):
return self._aggregate_select_cache
elif self.aggregate_select_mask is not None:
self._aggregate_select_cache = SortedDict([
- (k,v) for k,v in self.aggregates.items()
+ (k, v) for k, v in self.aggregates.items()
if k in self.aggregate_select_mask
])
return self._aggregate_select_cache
@@ -1793,7 +1788,7 @@ class Query(object):
return self._extra_select_cache
elif self.extra_select_mask is not None:
self._extra_select_cache = SortedDict([
- (k,v) for k,v in self.extra.items()
+ (k, v) for k, v in self.extra.items()
if k in self.extra_select_mask
])
return self._extra_select_cache
@@ -1876,6 +1871,7 @@ class Query(object):
else:
return field.null
+
def get_order_dir(field, default='ASC'):
"""
Returns the field name and direction for an order specification. For
@@ -1900,6 +1896,7 @@ def add_to_dict(data, key, value):
else:
data[key] = set([value])
+
def is_reverse_o2o(field):
"""
A little helper to check if the given field is reverse-o2o. The field is
@@ -1907,6 +1904,7 @@ 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
diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
index 20770162c9..6aab02bd9a 100644
--- a/django/db/models/sql/subqueries.py
+++ b/django/db/models/sql/subqueries.py
@@ -7,7 +7,7 @@ from django.core.exceptions import FieldError
from django.db import connections
from django.db.models.constants import LOOKUP_SEP
from django.db.models.fields import DateField, DateTimeField, FieldDoesNotExist
-from django.db.models.sql.constants import *
+from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE, SelectInfo
from django.db.models.sql.datastructures import Date, DateTime
from django.db.models.sql.query import Query
from django.db.models.sql.where import AND, Constraint
@@ -20,6 +20,7 @@ from django.utils import timezone
__all__ = ['DeleteQuery', 'UpdateQuery', 'InsertQuery', 'DateQuery',
'DateTimeQuery', 'AggregateQuery']
+
class DeleteQuery(Query):
"""
Delete queries are done through this class, since they are more constrained
@@ -77,7 +78,9 @@ class DeleteQuery(Query):
return
else:
innerq.clear_select_clause()
- innerq.select = [SelectInfo((self.get_initial_alias(), pk.column), None)]
+ innerq.select = [
+ SelectInfo((self.get_initial_alias(), pk.column), None)
+ ]
values = innerq
where = self.where_class()
where.add((Constraint(None, pk.column, pk), 'in', values), AND)
@@ -178,6 +181,7 @@ class UpdateQuery(Query):
result.append(query)
return result
+
class InsertQuery(Query):
compiler = 'SQLInsertCompiler'
@@ -215,6 +219,7 @@ class InsertQuery(Query):
self.objs = objs
self.raw = raw
+
class DateQuery(Query):
"""
A DateQuery is a normal query, except that it specifically selects a single
@@ -260,6 +265,7 @@ class DateQuery(Query):
def _get_select(self, col, lookup_type):
return Date(col, lookup_type)
+
class DateTimeQuery(DateQuery):
"""
A DateTimeQuery is like a DateQuery but for a datetime field. If time zone
@@ -280,6 +286,7 @@ class DateTimeQuery(DateQuery):
tzname = timezone._get_timezone_name(self.tzinfo)
return DateTime(col, lookup_type, tzname)
+
class AggregateQuery(Query):
"""
An AggregateQuery takes another query as a parameter to the FROM
diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py
index 2a342d417a..2e83ecdce4 100644
--- a/django/db/models/sql/where.py
+++ b/django/db/models/sql/where.py
@@ -4,6 +4,7 @@ Code to manage the creation and SQL rendering of 'where' constraints.
from __future__ import absolute_import
+import collections
import datetime
from itertools import repeat
@@ -11,15 +12,16 @@ from django.conf import settings
from django.db.models.fields import DateTimeField, Field
from django.db.models.sql.datastructures import EmptyResultSet, Empty
from django.db.models.sql.aggregates import Aggregate
-from django.utils.itercompat import is_iterator
from django.utils.six.moves import xrange
from django.utils import timezone
from django.utils import tree
+
# Connection types
AND = 'AND'
OR = 'OR'
+
class EmptyShortCircuit(Exception):
"""
Internal exception used to indicate that a "matches nothing" node should be
@@ -27,6 +29,7 @@ class EmptyShortCircuit(Exception):
"""
pass
+
class WhereNode(tree.Node):
"""
Used to represent the SQL where-clause.
@@ -58,7 +61,7 @@ class WhereNode(tree.Node):
if not isinstance(data, (list, tuple)):
return data
obj, lookup_type, value = data
- if is_iterator(value):
+ if isinstance(value, collections.Iterator):
# Consume any generators immediately, so that we can determine
# emptiness and transform any non-empty values correctly.
value = list(value)
@@ -175,7 +178,7 @@ class WhereNode(tree.Node):
"""
lvalue, lookup_type, value_annotation, params_or_value = child
field_internal_type = lvalue.field.get_internal_type() if lvalue.field else None
-
+
if isinstance(lvalue, Constraint):
try:
lvalue, params = lvalue.process(lookup_type, params_or_value, connection)
@@ -304,14 +307,15 @@ class WhereNode(tree.Node):
clone.children.append(child)
return clone
-class EmptyWhere(WhereNode):
+class EmptyWhere(WhereNode):
def add(self, data, connector):
return
def as_sql(self, qn=None, connection=None):
raise EmptyResultSet
+
class EverythingNode(object):
"""
A node that matches everything.
@@ -385,6 +389,7 @@ class Constraint(object):
new.alias, new.col, new.field = change_map[self.alias], self.col, self.field
return new
+
class SubqueryConstraint(object):
def __init__(self, alias, columns, targets, query_object):
self.alias = alias
diff --git a/django/db/transaction.py b/django/db/transaction.py
index 96be981e7b..bb2dbb8e9e 100644
--- a/django/db/transaction.py
+++ b/django/db/transaction.py
@@ -27,6 +27,7 @@ class TransactionManagementError(Exception):
"""
pass
+
################
# Private APIs #
################
@@ -40,6 +41,7 @@ def get_connection(using=None):
using = DEFAULT_DB_ALIAS
return connections[using]
+
###########################
# Deprecated private APIs #
###########################
@@ -56,6 +58,7 @@ def abort(using=None):
"""
get_connection(using).abort()
+
def enter_transaction_management(managed=True, using=None, forced=False):
"""
Enters transaction management for a running thread. It must be balanced with
@@ -68,6 +71,7 @@ def enter_transaction_management(managed=True, using=None, forced=False):
"""
get_connection(using).enter_transaction_management(managed, forced)
+
def leave_transaction_management(using=None):
"""
Leaves transaction management for a running thread. A dirty flag is carried
@@ -76,6 +80,7 @@ def leave_transaction_management(using=None):
"""
get_connection(using).leave_transaction_management()
+
def is_dirty(using=None):
"""
Returns True if the current transaction requires a commit for changes to
@@ -83,6 +88,7 @@ def is_dirty(using=None):
"""
return get_connection(using).is_dirty()
+
def set_dirty(using=None):
"""
Sets a dirty flag for the current thread and code streak. This can be used
@@ -91,6 +97,7 @@ def set_dirty(using=None):
"""
get_connection(using).set_dirty()
+
def set_clean(using=None):
"""
Resets a dirty flag for the current thread and code streak. This can be used
@@ -99,22 +106,27 @@ def set_clean(using=None):
"""
get_connection(using).set_clean()
+
def is_managed(using=None):
warnings.warn("'is_managed' is deprecated.",
DeprecationWarning, stacklevel=2)
+
def managed(flag=True, using=None):
warnings.warn("'managed' no longer serves a purpose.",
DeprecationWarning, stacklevel=2)
+
def commit_unless_managed(using=None):
warnings.warn("'commit_unless_managed' is now a no-op.",
DeprecationWarning, stacklevel=2)
+
def rollback_unless_managed(using=None):
warnings.warn("'rollback_unless_managed' is now a no-op.",
DeprecationWarning, stacklevel=2)
+
###############
# Public APIs #
###############
@@ -125,24 +137,28 @@ def get_autocommit(using=None):
"""
return get_connection(using).get_autocommit()
+
def set_autocommit(autocommit, using=None):
"""
Set the autocommit status of the connection.
"""
return get_connection(using).set_autocommit(autocommit)
+
def commit(using=None):
"""
Commits a transaction and resets the dirty flag.
"""
get_connection(using).commit()
+
def rollback(using=None):
"""
Rolls back a transaction and resets the dirty flag.
"""
get_connection(using).rollback()
+
def savepoint(using=None):
"""
Creates a savepoint (if supported and required by the backend) inside the
@@ -151,6 +167,7 @@ def savepoint(using=None):
"""
return get_connection(using).savepoint()
+
def savepoint_rollback(sid, using=None):
"""
Rolls back the most recent savepoint (if one exists). Does nothing if
@@ -158,6 +175,7 @@ def savepoint_rollback(sid, using=None):
"""
get_connection(using).savepoint_rollback(sid)
+
def savepoint_commit(sid, using=None):
"""
Commits the most recent savepoint (if one exists). Does nothing if
@@ -165,18 +183,21 @@ def savepoint_commit(sid, using=None):
"""
get_connection(using).savepoint_commit(sid)
+
def clean_savepoints(using=None):
"""
Resets the counter used to generate unique savepoint ids in this thread.
"""
get_connection(using).clean_savepoints()
+
def get_rollback(using=None):
"""
Gets the "needs rollback" flag -- for *advanced use* only.
"""
return get_connection(using).get_rollback()
+
def set_rollback(rollback, using=None):
"""
Sets or unsets the "needs rollback" flag -- for *advanced use* only.
@@ -191,6 +212,7 @@ def set_rollback(rollback, using=None):
"""
return get_connection(using).set_rollback(rollback)
+
#################################
# Decorators / context managers #
#################################
@@ -398,6 +420,7 @@ class Transaction(object):
return func(*args, **kwargs)
return inner
+
def _transaction_func(entering, exiting, using):
"""
Takes 3 things, an entering function (what to do to start this block of
@@ -436,6 +459,7 @@ def autocommit(using=None):
return _transaction_func(entering, exiting, using)
+
def commit_on_success(using=None):
"""
This decorator activates commit on response. This way, if the view function
@@ -466,6 +490,7 @@ def commit_on_success(using=None):
return _transaction_func(entering, exiting, using)
+
def commit_manually(using=None):
"""
Decorator that activates manual transaction control. It just disables
@@ -484,6 +509,7 @@ def commit_manually(using=None):
return _transaction_func(entering, exiting, using)
+
def commit_on_success_unless_managed(using=None, savepoint=False):
"""
Transitory API to preserve backwards-compatibility while refactoring.
diff --git a/django/forms/fields.py b/django/forms/fields.py
index c4bc3fa88c..a794c02e9f 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -370,8 +370,14 @@ class DecimalField(IntegerField):
def widget_attrs(self, widget):
attrs = super(DecimalField, self).widget_attrs(widget)
- if isinstance(widget, NumberInput) and self.decimal_places:
- attrs['step'] = '0.%s1' % ('0' * (self.decimal_places - 1))
+ if isinstance(widget, NumberInput):
+ if self.decimal_places is not None:
+ # Use exponential notation for small values since they might
+ # be parsed as 0 otherwise. ref #20765
+ step = str(Decimal('1') / 10 ** self.decimal_places).lower()
+ else:
+ step = 'any'
+ attrs.setdefault('step', step)
return attrs
@@ -1019,7 +1025,7 @@ class MultiValueField(Field):
if not isinstance(initial, list):
initial = self.widget.decompress(initial)
for field, initial, data in zip(self.fields, initial, data):
- if field._has_changed(initial, data):
+ if field._has_changed(field.to_python(initial), data):
return True
return False
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 6d59271d17..e144eb60f8 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -77,7 +77,7 @@ class BaseForm(object):
# information. Any improvements to the form API should be made to *this*
# class, not to the Form class.
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
- initial=None, error_class=ErrorList, label_suffix=':',
+ initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False):
self.is_bound = data is not None or files is not None
self.data = data or {}
@@ -86,7 +86,8 @@ class BaseForm(object):
self.prefix = prefix
self.initial = initial or {}
self.error_class = error_class
- self.label_suffix = label_suffix
+ # Translators: This is the default suffix added to form field labels
+ self.label_suffix = label_suffix if label_suffix is not None else _(':')
self.empty_permitted = empty_permitted
self._errors = None # Stores the errors after clean() has been called.
self._changed_data = None
@@ -518,7 +519,9 @@ class BoundField(object):
"""
contents = contents or self.label
# Only add the suffix if the label does not end in punctuation.
- if self.form.label_suffix and contents and contents[-1] not in ':?.!':
+ # Translators: If found as last label character, these punctuation
+ # characters will prevent the default label_suffix to be appended to the label
+ if self.form.label_suffix and contents and contents[-1] not in _(':?.!'):
contents = format_html('{0}{1}', contents, self.form.label_suffix)
widget = self.field.widget
id_ = widget.attrs.get('id') or self.auto_id
diff --git a/django/forms/models.py b/django/forms/models.py
index a0c3009d38..1d3bec1635 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -292,7 +292,7 @@ class ModelFormMetaclass(type):
class BaseModelForm(BaseForm):
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
- initial=None, error_class=ErrorList, label_suffix=':',
+ initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False, instance=None):
opts = self._meta
if opts.model is None:
diff --git a/django/forms/util.py b/django/forms/util.py
index 0a73320f83..3c7724900a 100644
--- a/django/forms/util.py
+++ b/django/forms/util.py
@@ -1,5 +1,7 @@
from __future__ import unicode_literals
+import warnings
+
from django.conf import settings
from django.utils.html import format_html, format_html_join
from django.utils.encoding import force_text, python_2_unicode_compatible
@@ -21,6 +23,11 @@ def flatatt(attrs):
The result is passed through 'mark_safe'.
"""
+ if [v for v in attrs.values() if v is True or v is False]:
+ warnings.warn(
+ 'The meaning of boolean values for widget attributes will change in Django 1.8',
+ DeprecationWarning
+ )
return format_html_join('', ' {0}="{1}"', sorted(attrs.items()))
@python_2_unicode_compatible
diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
index eeb435fa57..9a5193e49b 100644
--- a/django/http/multipartparser.py
+++ b/django/http/multipartparser.py
@@ -50,7 +50,7 @@ class MultiPartParser(object):
The raw post data, as a file-like object.
:upload_handlers:
A list of UploadHandler instances that perform operations on the uploaded
- data.
+ data.
:encoding:
The encoding with which to treat the incoming data.
"""
@@ -177,11 +177,9 @@ class MultiPartParser(object):
file_name = force_text(file_name, encoding, errors='replace')
file_name = self.IE_sanitize(unescape_entities(file_name))
- content_type = meta_data.get('content-type', ('',))[0].strip()
- try:
- charset = meta_data.get('content-type', (0, {}))[1].get('charset', None)
- except:
- charset = None
+ content_type, content_type_extra = meta_data.get('content-type', ('', {}))
+ content_type = content_type.strip()
+ charset = content_type_extra.get('charset')
try:
content_length = int(meta_data.get('content-length')[0])
@@ -194,7 +192,7 @@ class MultiPartParser(object):
try:
handler.new_file(field_name, file_name,
content_type, content_length,
- charset)
+ charset, content_type_extra)
except StopFutureHandlers:
break
diff --git a/django/template/context.py b/django/template/context.py
index 1ef7e889bc..3830c1c660 100644
--- a/django/template/context.py
+++ b/django/template/context.py
@@ -12,6 +12,21 @@ class ContextPopException(Exception):
"pop() has been called more times than push()"
pass
+
+class ContextDict(dict):
+ def __init__(self, context, *args, **kwargs):
+ super(ContextDict, self).__init__(*args, **kwargs)
+
+ context.dicts.append(self)
+ self.context = context
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args, **kwargs):
+ self.context.pop()
+
+
class BaseContext(object):
def __init__(self, dict_=None):
self._reset_dicts(dict_)
@@ -34,10 +49,8 @@ class BaseContext(object):
for d in reversed(self.dicts):
yield d
- def push(self):
- d = {}
- self.dicts.append(d)
- return d
+ def push(self, *args, **kwargs):
+ return ContextDict(self, *args, **kwargs)
def pop(self):
if len(self.dicts) == 1:
@@ -83,6 +96,7 @@ class BaseContext(object):
new_context._reset_dicts(values)
return new_context
+
class Context(BaseContext):
"A stack container for variable context"
def __init__(self, dict_=None, autoescape=True, current_app=None,
@@ -106,6 +120,7 @@ class Context(BaseContext):
self.dicts.append(other_dict)
return other_dict
+
class RenderContext(BaseContext):
"""
A stack container for storing Template state.
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 5b2a1b9501..5c9490f749 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -95,10 +95,9 @@ class FilterNode(Node):
def render(self, context):
output = self.nodelist.render(context)
# Apply filters.
- context.update({'var': output})
- filtered = self.filter_expr.resolve(context)
- context.pop()
- return filtered
+ with context.push(var=output):
+ return self.filter_expr.resolve(context)
+
class FirstOfNode(Node):
def __init__(self, variables, escape=False):
@@ -143,71 +142,69 @@ class ForNode(Node):
parentloop = context['forloop']
else:
parentloop = {}
- context.push()
- try:
- values = self.sequence.resolve(context, True)
- except VariableDoesNotExist:
- values = []
- if values is None:
- values = []
- if not hasattr(values, '__len__'):
- values = list(values)
- len_values = len(values)
- if len_values < 1:
- context.pop()
- return self.nodelist_empty.render(context)
- nodelist = NodeList()
- if self.is_reversed:
- values = reversed(values)
- unpack = len(self.loopvars) > 1
- # Create a forloop value in the context. We'll update counters on each
- # iteration just below.
- loop_dict = context['forloop'] = {'parentloop': parentloop}
- for i, item in enumerate(values):
- # Shortcuts for current loop iteration number.
- loop_dict['counter0'] = i
- loop_dict['counter'] = i+1
- # Reverse counter iteration numbers.
- loop_dict['revcounter'] = len_values - i
- loop_dict['revcounter0'] = len_values - i - 1
- # Boolean values designating first and last times through loop.
- loop_dict['first'] = (i == 0)
- loop_dict['last'] = (i == len_values - 1)
+ with context.push():
+ try:
+ values = self.sequence.resolve(context, True)
+ except VariableDoesNotExist:
+ values = []
+ if values is None:
+ values = []
+ if not hasattr(values, '__len__'):
+ values = list(values)
+ len_values = len(values)
+ if len_values < 1:
+ return self.nodelist_empty.render(context)
+ nodelist = NodeList()
+ if self.is_reversed:
+ values = reversed(values)
+ unpack = len(self.loopvars) > 1
+ # Create a forloop value in the context. We'll update counters on each
+ # iteration just below.
+ loop_dict = context['forloop'] = {'parentloop': parentloop}
+ for i, item in enumerate(values):
+ # Shortcuts for current loop iteration number.
+ loop_dict['counter0'] = i
+ loop_dict['counter'] = i+1
+ # Reverse counter iteration numbers.
+ loop_dict['revcounter'] = len_values - i
+ loop_dict['revcounter0'] = len_values - i - 1
+ # Boolean values designating first and last times through loop.
+ loop_dict['first'] = (i == 0)
+ loop_dict['last'] = (i == len_values - 1)
- pop_context = False
- if unpack:
- # If there are multiple loop variables, unpack the item into
- # them.
- try:
- unpacked_vars = dict(zip(self.loopvars, item))
- except TypeError:
- pass
- else:
- pop_context = True
- context.update(unpacked_vars)
- else:
- context[self.loopvars[0]] = item
- # In TEMPLATE_DEBUG mode provide source of the node which
- # actually raised the exception
- if settings.TEMPLATE_DEBUG:
- for node in self.nodelist_loop:
+ pop_context = False
+ if unpack:
+ # If there are multiple loop variables, unpack the item into
+ # them.
try:
+ unpacked_vars = dict(zip(self.loopvars, item))
+ except TypeError:
+ pass
+ else:
+ pop_context = True
+ context.update(unpacked_vars)
+ else:
+ context[self.loopvars[0]] = item
+ # In TEMPLATE_DEBUG mode provide source of the node which
+ # actually raised the exception
+ if settings.TEMPLATE_DEBUG:
+ for node in self.nodelist_loop:
+ try:
+ nodelist.append(node.render(context))
+ except Exception as e:
+ if not hasattr(e, 'django_template_source'):
+ e.django_template_source = node.source
+ raise
+ else:
+ for node in self.nodelist_loop:
nodelist.append(node.render(context))
- except Exception as e:
- if not hasattr(e, 'django_template_source'):
- e.django_template_source = node.source
- raise
- else:
- for node in self.nodelist_loop:
- nodelist.append(node.render(context))
- if pop_context:
- # The loop variables were pushed on to the context so pop them
- # off again. This is necessary because the tag lets the length
- # of loopvars differ to the length of each set of items and we
- # don't want to leave any vars from the previous loop on the
- # context.
- context.pop()
- context.pop()
+ if pop_context:
+ # The loop variables were pushed on to the context so pop them
+ # off again. This is necessary because the tag lets the length
+ # of loopvars differ to the length of each set of items and we
+ # don't want to leave any vars from the previous loop on the
+ # context.
+ context.pop()
return nodelist.render(context)
class IfChangedNode(Node):
@@ -500,10 +497,9 @@ class WithNode(Node):
def render(self, context):
values = dict([(key, val.resolve(context)) for key, val in
six.iteritems(self.extra_context)])
- context.update(values)
- output = self.nodelist.render(context)
- context.pop()
- return output
+ with context.push(**values):
+ return self.nodelist.render(context)
+
@register.tag
def autoescape(parser, token):
diff --git a/django/template/loader.py b/django/template/loader.py
index 6df4e43c4f..44b8f600fb 100644
--- a/django/template/loader.py
+++ b/django/template/loader.py
@@ -164,11 +164,8 @@ def render_to_string(template_name, dictionary=None, context_instance=None):
return t.render(Context(dictionary))
# Add the dictionary to the context stack, ensuring it gets removed again
# to keep the context_instance in the same state it started in.
- context_instance.update(dictionary)
- try:
+ with context_instance.push(dictionary):
return t.render(context_instance)
- finally:
- context_instance.pop()
def select_template(template_name_list):
"Given a list of template names, returns the first that can be loaded."
diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
index 767f0e5ff8..406775da9d 100644
--- a/django/template/loader_tags.py
+++ b/django/template/loader_tags.py
@@ -47,22 +47,21 @@ class BlockNode(Node):
def render(self, context):
block_context = context.render_context.get(BLOCK_CONTEXT_KEY)
- context.push()
- if block_context is None:
- context['block'] = self
- result = self.nodelist.render(context)
- else:
- push = block = block_context.pop(self.name)
- if block is None:
- block = self
- # Create new block so we can store context without thread-safety issues.
- block = BlockNode(block.name, block.nodelist)
- block.context = context
- context['block'] = block
- result = block.nodelist.render(context)
- if push is not None:
- block_context.push(self.name, push)
- context.pop()
+ with context.push():
+ if block_context is None:
+ context['block'] = self
+ result = self.nodelist.render(context)
+ else:
+ push = block = block_context.pop(self.name)
+ if block is None:
+ block = self
+ # Create new block so we can store context without thread-safety issues.
+ block = BlockNode(block.name, block.nodelist)
+ block.context = context
+ context['block'] = block
+ result = block.nodelist.render(context)
+ if push is not None:
+ block_context.push(self.name, push)
return result
def super(self):
@@ -133,10 +132,9 @@ class BaseIncludeNode(Node):
in six.iteritems(self.extra_context)])
if self.isolated_context:
return template.render(context.new(values))
- context.update(values)
- output = template.render(context)
- context.pop()
- return output
+ with context.push(**values):
+ return template.render(context)
+
class ConstantIncludeNode(BaseIncludeNode):
def __init__(self, template_path, *args, **kwargs):
diff --git a/django/test/client.py b/django/test/client.py
index 94cfada725..8dbd33ccf1 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -180,7 +180,11 @@ def encode_multipart(boundary, data):
def encode_file(boundary, key, file):
to_bytes = lambda s: force_bytes(s, settings.DEFAULT_CHARSET)
- content_type = mimetypes.guess_type(file.name)[0]
+ if hasattr(file, 'content_type'):
+ content_type = file.content_type
+ else:
+ content_type = mimetypes.guess_type(file.name)[0]
+
if content_type is None:
content_type = 'application/octet-stream'
return [
diff --git a/django/test/runner.py b/django/test/runner.py
index 6897906098..c7881ada63 100644
--- a/django/test/runner.py
+++ b/django/test/runner.py
@@ -238,6 +238,7 @@ def setup_databases(verbosity, interactive, **kwargs):
mirrored_aliases = {}
test_databases = {}
dependencies = {}
+ default_sig = connections[DEFAULT_DB_ALIAS].creation.test_db_signature()
for alias in connections:
connection = connections[alias]
if connection.settings_dict['TEST_MIRROR']:
@@ -259,7 +260,7 @@ def setup_databases(verbosity, interactive, **kwargs):
dependencies[alias] = (
connection.settings_dict['TEST_DEPENDENCIES'])
else:
- if alias != DEFAULT_DB_ALIAS:
+ if alias != DEFAULT_DB_ALIAS and connection.creation.test_db_signature() != default_sig:
dependencies[alias] = connection.settings_dict.get(
'TEST_DEPENDENCIES', [DEFAULT_DB_ALIAS])
@@ -271,15 +272,16 @@ def setup_databases(verbosity, interactive, **kwargs):
test_databases.items(), dependencies):
test_db_name = None
# Actually create the database for the first connection
-
for alias in aliases:
connection = connections[alias]
- old_names.append((connection, db_name, True))
if test_db_name is None:
test_db_name = connection.creation.create_test_db(
verbosity, autoclobber=not interactive)
+ destroy = True
else:
connection.settings_dict['NAME'] = test_db_name
+ destroy = False
+ old_names.append((connection, db_name, destroy))
for alias, mirror_alias in mirrored_aliases.items():
mirrors.append((alias, connections[alias].settings_dict['NAME']))
diff --git a/django/test/signals.py b/django/test/signals.py
index a96bdff3b3..de8802b381 100644
--- a/django/test/signals.py
+++ b/django/test/signals.py
@@ -1,5 +1,6 @@
import os
import time
+import warnings
from django.conf import settings
from django.db import connections
@@ -9,11 +10,14 @@ from django.utils.functional import empty
template_rendered = Signal(providing_args=["template", "context"])
-setting_changed = Signal(providing_args=["setting", "value"])
+setting_changed = Signal(providing_args=["setting", "value", "enter"])
# Most setting_changed receivers are supposed to be added below,
# except for cases where the receiver is related to a contrib app.
+# Settings that may not work well when using 'override_settings' (#19031)
+COMPLEX_OVERRIDE_SETTINGS = set(['DATABASES'])
+
@receiver(setting_changed)
def update_connections_time_zone(**kwargs):
@@ -74,8 +78,15 @@ def language_changed(**kwargs):
if kwargs['setting'] == 'LOCALE_PATHS':
trans_real._translations = {}
+
@receiver(setting_changed)
def file_storage_changed(**kwargs):
if kwargs['setting'] in ('MEDIA_ROOT', 'DEFAULT_FILE_STORAGE'):
from django.core.files.storage import default_storage
default_storage._wrapped = empty
+
+
+@receiver(setting_changed)
+def complex_setting_changed(**kwargs):
+ if kwargs['enter'] and kwargs['setting'] in COMPLEX_OVERRIDE_SETTINGS:
+ warnings.warn("Overriding setting %s can lead to unexpected behaviour." % kwargs['setting'])
diff --git a/django/test/testcases.py b/django/test/testcases.py
index c5299bdabc..6f3f1c00e4 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -8,7 +8,6 @@ import json
import os
import re
import sys
-import select
import socket
import threading
import unittest
@@ -880,10 +879,19 @@ class TestCase(TransactionTestCase):
self.atomics[db_name].__exit__(None, None, None)
+class CheckCondition(object):
+ """Descriptor class for deferred condition checking"""
+ def __init__(self, cond_func):
+ self.cond_func = cond_func
+
+ def __get__(self, obj, objtype):
+ return self.cond_func()
+
+
def _deferredSkip(condition, reason):
def decorator(test_func):
if not (isinstance(test_func, type) and
- issubclass(test_func, TestCase)):
+ issubclass(test_func, unittest.TestCase)):
@wraps(test_func)
def skip_wrapper(*args, **kwargs):
if condition():
@@ -891,7 +899,9 @@ def _deferredSkip(condition, reason):
return test_func(*args, **kwargs)
test_item = skip_wrapper
else:
+ # Assume a class is decorated
test_item = test_func
+ test_item.__unittest_skip__ = CheckCondition(condition)
test_item.__unittest_skip_why__ = reason
return test_item
return decorator
@@ -924,104 +934,6 @@ class QuietWSGIRequestHandler(WSGIRequestHandler):
pass
-if sys.version_info >= (3, 3, 0):
- _ImprovedEvent = threading.Event
-elif sys.version_info >= (2, 7, 0):
- _ImprovedEvent = threading._Event
-else:
- class _ImprovedEvent(threading._Event):
- """
- Does the same as `threading.Event` except it overrides the wait() method
- with some code borrowed from Python 2.7 to return the set state of the
- event (see: http://hg.python.org/cpython/rev/b5aa8aa78c0f/). This allows
- to know whether the wait() method exited normally or because of the
- timeout. This class can be removed when Django supports only Python >= 2.7.
- """
-
- def wait(self, timeout=None):
- self._Event__cond.acquire()
- try:
- if not self._Event__flag:
- self._Event__cond.wait(timeout)
- return self._Event__flag
- finally:
- self._Event__cond.release()
-
-
-class StoppableWSGIServer(WSGIServer):
- """
- The code in this class is borrowed from the `SocketServer.BaseServer` class
- in Python 2.6. The important functionality here is that the server is non-
- blocking and that it can be shut down at any moment. This is made possible
- by the server regularly polling the socket and checking if it has been
- asked to stop.
- Note for the future: Once Django stops supporting Python 2.6, this class
- can be removed as `WSGIServer` will have this ability to shutdown on
- demand and will not require the use of the _ImprovedEvent class whose code
- is borrowed from Python 2.7.
- """
-
- def __init__(self, *args, **kwargs):
- super(StoppableWSGIServer, self).__init__(*args, **kwargs)
- self.__is_shut_down = _ImprovedEvent()
- self.__serving = False
-
- def serve_forever(self, poll_interval=0.5):
- """
- Handle one request at a time until shutdown.
-
- Polls for shutdown every poll_interval seconds.
- """
- self.__serving = True
- self.__is_shut_down.clear()
- while self.__serving:
- r, w, e = select.select([self], [], [], poll_interval)
- if r:
- self._handle_request_noblock()
- self.__is_shut_down.set()
-
- def shutdown(self):
- """
- Stops the serve_forever loop.
-
- Blocks until the loop has finished. This must be called while
- serve_forever() is running in another thread, or it will
- deadlock.
- """
- self.__serving = False
- if not self.__is_shut_down.wait(2):
- raise RuntimeError(
- "Failed to shutdown the live test server in 2 seconds. The "
- "server might be stuck or generating a slow response.")
-
- def handle_request(self):
- """Handle one request, possibly blocking.
- """
- fd_sets = select.select([self], [], [], None)
- if not fd_sets[0]:
- return
- self._handle_request_noblock()
-
- def _handle_request_noblock(self):
- """
- Handle one request, without blocking.
-
- I assume that select.select has returned that the socket is
- readable before this function was called, so there should be
- no risk of blocking in get_request().
- """
- try:
- request, client_address = self.get_request()
- except socket.error:
- return
- if self.verify_request(request, client_address):
- try:
- self.process_request(request, client_address)
- except Exception:
- self.handle_error(request, client_address)
- self.close_request(request)
-
-
class _MediaFilesHandler(StaticFilesHandler):
"""
Handler for serving the media files. This is a private class that is
@@ -1071,7 +983,7 @@ class LiveServerThread(threading.Thread):
# one that is free to use for the WSGI server.
for index, port in enumerate(self.possible_ports):
try:
- self.httpd = StoppableWSGIServer(
+ self.httpd = WSGIServer(
(self.host, port), QuietWSGIRequestHandler)
except WSGIServerException as e:
if (index + 1 < len(self.possible_ports) and
@@ -1128,7 +1040,7 @@ class LiveServerTestCase(TransactionTestCase):
for conn in connections.all():
# If using in-memory sqlite databases, pass the connections to
# the server thread.
- if (conn.settings_dict['ENGINE'].rsplit('.', 1)[-1] in ('sqlite3', 'spatialite')
+ if (conn.vendor == 'sqlite'
and conn.settings_dict['NAME'] == ':memory:'):
# Explicitly enable thread-shareability for this connection
conn.allow_thread_sharing = True
@@ -1180,7 +1092,7 @@ class LiveServerTestCase(TransactionTestCase):
# Restore sqlite connections' non-sharability
for conn in connections.all():
- if (conn.settings_dict['ENGINE'].rsplit('.', 1)[-1] in ('sqlite3', 'spatialite')
+ if (conn.vendor == 'sqlite'
and conn.settings_dict['NAME'] == ':memory:'):
conn.allow_thread_sharing = False
diff --git a/django/test/utils.py b/django/test/utils.py
index 70ce640239..dc3e5952ea 100644
--- a/django/test/utils.py
+++ b/django/test/utils.py
@@ -228,7 +228,7 @@ class override_settings(object):
settings._wrapped = override
for key, new_value in self.options.items():
setting_changed.send(sender=settings._wrapped.__class__,
- setting=key, value=new_value)
+ setting=key, value=new_value, enter=True)
def disable(self):
settings._wrapped = self.wrapped
@@ -236,7 +236,7 @@ class override_settings(object):
for key in self.options:
new_value = getattr(settings, key, None)
setting_changed.send(sender=settings._wrapped.__class__,
- setting=key, value=new_value)
+ setting=key, value=new_value, enter=False)
def compare_xml(want, got):
diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py
index 7eba842a89..de32b9f8cc 100644
--- a/django/utils/feedgenerator.py
+++ b/django/utils/feedgenerator.py
@@ -114,11 +114,11 @@ class SyndicationFeed(object):
def add_item(self, title, link, description, author_email=None,
author_name=None, author_link=None, pubdate=None, comments=None,
unique_id=None, unique_id_is_permalink=None, enclosure=None,
- categories=(), item_copyright=None, ttl=None, **kwargs):
+ categories=(), item_copyright=None, ttl=None, updateddate=None, **kwargs):
"""
Adds an item to the feed. All args are expected to be Python Unicode
- objects except pubdate, which is a datetime.datetime object, and
- enclosure, which is an instance of the Enclosure class.
+ objects except pubdate and updateddate, which are datetime.datetime
+ objects, and enclosure, which is an instance of the Enclosure class.
"""
to_unicode = lambda s: force_text(s, strings_only=True)
if categories:
@@ -134,6 +134,7 @@ class SyndicationFeed(object):
'author_name': to_unicode(author_name),
'author_link': iri_to_uri(author_link),
'pubdate': pubdate,
+ 'updateddate': updateddate,
'comments': to_unicode(comments),
'unique_id': to_unicode(unique_id),
'unique_id_is_permalink': unique_id_is_permalink,
@@ -191,15 +192,20 @@ class SyndicationFeed(object):
def latest_post_date(self):
"""
- Returns the latest item's pubdate. If none of them have a pubdate,
- this returns the current date/time.
+ Returns the latest item's pubdate or updateddate. If no items
+ have either of these attributes this returns the current date/time.
"""
- updates = [i['pubdate'] for i in self.items if i['pubdate'] is not None]
- if len(updates) > 0:
- updates.sort()
- return updates[-1]
- else:
- return datetime.datetime.now()
+ latest_date = None
+ date_keys = ('updateddate', 'pubdate')
+
+ for item in self.items:
+ for date_key in date_keys:
+ item_date = item.get(date_key)
+ if item_date:
+ if latest_date is None or item_date > latest_date:
+ latest_date = item_date
+
+ return latest_date or datetime.datetime.now()
class Enclosure(object):
"Represents an RSS enclosure"
@@ -349,8 +355,12 @@ class Atom1Feed(SyndicationFeed):
def add_item_elements(self, handler, item):
handler.addQuickElement("title", item['title'])
handler.addQuickElement("link", "", {"href": item['link'], "rel": "alternate"})
+
if item['pubdate'] is not None:
- handler.addQuickElement("updated", rfc3339_date(item['pubdate']))
+ handler.addQuickElement('published', rfc3339_date(item['pubdate']))
+
+ if item['updateddate'] is not None:
+ handler.addQuickElement('updated', rfc3339_date(item['updateddate']))
# Author information.
if item['author_name'] is not None:
diff --git a/django/utils/functional.py b/django/utils/functional.py
index 0606c775ef..e23bd3ff80 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -403,9 +403,8 @@ def partition(predicate, values):
if sys.version_info >= (2, 7, 2):
from functools import total_ordering
else:
- # For Python < 2.7.2. Python 2.6 does not have total_ordering, and
- # total_ordering in 2.7 versions prior to 2.7.2 is buggy. See
- # http://bugs.python.org/issue10042 for details. For these versions use
+ # For Python < 2.7.2. total_ordering in versions prior to 2.7.2 is buggy.
+ # See http://bugs.python.org/issue10042 for details. For these versions use
# code borrowed from Python 2.7.3.
def total_ordering(cls):
"""Class decorator that fills in missing ordering methods"""
diff --git a/django/utils/image.py b/django/utils/image.py
index 2fd0c6e6b0..dd2fab6197 100644
--- a/django/utils/image.py
+++ b/django/utils/image.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
To provide a shim layer over Pillow/PIL situation until the PIL support is
-removed.
+removed. See #19934.
Combinations To Account For
@@ -132,6 +132,8 @@ def _detect_image_library():
try:
from PIL import ImageFile as PILImageFile
except ImportError:
+ # This import cannot fail unless Pillow/PIL install is completely
+ # broken (e.g. missing Python modules).
import ImageFile as PILImageFile
# Finally, warn about deprecation...
diff --git a/django/utils/itercompat.py b/django/utils/itercompat.py
index 1df1e57cb2..096f808671 100644
--- a/django/utils/itercompat.py
+++ b/django/utils/itercompat.py
@@ -4,9 +4,6 @@ Where possible, we try to use the system-native version and only fall back to
these implementations if necessary.
"""
-import collections
-import sys
-
def is_iterable(x):
"A implementation independent way of checking for iterables"
@@ -16,14 +13,3 @@ def is_iterable(x):
return False
else:
return True
-
-def is_iterator(x):
- """An implementation independent way of checking for iterators
-
- Python 2.6 has a different implementation of collections.Iterator which
- accepts anything with a `next` method. 2.7+ requires and `__iter__` method
- as well.
- """
- if sys.version_info >= (2, 7):
- return isinstance(x, collections.Iterator)
- return isinstance(x, collections.Iterator) and hasattr(x, '__iter__')
diff --git a/django/utils/timesince.py b/django/utils/timesince.py
index 46c387f262..8fb0f64e09 100644
--- a/django/utils/timesince.py
+++ b/django/utils/timesince.py
@@ -17,7 +17,8 @@ def timesince(d, now=None, reversed=False):
displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are
possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not.
- Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
+ Adapted from
+ http://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
"""
chunks = (
(60 * 60 * 24 * 365, ungettext_lazy('%d year', '%d years')),
diff --git a/docs/faq/admin.txt b/docs/faq/admin.txt
index ec40754094..dbcd725a8d 100644
--- a/docs/faq/admin.txt
+++ b/docs/faq/admin.txt
@@ -82,7 +82,7 @@ How can I customize the functionality of the admin interface?
You've got several options. If you want to piggyback on top of an add/change
form that Django automatically generates, you can attach arbitrary JavaScript
modules to the page via the model's class Admin :ref:`js parameter
-<modeladmin-media-definitions>`. That parameter is a list of URLs, as strings,
+<modeladmin-asset-definitions>`. That parameter is a list of URLs, as strings,
pointing to JavaScript modules that will be included within the admin form via
a ``<script>`` tag.
diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt
index 54913a887a..af652de78c 100644
--- a/docs/howto/custom-model-fields.txt
+++ b/docs/howto/custom-model-fields.txt
@@ -249,7 +249,7 @@ appropriate Python object. The details of how this happens internally are a
little complex, but the code you need to write in your ``Field`` class is
simple: make sure your field subclass uses a special metaclass:
-For example::
+For example, on Python 2::
class HandField(models.Field):
@@ -258,7 +258,21 @@ For example::
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
- # ...
+ ...
+
+On Python 3, in lieu of setting the ``__metaclass__`` attribute, add
+``metaclass`` to the class definition::
+
+ class HandField(models.Field, metaclass=models.SubfieldBase):
+ ...
+
+If you want your code to work on Python 2 & 3, you can use
+:func:`six.with_metaclass`::
+
+ from django.utils.six import with_metaclass
+
+ class HandField(with_metaclass(models.SubfieldBase, models.Field)):
+ ...
This ensures that the :meth:`.to_python` method, documented below, will always
be called when the attribute is initialized.
@@ -300,6 +314,13 @@ descriptive text in a ``description`` class attribute of your custom field. In
the above example, the description displayed by the ``admindocs``
application for a ``HandField`` will be 'A hand of cards (bridge style)'.
+In the :mod:`django.contrib.admindocs` display, the field description is
+interpolated with ``field.__dict__`` which allows the description to
+incorporate arguments of the field. For example, the description for
+:class:`~django.db.models.CharField` is::
+
+ description = _("String (up to %(max_length)s)")
+
Useful methods
--------------
@@ -696,9 +717,9 @@ smoothly:
behavior of the field code is to call
:func:`~django.utils.encoding.force_text` on the value. (In our
examples in this document, ``value`` would be a ``Hand`` instance, not a
- ``HandField``). So if your ``__unicode__()`` method automatically
- converts to the string form of your Python object, you can save yourself
- a lot of work.
+ ``HandField``). So if your ``__unicode__()`` method (``__str__()`` on
+ Python 3) automatically converts to the string form of your Python object,
+ you can save yourself a lot of work.
Writing a ``FileField`` subclass
diff --git a/docs/howto/deployment/checklist.txt b/docs/howto/deployment/checklist.txt
index 1a235673e8..7d382401a0 100644
--- a/docs/howto/deployment/checklist.txt
+++ b/docs/howto/deployment/checklist.txt
@@ -217,9 +217,9 @@ This setting is required if you're using the :ttag:`ssi` template tag.
Python Options
==============
-If you're using Python 2.6.8+, it's strongly recommended that you invoke the
-Python process running your Django application using the `-R`_ option or with
-the :envvar:`PYTHONHASHSEED` environment variable set to ``random``.
+It's strongly recommended that you invoke the Python process running your
+Django application using the `-R`_ option or with the
+:envvar:`PYTHONHASHSEED` environment variable set to ``random``.
These options help protect your site from denial-of-service (DoS)
attacks triggered by carefully crafted inputs. Such an attack can
diff --git a/docs/howto/deployment/wsgi/uwsgi.txt b/docs/howto/deployment/wsgi/uwsgi.txt
index 3e9b47e8f9..59dddda418 100644
--- a/docs/howto/deployment/wsgi/uwsgi.txt
+++ b/docs/howto/deployment/wsgi/uwsgi.txt
@@ -109,6 +109,6 @@ Example ini configuration file usage::
uwsgi --ini uwsgi.ini
See the uWSGI docs on `managing the uWSGI process`_ for information on
-starting, stoping and reloading the uWSGI workers.
+starting, stopping and reloading the uWSGI workers.
.. _managing the uWSGI process: http://projects.unbit.it/uwsgi/wiki/Management
diff --git a/docs/howto/jython.txt b/docs/howto/jython.txt
index 461a5d3804..96a3d64251 100644
--- a/docs/howto/jython.txt
+++ b/docs/howto/jython.txt
@@ -4,13 +4,6 @@ Running Django on Jython
.. index:: Jython, Java, JVM
-.. admonition:: Python 2.6 support
-
- Django 1.5 has dropped support for Python 2.5. Therefore, you have to use
- a Jython 2.7 alpha release if you want to use Django 1.5 with Jython.
- Please use Django 1.4 if you want to keep using Django on a stable Jython
- version.
-
Jython_ is an implementation of Python that runs on the Java platform (JVM).
Django runs cleanly on Jython version 2.5 or later, which means you can deploy
Django on any Java platform.
@@ -22,7 +15,7 @@ This document will get you up and running with Django on top of Jython.
Installing Jython
=================
-Django works with Jython versions 2.5b3 and higher. Download Jython at
+Django works with Jython versions 2.5b3 and higher. Download Jython at
http://www.jython.org/.
Creating a servlet container
diff --git a/docs/howto/upgrade-version.txt b/docs/howto/upgrade-version.txt
index 8777f433f9..fd018d4342 100644
--- a/docs/howto/upgrade-version.txt
+++ b/docs/howto/upgrade-version.txt
@@ -89,3 +89,11 @@ Deployment
When you are sufficiently confident your app works with the new version of
Django, you're ready to go ahead and :doc:`deploy </howto/deployment/index>`
your upgraded Django project.
+
+If you are using caching provided by Django, you should consider clearing your
+cache after upgrading. Otherwise you may run into problems, for example, if you
+are caching pickled objects as these objects are not guaranteed to be
+pickle-compatible across Django versions. A past instance of incompatibility
+was caching pickled :class:`~django.http.HttpResponse` objects, either
+directly or indirectly via the :func:`~django.views.decorators.cache.cache_page`
+decorator.
diff --git a/docs/internals/contributing/triaging-tickets.txt b/docs/internals/contributing/triaging-tickets.txt
index 7bb59bc329..2aa8aaedba 100644
--- a/docs/internals/contributing/triaging-tickets.txt
+++ b/docs/internals/contributing/triaging-tickets.txt
@@ -175,6 +175,9 @@ This means the ticket has an associated
:doc:`patch<writing-code/submitting-patches>`. These will be reviewed
to see if the patch is "good".
+The following three fields (Needs documentation, Needs tests,
+Patch needs improvement) apply only if a patch has been supplied.
+
Needs documentation
~~~~~~~~~~~~~~~~~~~
diff --git a/docs/internals/contributing/writing-code/coding-style.txt b/docs/internals/contributing/writing-code/coding-style.txt
index 21146600b4..437d2f7ec5 100644
--- a/docs/internals/contributing/writing-code/coding-style.txt
+++ b/docs/internals/contributing/writing-code/coding-style.txt
@@ -124,13 +124,16 @@ Model style
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=40)
+* If you define a ``__str__`` method (previously ``__unicode__`` before Python 3
+ was supported), decorate the model class with
+ :func:`~django.utils.encoding.python_2_unicode_compatible`.
+
* The order of model inner classes and standard methods should be as
follows (noting that these are not all required):
* All database fields
* Custom manager attributes
* ``class Meta``
- * ``def __unicode__()``
* ``def __str__()``
* ``def save()``
* ``def get_absolute_url()``
diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt
index 0737b84888..7ee65cd6fa 100644
--- a/docs/internals/contributing/writing-code/unit-tests.txt
+++ b/docs/internals/contributing/writing-code/unit-tests.txt
@@ -136,23 +136,44 @@ Running all the tests
If you want to run the full suite of tests, you'll need to install a number of
dependencies:
+* bcrypt_
+* docutils_
+* Pillow_
* PyYAML_
+* pytz_
* setuptools_
* memcached_, plus a :ref:`supported Python binding <memcached>`
* gettext_ (:ref:`gettext_on_windows`)
-* selenium_ (if also using Python >= 2.6)
+* selenium_
+
+You can find these dependencies in `pip requirements files`_ inside the
+``tests/requirements`` directory of the Django source tree and install them
+like so::
+
+ pip install -r tests/requirements/py2.txt # Python 3: py3.txt
+
+You can also install the database adapter(s) of your choice using
+``oracle.txt``, ``mysql.txt``, or ``postgres.txt``.
If you want to test the memcached cache backend, you'll also need to define
a :setting:`CACHES` setting that points at your memcached instance.
+To run the GeoDjango tests, you will need to :doc:`setup a spatial database
+and install the Geospatial libraries</ref/contrib/gis/install/index>`.
+
Each of these dependencies is optional. If you're missing any of them, the
associated tests will be skipped.
+.. _bcrypt: https://pypi.python.org/pypi/bcrypt
+.. _docutils: https://pypi.python.org/pypi/docutils
+.. _Pillow: https://pypi.python.org/pypi/Pillow/
.. _PyYAML: http://pyyaml.org/wiki/PyYAML
+.. _pytz: https://pypi.python.org/pypi/pytz/
.. _setuptools: http://pypi.python.org/pypi/setuptools/
.. _memcached: http://memcached.org/
.. _gettext: http://www.gnu.org/software/gettext/manual/gettext.html
.. _selenium: http://pypi.python.org/pypi/selenium
+.. _pip requirements files: http://www.pip-installer.org/en/latest/requirements.html
Code coverage
~~~~~~~~~~~~~
diff --git a/docs/internals/security.txt b/docs/internals/security.txt
index 7121ff31ec..486b2c9968 100644
--- a/docs/internals/security.txt
+++ b/docs/internals/security.txt
@@ -19,7 +19,7 @@ security@djangoproject.com**.
Most normal bugs in Django are reported to `our public Trac
instance`_, but due to the sensitive nature of security issues, we ask
-that they *not* be publicly reported in this fashion.
+that they **not** be publicly reported in this fashion.
Instead, if you believe you've found something in Django which has
security implications, please send a description of the issue via
@@ -28,15 +28,17 @@ reaches a subset of the core development team, who can forward
security issues into the private committers' mailing list for broader
discussion if needed.
-You can send encrypted email to this address; the public key ID for
-``security@djangoproject.com`` is ``0xfcb84b8d1d17f80b``, and this
-public key is available from most commonly-used keyservers.
-
Once you've submitted an issue via email, you should receive an
acknowledgment from a member of the Django development team within 48
hours, and depending on the action to be taken, you may receive
further followup emails.
+.. note::
+
+ If you want to send an encrypted email (*optional*), the public key ID for
+ ``security@djangoproject.com`` is ``0xfcb84b8d1d17f80b``, and this public
+ key is available from most commonly-used keyservers.
+
.. _our public Trac instance: https://code.djangoproject.com/query
.. _security-support:
diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt
index 77838ffcaa..55366fb2c6 100644
--- a/docs/intro/overview.txt
+++ b/docs/intro/overview.txt
@@ -31,6 +31,7 @@ the file ``mysite/news/models.py``::
class Reporter(models.Model):
full_name = models.CharField(max_length=70)
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.full_name
@@ -40,6 +41,7 @@ the file ``mysite/news/models.py``::
content = models.TextField()
reporter = models.ForeignKey(Reporter)
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.headline
diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt
index 879cda913a..7fa1ffc8d9 100644
--- a/docs/intro/reusable-apps.txt
+++ b/docs/intro/reusable-apps.txt
@@ -255,9 +255,7 @@ working. We'll now fix this by installing our new ``django-polls`` package.
installs have a lot of advantages over installing the package system-wide,
such as being usable on systems where you don't have administrator access
as well as preventing the package from affecting system services and other
- users of the machine. Python 2.6 added support for user libraries, so if
- you are using an older version this won't work, but Django 1.5 requires
- Python 2.6 or newer anyway.
+ users of the machine.
Note that per-user installations can still affect the behavior of system
tools that run as that user, so ``virtualenv`` is a more robust solution
diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt
index 6e5988b15a..f2b01758aa 100644
--- a/docs/intro/tutorial01.txt
+++ b/docs/intro/tutorial01.txt
@@ -99,7 +99,7 @@ Let's look at what :djadmin:`startproject` created::
These files are:
-* The outer :file:`mysite/` directory is just a container for your
+* The outer :file:`mysite/` root directory is just a container for your
project. Its name doesn't matter to Django; you can rename it to anything
you like.
@@ -109,7 +109,7 @@ These files are:
* The inner :file:`mysite/` directory is the actual Python package for your
project. Its name is the Python package name you'll need to use to import
- anything inside it (e.g. ``import mysite.settings``).
+ anything inside it (e.g. ``mysite.urls``).
* :file:`mysite/__init__.py`: An empty file that tells Python that this
directory should be considered a Python package. (Read `more about
diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt
index 91409848cf..f0c39e2c93 100644
--- a/docs/intro/tutorial03.txt
+++ b/docs/intro/tutorial03.txt
@@ -182,7 +182,7 @@ slightly different, because they take an argument::
def vote(request, poll_id):
return HttpResponse("You're voting on poll %s." % poll_id)
-Wire these news views into the ``polls.urls`` module by adding the following
+Wire these new views into the ``polls.urls`` module by adding the following
:func:`~django.conf.urls.url` calls::
from django.conf.urls import patterns, url
diff --git a/docs/ref/class-based-views/generic-display.txt b/docs/ref/class-based-views/generic-display.txt
index 7fc95ac5d2..24006d48d3 100644
--- a/docs/ref/class-based-views/generic-display.txt
+++ b/docs/ref/class-based-views/generic-display.txt
@@ -70,7 +70,7 @@ DetailView
<p>{{ object.content }}</p>
<p>Reporter: {{ object.reporter }}</p>
<p>Published: {{ object.pub_date|date }}</p>
- <p>Date: {{ object.now|date }}</p>
+ <p>Date: {{ now|date }}</p>
ListView
--------
diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt
index c79f978850..65d096921f 100644
--- a/docs/ref/contrib/admin/actions.txt
+++ b/docs/ref/contrib/admin/actions.txt
@@ -57,6 +57,7 @@ simple news application with an ``Article`` model::
body = models.TextField()
status = models.CharField(max_length=1, choices=STATUS_CHOICES)
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.title
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index 318ce297a2..e5e9428805 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -438,7 +438,8 @@ subclass::
list_display = ('first_name', 'last_name')
If you don't set ``list_display``, the admin site will display a single
- column that displays the ``__unicode__()`` representation of each object.
+ column that displays the ``__unicode__()`` (``__str__()`` on Python 3)
+ representation of each object.
You have four possible values that can be used in ``list_display``:
@@ -488,7 +489,7 @@ subclass::
A few special cases to note about ``list_display``:
* If the field is a ``ForeignKey``, Django will display the
- ``__unicode__()`` of the related object.
+ ``__unicode__()`` (``__str__()`` on Python 3) of the related object.
* ``ManyToManyField`` fields aren't supported, because that would
entail executing a separate SQL statement for each row in the table.
@@ -1553,13 +1554,13 @@ instances which allow you to easily customize the response data before
rendering. For more details, see the :doc:`TemplateResponse documentation
</ref/template-response>`.
-.. _modeladmin-media-definitions:
+.. _modeladmin-asset-definitions:
-``ModelAdmin`` media definitions
+``ModelAdmin`` asset definitions
--------------------------------
There are times where you would like add a bit of CSS and/or JavaScript to
-the add/change views. This can be accomplished by using a Media inner class
+the add/change views. This can be accomplished by using a ``Media`` inner class
on your ``ModelAdmin``::
class ArticleAdmin(admin.ModelAdmin):
@@ -1571,8 +1572,8 @@ on your ``ModelAdmin``::
The :doc:`staticfiles app </ref/contrib/staticfiles>` prepends
:setting:`STATIC_URL` (or :setting:`MEDIA_URL` if :setting:`STATIC_URL` is
-``None``) to any media paths. The same rules apply as :ref:`regular media
-definitions on forms <form-media-paths>`.
+``None``) to any asset paths. The same rules apply as :ref:`regular asset
+definitions on forms <form-asset-paths>`.
jQuery
~~~~~~
@@ -1712,9 +1713,9 @@ The ``InlineModelAdmin`` class adds:
.. attribute:: InlineModelAdmin.formset
- This defaults to ``BaseInlineFormSet``. Using your own formset can give you
- many possibilities of customization. Inlines are built around
- :ref:`model formsets <model-formsets>`.
+ This defaults to :class:`~django.forms.models.BaseInlineFormSet`. Using
+ your own formset can give you many possibilities of customization. Inlines
+ are built around :ref:`model formsets <model-formsets>`.
.. attribute:: InlineModelAdmin.form
@@ -1790,8 +1791,9 @@ The ``InlineModelAdmin`` class adds:
.. method:: InlineModelAdmin.get_formset(self, request, obj=None, **kwargs)
- Returns a ``BaseInlineFormSet`` class for use in admin add/change views.
- See the example for :class:`ModelAdmin.get_formsets`.
+ Returns a :class:`~django.forms.models.BaseInlineFormSet` class for use in
+ admin add/change views. See the example for
+ :class:`ModelAdmin.get_formsets`.
.. method:: InlineModelAdmin.get_extra(self, request, obj=None, **kwargs)
diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt
index 199401c64a..fcd66a5b03 100644
--- a/docs/ref/contrib/contenttypes.txt
+++ b/docs/ref/contrib/contenttypes.txt
@@ -270,6 +270,7 @@ A simple example is a tagging system, which might look like this::
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.tag
diff --git a/docs/ref/contrib/gis/commands.txt b/docs/ref/contrib/gis/commands.txt
index 015a1f9741..3bacb03bc1 100644
--- a/docs/ref/contrib/gis/commands.txt
+++ b/docs/ref/contrib/gis/commands.txt
@@ -65,8 +65,8 @@ of using ``ogrinspect`` :ref:`in the tutorial <ogrinspect-intro>`.
.. django-admin-option:: --name-field <name_field>
- Generates a ``__unicode__`` routine on the model that will return the
- the given field name.
+ Generates a ``__unicode__`` routine (``__str__`` on Python 3) on the model
+ that will return the the given field name.
.. django-admin-option:: --no-imports
diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt
index c68030673b..5c57773f3e 100644
--- a/docs/ref/contrib/gis/gdal.txt
+++ b/docs/ref/contrib/gis/gdal.txt
@@ -80,7 +80,7 @@ each feature in that layer.
>>> from django.contrib.gis.gdal import DataSource
>>> ds = DataSource(CITIES_PATH)
>>> ds.name # The exact filename may be different on your computer
- '/usr/local/lib/python2.6/site-packages/django/contrib/gis/tests/data/cities/cities.shp'
+ '/usr/local/lib/python2.7/site-packages/django/contrib/gis/tests/data/cities/cities.shp'
>>> ds.layer_count # This file only contains one layer
1
diff --git a/docs/ref/contrib/gis/install/geolibs.txt b/docs/ref/contrib/gis/install/geolibs.txt
index c9a1b405a3..508bf7809c 100644
--- a/docs/ref/contrib/gis/install/geolibs.txt
+++ b/docs/ref/contrib/gis/install/geolibs.txt
@@ -12,12 +12,30 @@ Program Description Required
======================== ==================================== ================================ ==========================
:ref:`GEOS <ref-geos>` Geometry Engine Open Source Yes 3.3, 3.2, 3.1, 3.0
`PROJ.4`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 4.8, 4.7, 4.6, 4.5, 4.4
-:ref:`GDAL <ref-gdal>` Geospatial Data Abstraction Library No (but, required for SQLite) 1.9, 1.8, 1.7, 1.6, 1.5
+:ref:`GDAL <ref-gdal>` Geospatial Data Abstraction Library No (but, required for SQLite) 1.9, 1.8, 1.7, 1.6
:ref:`GeoIP <ref-geoip>` IP-based geolocation library No 1.4
`PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 2.0, 1.5, 1.4, 1.3
`SpatiaLite`__ Spatial extensions for SQLite Yes (SQLite only) 3.0, 2.4, 2.3
======================== ==================================== ================================ ==========================
+..
+ Libs release dates:
+ GEOS 3.0.0 2008-08-14
+ GEOS 3.1.0 2009-03-11
+ GEOS 3.2.0 2009-12-14
+ GEOS 3.3.0 2011-05-30
+ GDAL 1.6.0 2008-12-04
+ GDAL 1.7.1 2010-02-08
+ GDAL 1.8.0 2011-01-13
+ GDAL 1.9.0 2012-01-03
+ PostGIS 1.3.0 2007-08-09
+ PostGIS 1.4.0 2009-07-24
+ PostGIS 1.5.0 2010-02-04
+ PostGIS 2.0.0 2012-04-03
+ SpatiaLite 2.3.0 2009-04-06
+ Spatialite 2.4.0 2010-11-14
+ Spatialite 3.0.0 2011-12-30
+
.. admonition:: Install GDAL
While :ref:`gdalbuild` is technically not required, it is *recommended*.
diff --git a/docs/ref/contrib/gis/install/index.txt b/docs/ref/contrib/gis/install/index.txt
index 62369d8253..3cf8a29822 100644
--- a/docs/ref/contrib/gis/install/index.txt
+++ b/docs/ref/contrib/gis/install/index.txt
@@ -239,13 +239,14 @@ Python
^^^^^^
Although OS X comes with Python installed, users can use framework
-installers (`2.6`__ and `2.7`__ are available) provided by
+installers (`2.7`__, `3.2`__ and `3.3`__ are available) provided by
the Python Software Foundation. An advantage to using the installer is
that OS X's Python will remain "pristine" for internal operating system
use.
-__ http://python.org/ftp/python/2.6.6/python-2.6.6-macosx10.3.dmg
-__ http://python.org/ftp/python/2.7.3/
+__ http://python.org/ftp/python/2.7.5/
+__ http://python.org/ftp/python/3.2.5/
+__ http://python.org/ftp/python/3.3.2/
.. note::
@@ -284,7 +285,7 @@ William Kyngesburye provides a number of `geospatial library binary packages`__
that make it simple to get GeoDjango installed on OS X without compiling
them from source. However, the `Apple Developer Tools`_ are still necessary
for compiling the Python database adapters :ref:`psycopg2_kyngchaos` (for PostGIS)
-and :ref:`pysqlite2` (for SpatiaLite).
+and ``pysqlite2`` (for SpatiaLite).
.. note::
diff --git a/docs/ref/contrib/gis/install/spatialite.txt b/docs/ref/contrib/gis/install/spatialite.txt
index 941d559272..b6cf1943ae 100644
--- a/docs/ref/contrib/gis/install/spatialite.txt
+++ b/docs/ref/contrib/gis/install/spatialite.txt
@@ -105,56 +105,6 @@ Finally, do the same for the SpatiaLite tools::
__ http://www.gaia-gis.it/gaia-sins/libspatialite-sources/
-.. _pysqlite2:
-
-pysqlite2
-^^^^^^^^^
-
-If you are on Python 2.6, you will also have to compile pysqlite2, because
-``SpatiaLite`` must be loaded as an external extension, and the required
-``enable_load_extension`` method is only available in versions 2.5+ of
-pysqlite2. Thus, download pysqlite2 2.6, and untar::
-
- $ wget http://pysqlite.googlecode.com/files/pysqlite-2.6.3.tar.gz
- $ tar xzf pysqlite-2.6.3.tar.gz
- $ cd pysqlite-2.6.3
-
-Next, use a text editor (e.g., ``emacs`` or ``vi``) to edit the ``setup.cfg`` file
-to look like the following:
-
-.. code-block:: ini
-
- [build_ext]
- #define=
- include_dirs=/usr/local/include
- library_dirs=/usr/local/lib
- libraries=sqlite3
- #define=SQLITE_OMIT_LOAD_EXTENSION
-
-or if you are on Mac OS X:
-
-.. code-block:: ini
-
- [build_ext]
- #define=
- include_dirs=/Library/Frameworks/SQLite3.framework/unix/include
- library_dirs=/Library/Frameworks/SQLite3.framework/unix/lib
- libraries=sqlite3
- #define=SQLITE_OMIT_LOAD_EXTENSION
-
-.. note::
-
- The important thing here is to make sure you comment out the
- ``define=SQLITE_OMIT_LOAD_EXTENSION`` flag and that the ``include_dirs``
- and ``library_dirs`` settings are uncommented and set to the appropriate
- path if the SQLite header files and libraries are not in ``/usr/include``
- and ``/usr/lib``, respectively.
-
-After modifying ``setup.cfg`` appropriately, then run the ``setup.py`` script
-to build and install::
-
- $ sudo python setup.py install
-
.. _spatialite_macosx:
Mac OS X-specific instructions
diff --git a/docs/ref/contrib/gis/layermapping.txt b/docs/ref/contrib/gis/layermapping.txt
index dd0a41cbc0..381c7df133 100644
--- a/docs/ref/contrib/gis/layermapping.txt
+++ b/docs/ref/contrib/gis/layermapping.txt
@@ -60,6 +60,8 @@ Example
name = models.CharField(max_length=25) # corresponds to the 'str' field
poly = models.PolygonField(srid=4269) # we want our model in a different SRID
objects = models.GeoManager()
+
+ # On Python 3: def __str__(self):
def __unicode__(self):
return 'Name: %s' % self.name
diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt
index 56d90c8593..3df4db12a0 100644
--- a/docs/ref/contrib/gis/tutorial.txt
+++ b/docs/ref/contrib/gis/tutorial.txt
@@ -244,6 +244,7 @@ model to represent this data::
objects = models.GeoManager()
# Returns the string representation of the model.
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.name
diff --git a/docs/ref/contrib/syndication.txt b/docs/ref/contrib/syndication.txt
index 51d038d187..9a0fb5830e 100644
--- a/docs/ref/contrib/syndication.txt
+++ b/docs/ref/contrib/syndication.txt
@@ -17,7 +17,7 @@ you want to generate feeds outside of a Web context, or in some other
lower-level way.
.. _RSS: http://www.whatisrss.com/
-.. _Atom: http://www.atomenabled.org/
+.. _Atom: http://tools.ietf.org/html/rfc4287
The high-level framework
========================
@@ -815,6 +815,24 @@ This example illustrates all possible attributes and methods for a
item_pubdate = datetime.datetime(2005, 5, 3) # Hard-coded pubdate.
+ # ITEM UPDATED -- It's optional to use one of these three. This is a
+ # hook that specifies how to get the updateddate for a given item.
+ # In each case, the method/attribute should return a Python
+ # datetime.datetime object.
+
+ def item_updateddate(self, item):
+ """
+ Takes an item, as returned by items(), and returns the item's
+ updateddate.
+ """
+
+ def item_updateddate(self):
+ """
+ Returns the updateddated for every item in the feed.
+ """
+
+ item_updateddate = datetime.datetime(2005, 5, 3) # Hard-coded updateddate.
+
# ITEM CATEGORIES -- It's optional to use one of these three. This is
# a hook that specifies how to get the list of categories for a given
# item. In each case, the method/attribute should return an iterable
@@ -928,16 +946,22 @@ They share this interface:
* ``categories``
* ``item_copyright``
* ``ttl``
+ * ``updateddate``
Extra keyword arguments will be stored for `custom feed generators`_.
All parameters, if given, should be Unicode objects, except:
* ``pubdate`` should be a Python :class:`~datetime.datetime` object.
+ * ``updateddate`` should be a Python :class:`~datetime.datetime` object.
* ``enclosure`` should be an instance of
:class:`django.utils.feedgenerator.Enclosure`.
* ``categories`` should be a sequence of Unicode objects.
+ .. versionadded:: 1.7
+
+ The optional ``updateddate`` argument was added.
+
:meth:`.SyndicationFeed.write`
Outputs the feed in the given encoding to outfile, which is a file-like object.
diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt
index 4e5f136e2e..29f2f3972d 100644
--- a/docs/ref/databases.txt
+++ b/docs/ref/databases.txt
@@ -208,8 +208,8 @@ MySQL when using the MyISAM storage engine, see the next section.
Storage engines
---------------
-MySQL has several `storage engines`_ (previously called table types). You can
-change the default storage engine in the server configuration.
+MySQL has several `storage engines`_. You can change the default storage engine
+in the server configuration.
Until MySQL 5.5.4, the default engine was MyISAM_ [#]_. The main drawbacks of
MyISAM are that it doesn't support transactions or enforce foreign-key
@@ -232,7 +232,7 @@ running ``syncdb``::
.. _storage engines: http://dev.mysql.com/doc/refman/5.5/en/storage-engines.html
.. _MyISAM: http://dev.mysql.com/doc/refman/5.5/en/myisam-storage-engine.html
-.. _InnoDB: http://dev.mysql.com/doc/refman/5.5/en/innodb.html
+.. _InnoDB: http://dev.mysql.com/doc/refman/5.5/en/innodb-storage-engine.html
.. [#] Unless this was changed by the packager of your MySQL package. We've
had reports that the Windows Community Server installer sets up InnoDB as
@@ -718,9 +718,9 @@ as empty strings.
Threaded option
----------------
-If you plan to run Django in a multithreaded environment (e.g. Apache in Windows
-using the default MPM module), then you **must** set the ``threaded`` option of
-your Oracle database configuration to True::
+If you plan to run Django in a multithreaded environment (e.g. Apache using the
+the default MPM module on any modern operating system), then you **must** set
+the ``threaded`` option of your Oracle database configuration to True::
'OPTIONS': {
'threaded': True,
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index caee368e63..d16766618a 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -89,10 +89,10 @@ documentation for the :djadminopt:`--verbosity` option.
Available commands
==================
-checksetup
-----------
+check
+-----
-.. django-admin:: checksetup
+.. django-admin:: check
.. versionadded:: 1.6
@@ -311,7 +311,7 @@ you need to perform on the resulting ``models.py`` file is to change the
Python declaration of each one of these models to specify it is a
:attr:`managed <django.db.models.Options.managed>` one.
-This servers as an explicit opt-in to give your nascent Django project write
+This serves as an explicit opt-in to give your nascent Django project write
access to your precious data on a model by model basis.
The :djadminopt:`--database` option may be used to specify the
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index 3c17827800..7c1601d3ea 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -500,9 +500,15 @@ By default, ``auto_id`` is set to the string ``'id_%s'``.
.. attribute:: Form.label_suffix
-Normally, a colon (``:``) will be appended after any label name when a form is
-rendered. It's possible to change the colon to another character, or omit it
-entirely, using the ``label_suffix`` parameter::
+A translatable string (defaults to a colon (``:``) in English) that will be
+appended after any label name when a form is rendered.
+
+.. versionchanged:: 1.6
+
+ The default ``label_suffix`` is translatable.
+
+It's possible to customize that character, or omit it entirely, using the
+ ``label_suffix`` parameter::
>>> f = ContactForm(auto_id='id_for_%s', label_suffix='')
>>> print(f.as_ul())
@@ -518,7 +524,8 @@ entirely, using the ``label_suffix`` parameter::
<li><label for="id_for_cc_myself">Cc myself -></label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></li>
Note that the label suffix is added only if the last character of the
-label isn't a punctuation character (``.``, ``!``, ``?`` or ``:``)
+label isn't a punctuation character (in English, those are ``.``, ``!``, ``?``
+or ``:``).
Notes on field ordering
~~~~~~~~~~~~~~~~~~~~~~~
@@ -566,7 +573,8 @@ Customizing the error list format
By default, forms use ``django.forms.util.ErrorList`` to format validation
errors. If you'd like to use an alternate class for displaying errors, you can
-pass that in at construction time::
+pass that in at construction time (replace ``__unicode__`` by ``__str__`` on
+Python 3)::
>>> from django.forms.util import ErrorList
>>> class DivErrorList(ErrorList):
@@ -661,7 +669,7 @@ additional attributes for the ``<label>`` tag.
.. versionchanged:: 1.6
The label now includes the form's :attr:`~django.forms.Form.label_suffix`
- (a semicolon, by default).
+ (a colon, by default).
.. method:: BoundField.css_classes()
diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt
index 69e3aa71ad..ef4ed729bd 100644
--- a/docs/ref/forms/fields.txt
+++ b/docs/ref/forms/fields.txt
@@ -1000,12 +1000,12 @@ objects (in the case of ``ModelMultipleChoiceField``) into the
initial value, no empty choice is created (regardless of the value
of ``empty_label``).
- The ``__unicode__`` method of the model will be called to generate
- string representations of the objects for use in the field's choices;
- to provide customized representations, subclass ``ModelChoiceField``
- and override ``label_from_instance``. This method will receive a model
- object, and should return a string suitable for representing it. For
- example::
+ The ``__unicode__`` (``__str__`` on Python 3) method of the model will be
+ called to generate string representations of the objects for use in the
+ field's choices; to provide customized representations, subclass
+ ``ModelChoiceField`` and override ``label_from_instance``. This method will
+ receive a model object, and should return a string suitable for representing
+ it. For example::
from django.forms import ModelChoiceField
diff --git a/docs/ref/forms/models.txt b/docs/ref/forms/models.txt
index 840a896d75..1d2b468553 100644
--- a/docs/ref/forms/models.txt
+++ b/docs/ref/forms/models.txt
@@ -69,8 +69,8 @@ Model Form Functions
.. function:: inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, fk_name=None, fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, widgets=None, validate_max=False, localized_fields=None, labels=None, help_texts=None, error_messages=None)
Returns an ``InlineFormSet`` using :func:`modelformset_factory` with
- defaults of ``formset=BaseInlineFormSet``, ``can_delete=True``, and
- ``extra=3``.
+ defaults of ``formset=``:class:`~django.forms.models.BaseInlineFormSet`,
+ ``can_delete=True``, and ``extra=3``.
If your model has more than one :class:`~django.db.models.ForeignKey` to
the ``parent_model``, you must specify a ``fk_name``.
diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt
index 341574ad37..5b66776cfc 100644
--- a/docs/ref/forms/widgets.txt
+++ b/docs/ref/forms/widgets.txt
@@ -171,16 +171,15 @@ You can also set the HTML ``id`` using :attr:`~Widget.attrs`. See
Styling widget classes
^^^^^^^^^^^^^^^^^^^^^^
-With widgets, it is possible to add media (``css`` and ``javascript``)
+With widgets, it is possible to add assets (``css`` and ``javascript``)
and more deeply customize their appearance and behavior.
In a nutshell, you will need to subclass the widget and either
-:ref:`define a class "Media" <media-as-a-static-definition>` as a member of the
-subclass, or :ref:`create a property "media" <dynamic-property>`, returning an
-instance of that class.
+:ref:`define a "Media" inner class <assets-as-a-static-definition>` or
+:ref:`create a "media" property <dynamic-property>`.
These methods involve somewhat advanced Python programming and are described in
-detail in the :doc:`Form Media </topics/forms/media>` topic guide.
+detail in the :doc:`Form Assets </topics/forms/media>` topic guide.
.. _base-widget-classes:
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index f5c1058b17..869996643f 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -1184,10 +1184,10 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
.. class:: ManyToManyField(othermodel, [**options])
-A many-to-many relationship. Requires a positional argument: the class to which
-the model is related. This works exactly the same as it does for
-:class:`ForeignKey`, including all the options regarding :ref:`recursive
-<recursive-relationships>` and :ref:`lazy <lazy-relationships>` relationships.
+A many-to-many relationship. Requires a positional argument: the class to
+which the model is related, which works exactly the same as it does for
+:class:`ForeignKey`, including :ref:`recursive <recursive-relationships>` and
+:ref:`lazy <lazy-relationships>` relationships.
Related objects can be added, removed, or created with the field's
:class:`~django.db.models.fields.related.RelatedManager`.
diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt
index 17c9aa9fb7..f06866d9a1 100644
--- a/docs/ref/models/instances.txt
+++ b/docs/ref/models/instances.txt
@@ -430,6 +430,12 @@ Other model instance methods
A few object methods have special purposes.
+.. note::
+ On Python 3, as all strings are natively considered Unicode, only use the
+ ``__str__()`` method (the ``__unicode__()`` method is obsolete).
+ If you'd like compatibility with Python 2, you can decorate your model class
+ with :func:`~django.utils.encoding.python_2_unicode_compatible`.
+
``__unicode__``
---------------
diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
index 90099d13a3..d54af37e86 100644
--- a/docs/ref/models/options.txt
+++ b/docs/ref/models/options.txt
@@ -24,12 +24,17 @@ Available ``Meta`` options
.. attribute:: Options.app_label
- If a model exists outside of the standard :file:`models.py` (for instance,
- if the app's models are in submodules of ``myapp.models``), the model must
- define which app it is part of::
+ If a model exists outside of the standard locations (:file:`models.py` or
+ a ``models`` package in an app), the model must define which app it is part
+ of::
app_label = 'myapp'
+ .. versionadded:: 1.7
+
+ ``app_label`` is no longer required for models that are defined
+ in a ``models`` package within an app.
+
``db_table``
------------
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index 6fd8a42dee..3963785733 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -379,17 +379,14 @@ query spans multiple tables, it's possible to get duplicate results when a
:meth:`values()` together, be careful when ordering by fields not in the
:meth:`values()` call.
-You can pass positional arguments (``*fields``) in order to specify the names
-of fields to which the ``DISTINCT`` should apply. This translates to a
-``SELECT DISTINCT ON`` SQL query. Here's the difference. For a normal
-``distinct()`` call, the database compares *each* field in each row when
+On PostgreSQL only, you can pass positional arguments (``*fields``) in order to
+specify the names of fields to which the ``DISTINCT`` should apply. This
+translates to a ``SELECT DISTINCT ON`` SQL query. Here's the difference. For a
+normal ``distinct()`` call, the database compares *each* field in each row when
determining which rows are distinct. For a ``distinct()`` call with specified
field names, the database will only compare the specified field names.
.. note::
- This ability to specify field names is only available in PostgreSQL.
-
-.. note::
When you specify field names, you *must* provide an ``order_by()`` in the
QuerySet, and the fields in ``order_by()`` must start with the fields in
``distinct()``, in the same order.
@@ -398,7 +395,7 @@ field names, the database will only compare the specified field names.
value in column ``a``. If you don't specify an order, you'll get some
arbitrary row.
-Examples::
+Examples (those after the first will only work on PostgreSQL)::
>>> Author.objects.distinct()
[...]
@@ -817,6 +814,7 @@ For example, suppose you have these models::
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
+ # On Python 3: def __str__(self):
def __unicode__(self):
return u"%s (%s)" % (self.name, u", ".join([topping.name
for topping in self.toppings.all()]))
@@ -1332,9 +1330,9 @@ prepared to handle the exception if you are using manual primary keys.
get_or_create
~~~~~~~~~~~~~
-.. method:: get_or_create(**kwargs)
+.. method:: get_or_create(defaults=None, **kwargs)
-A convenience method for looking up an object with the given kwargs (may be
+A convenience method for looking up an object with the given ``kwargs`` (may be
empty if your model has defaults for all fields), creating one if necessary.
.. versionchanged:: 1.6
@@ -1345,8 +1343,7 @@ Returns a tuple of ``(object, created)``, where ``object`` is the retrieved or
created object and ``created`` is a boolean specifying whether a new object was
created.
-This is meant as a shortcut to boilerplatish code and is mostly useful for
-data-import scripts. For example::
+This is meant as a shortcut to boilerplatish code. For example::
try:
obj = Person.objects.get(first_name='John', last_name='Lennon')
@@ -1369,7 +1366,6 @@ found, ``get_or_create()`` will instantiate and save a new object, returning a
tuple of the new object and ``True``. The new object will be created roughly
according to this algorithm::
- defaults = kwargs.pop('defaults', {})
params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
params.update(defaults)
obj = self.model(**params)
@@ -1394,13 +1390,25 @@ when you're using manually specified primary keys. If an object needs to be
created and the key already exists in the database, an
:exc:`~django.db.IntegrityError` will be raised.
-Finally, a word on using ``get_or_create()`` in Django views. As mentioned
-earlier, ``get_or_create()`` is mostly useful in scripts that need to parse
-data and create new records if existing ones aren't available. But if you need
-to use ``get_or_create()`` in a view, please make sure to use it only in
-``POST`` requests unless you have a good reason not to. ``GET`` requests
-shouldn't have any effect on data; use ``POST`` whenever a request to a page
-has a side effect on your data. For more, see `Safe methods`_ in the HTTP spec.
+This method is atomic assuming correct usage, correct database configuration,
+and correct behavior of the underlying database. However, if uniqueness is not
+enforced at the database level for the ``kwargs`` used in a ``get_or_create``
+call (see :attr:`~django.db.models.Field.unique` or
+:attr:`~django.db.models.Options.unique_together`), this method is prone to a
+race-condition which can result in multiple rows with the same parameters being
+inserted simultaneously.
+
+If you are using MySQL, be sure to use the ``READ COMMITTED`` isolation level
+rather than ``REPEATABLE READ`` (the default), otherwise you may see cases
+where ``get_or_create`` will raise an :exc:`~django.db.IntegrityError` but the
+object won't appear in a subsequent :meth:`~django.db.models.query.QuerySet.get`
+call.
+
+Finally, a word on using ``get_or_create()`` in Django views: please make sure
+to use it only in ``POST`` requests unless you have a good reason not to
+``GET`` requests shouldn't have any effect on data; use ``POST`` whenever a
+request to a page as a side effect on your data. For more, see `Safe methods`_
+in the HTTP spec.
.. _Safe methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
@@ -1438,6 +1446,49 @@ has a side effect on your data. For more, see `Safe methods`_ in the HTTP spec.
chapter because it isn't related to that book, but it can't create it either
because ``title`` field should be unique.
+update_or_create
+~~~~~~~~~~~~~~~~
+
+.. method:: update_or_create(defaults=None, **kwargs)
+
+.. versionadded:: 1.7
+
+A convenience method for updating an object with the given ``kwargs``, creating
+a new one if necessary. The ``defaults`` is a dictionary of (field, value)
+pairs used to update the object.
+
+Returns a tuple of ``(object, created)``, where ``object`` is the created or
+updated object and ``created`` is a boolean specifying whether a new object was
+created.
+
+The ``update_or_create`` method tries to fetch an object from database based on
+the given ``kwargs``. If a match is found, it updates the fields passed in the
+``defaults`` dictionary.
+
+This is meant as a shortcut to boilerplatish code. For example::
+
+ try:
+ obj = Person.objects.get(first_name='John', last_name='Lennon')
+ for key, value in updated_values.iteritems():
+ setattr(obj, key, value)
+ obj.save()
+ except Person.DoesNotExist:
+ updated_values.update({'first_name': 'John', 'last_name': 'Lennon'})
+ obj = Person(**updated_values)
+ obj.save()
+
+This pattern gets quite unwieldy as the number of fields in a model goes up.
+The above example can be rewritten using ``update_or_create()`` like so::
+
+ obj, created = Person.objects.update_or_create(
+ first_name='John', last_name='Lennon', defaults=updated_values)
+
+For detailed description how names passed in ``kwargs`` are resolved see
+:meth:`get_or_create`.
+
+As described above in :meth:`get_or_create`, this method is prone to a
+race-condition which can result in multiple rows being inserted simultaneously
+if uniqueness is not enforced at the database level.
bulk_create
~~~~~~~~~~~
diff --git a/docs/ref/models/relations.txt b/docs/ref/models/relations.txt
index ffebe37193..5a7d68417c 100644
--- a/docs/ref/models/relations.txt
+++ b/docs/ref/models/relations.txt
@@ -48,6 +48,14 @@ Related objects reference
>>> e = Entry.objects.get(id=234)
>>> b.entry_set.add(e) # Associates Entry e with Blog b.
+ In the example above, ``e.save()`` is called to perform the update.
+ Using ``add()`` with a many-to-many relationship, however, will not
+ call any ``save()`` methods, but rather create the relationships
+ using :meth:`QuerySet.bulk_create()
+ <django.db.models.query.QuerySet.bulk_create>`. If you need to execute
+ some custom logic when a relationship is created, listen to the
+ :data:`~django.db.models.signals.m2m_changed` signal.
+
.. method:: create(**kwargs)
Creates a new object, saves it and puts it in the related object set.
@@ -86,6 +94,14 @@ Related objects reference
>>> e = Entry.objects.get(id=234)
>>> b.entry_set.remove(e) # Disassociates Entry e from Blog b.
+ Similar to :meth:`add()`, ``e.save()`` is called in the example above
+ to perform the update. Using ``remove()`` with a many-to-many
+ relationship, however, will delete the relationships using
+ :meth:`QuerySet.delete()<django.db.models.query.QuerySet.delete>` which
+ means no model ``save()`` methods are called; listen to the
+ :data:`~django.db.models.signals.m2m_changed` signal if you wish to
+ execute custom code when a relationship is deleted.
+
For :class:`~django.db.models.ForeignKey` objects, this method only
exists if ``null=True``. If the related field can't be set to ``None``
(``NULL``), then an object can't be removed from a relation without
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt
index f1c5702d36..060ec02e91 100644
--- a/docs/ref/request-response.txt
+++ b/docs/ref/request-response.txt
@@ -165,10 +165,11 @@ All attributes should be considered read-only, unless stated otherwise below.
.. attribute:: HttpRequest.user
- A ``django.contrib.auth.models.User`` object representing the currently
+ An object of type :setting:`AUTH_USER_MODEL` representing the currently
logged-in user. If the user isn't currently logged in, ``user`` will be set
- to an instance of ``django.contrib.auth.models.AnonymousUser``. You
- can tell them apart with ``is_authenticated()``, like so::
+ to an instance of :class:`django.contrib.auth.models.AnonymousUser`. You
+ can tell them apart with
+ :meth:`~django.contrib.auth.models.User.is_authenticated`, like so::
if request.user.is_authenticated():
# Do something for logged-in users.
@@ -176,8 +177,8 @@ All attributes should be considered read-only, unless stated otherwise below.
# Do something for anonymous users.
``user`` is only available if your Django installation has the
- ``AuthenticationMiddleware`` activated. For more, see
- :doc:`/topics/auth/index`.
+ :class:`~django.contrib.auth.middleware.AuthenticationMiddleware`
+ activated. For more, see :doc:`/topics/auth/index`.
.. attribute:: HttpRequest.session
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 30e7e84fdb..c970311342 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -1055,6 +1055,26 @@ EMAIL_USE_TLS
Default: ``False``
Whether to use a TLS (secure) connection when talking to the SMTP server.
+This is used for explicit TLS connections, generally on port 587. If you are
+experiencing hanging connections, see the implicit TLS setting
+:setting:`EMAIL_USE_SSL`.
+
+.. setting:: EMAIL_USE_SSL
+
+EMAIL_USE_SSL
+-------------
+
+.. versionadded:: 1.7
+
+Default: ``False``
+
+Whether to use an implicit TLS (secure) connection when talking to the SMTP
+server. In most email documentation this type of TLS connection is referred
+to as SSL. It is generally used on port 465. If you are experiencing problems,
+see the explicit TLS setting :setting:`EMAIL_USE_TLS`.
+
+Note that :setting:`EMAIL_USE_TLS`/:setting:`EMAIL_USE_SSL` are mutually
+exclusive, so only set one of those settings to ``True``.
.. setting:: FILE_CHARSET
@@ -2475,7 +2495,7 @@ URL to use when referring to static files located in :setting:`STATIC_ROOT`.
Example: ``"/static/"`` or ``"http://static.example.com/"``
If not ``None``, this will be used as the base path for
-:ref:`media definitions<form-media-paths>` and the
+:ref:`asset definitions<form-asset-paths>` (the ``Media`` class) and the
:doc:`staticfiles app</ref/contrib/staticfiles>`.
It must end in a slash if set to a non-empty value.
diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt
index 0253832b8d..d4f261cadb 100644
--- a/docs/ref/signals.txt
+++ b/docs/ref/signals.txt
@@ -553,7 +553,8 @@ This signal is sent when the value of a setting is changed through the
:func:`django.test.utils.override_settings` decorator/context manager.
It's actually sent twice: when the new value is applied ("setup") and when the
-original value is restored ("teardown").
+original value is restored ("teardown"). Use the ``enter`` argument to
+distinguish between the two.
Arguments sent with this signal:
@@ -567,6 +568,11 @@ Arguments sent with this signal:
The value of the setting after the change. For settings that initially
don't exist, in the "teardown" phase, ``value`` is ``None``.
+``enter``
+ .. versionadded:: 1.7
+
+ A boolean; ``True`` if the setting is applied, ``False`` if restored.
+
template_rendered
-----------------
diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt
index 160cdc7194..f7dd0121d1 100644
--- a/docs/ref/templates/api.txt
+++ b/docs/ref/templates/api.txt
@@ -2,6 +2,9 @@
The Django template language: For Python programmers
====================================================
+.. module:: django.template
+ :synopsis: Django's template system
+
This document explains the Django template system from a technical
perspective -- how it works and how to extend it. If you're just looking for
reference on the language syntax, see :doc:`/topics/templates`.
@@ -52,7 +55,7 @@ from the context and executing all block tags.
Using the template system
=========================
-.. class:: django.template.Template
+.. class:: Template
Using the template system in Python is a two-step process:
@@ -279,7 +282,7 @@ these variables resolve to the corresponding Python objects.
Playing with Context objects
----------------------------
-.. class:: django.template.Context
+.. class:: Context
Most of the time, you'll instantiate ``Context`` objects by passing in a
fully-populated dictionary to ``Context()``. But you can add and delete items
@@ -297,9 +300,9 @@ dictionary syntax::
>>> c['newvariable']
'hello'
-.. method:: pop()
-.. method:: push()
-.. exception:: django.template.ContextPopException
+.. method:: Context.pop()
+.. method:: Context.push()
+.. exception:: ContextPopException
A ``Context`` object is a stack. That is, you can ``push()`` and ``pop()`` it.
If you ``pop()`` too much, it'll raise
@@ -322,6 +325,31 @@ If you ``pop()`` too much, it'll raise
...
django.template.ContextPopException
+.. versionadded:: 1.7
+
+You can also use ``push()`` as a context manager to ensure a matching ``pop()``
+is called.
+
+ >>> c = Context()
+ >>> c['foo'] = 'first level'
+ >>> with c.push():
+ >>> c['foo'] = 'second level'
+ >>> c['foo']
+ 'second level'
+ >>> c['foo']
+ 'first level'
+
+All arguments passed to ``push()`` will be passed to the ``dict`` constructor
+used to build the new context level.
+
+ >>> c = Context()
+ >>> c['foo'] = 'first level'
+ >>> with c.push(foo='second level'):
+ >>> c['foo']
+ 'second level'
+ >>> c['foo']
+ 'first level'
+
.. method:: update(other_dict)
In addition to ``push()`` and ``pop()``, the ``Context``
@@ -348,7 +376,7 @@ you'll see below.
Subclassing Context: RequestContext
-----------------------------------
-.. class:: django.template.RequestContext
+.. class:: RequestContext
Django comes with a special ``Context`` class,
``django.template.RequestContext``, that acts slightly differently than the
@@ -450,6 +478,8 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every
``django.contrib.auth.context_processors.PermWrapper``, representing the
permissions that the currently logged-in user has.
+.. currentmodule:: django.core.context_processors
+
django.core.context_processors.debug
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -486,7 +516,7 @@ value of the :setting:`MEDIA_URL` setting.
django.core.context_processors.static
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. function:: django.core.context_processors.static
+.. function:: static
If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every
``RequestContext`` will contain a variable ``STATIC_URL``, providing the
diff --git a/docs/ref/unicode.txt b/docs/ref/unicode.txt
index e5074285e4..c9534c12c9 100644
--- a/docs/ref/unicode.txt
+++ b/docs/ref/unicode.txt
@@ -240,6 +240,13 @@ Django will convert it to Unicode when it needs to.
Choosing between ``__str__()`` and ``__unicode__()``
----------------------------------------------------
+.. note::
+
+ If you are on Python 3, you can skip this section because you'll always
+ create ``__str__()`` rather than ``__unicode__()``. If you'd like
+ compatibility with Python 2, you can decorate your model class with
+ :func:`~django.utils.encoding.python_2_unicode_compatible`.
+
One consequence of using Unicode by default is that you have to take some care
when printing data from the model.
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
index d7be7a4735..6b614e0c83 100644
--- a/docs/ref/utils.txt
+++ b/docs/ref/utils.txt
@@ -342,11 +342,15 @@ SyndicationFeed
All parameters should be Unicode objects, except ``categories``, which
should be a sequence of Unicode objects.
- .. method:: add_item(title, link, description, [author_email=None, author_name=None, author_link=None, pubdate=None, comments=None, unique_id=None, enclosure=None, categories=(), item_copyright=None, ttl=None, **kwargs])
+ .. method:: add_item(title, link, description, [author_email=None, author_name=None, author_link=None, pubdate=None, comments=None, unique_id=None, enclosure=None, categories=(), item_copyright=None, ttl=None, updateddate=None, **kwargs])
Adds an item to the feed. All args are expected to be Python ``unicode``
- objects except ``pubdate``, which is a ``datetime.datetime`` object, and
- ``enclosure``, which is an instance of the ``Enclosure`` class.
+ objects except ``pubdate`` and ``updateddate``, which are ``datetime.datetime``
+ objects, and ``enclosure``, which is an instance of the ``Enclosure`` class.
+
+ .. versionadded:: 1.7
+
+ The optional ``updateddate`` argument was added.
.. method:: num_items()
@@ -380,8 +384,9 @@ SyndicationFeed
.. method:: latest_post_date()
- Returns the latest item's ``pubdate``. If none of them have a
- ``pubdate``, this returns the current date/time.
+ Returns the latest ``pubdate`` or ``updateddate`` for all items in the
+ feed. If no items have either of these attributes this returns the
+ current date/time.
Enclosure
---------
@@ -414,7 +419,7 @@ Atom1Feed
.. class:: Atom1Feed(SyndicationFeed)
- Spec: http://www.atomenabled.org/developers/syndication/atom-format-spec.php
+ Spec: http://tools.ietf.org/html/rfc4287
``django.utils.functional``
===========================
@@ -446,12 +451,14 @@ Atom1Feed
def fancy_utility_function(s, ...):
# Do some conversion on string 's'
...
+ # Replace unicode by str on Python 3
fancy_utility_function = allow_lazy(fancy_utility_function, unicode)
The ``allow_lazy()`` decorator takes, in addition to the function to decorate,
a number of extra arguments (``*args``) specifying the type(s) that the
- original function can return. Usually, it's enough to include ``unicode`` here
- and ensure that your function returns only Unicode strings.
+ original function can return. Usually, it's enough to include ``unicode``
+ (or ``str`` on Python 3) here and ensure that your function returns only
+ Unicode strings.
Using this decorator means you can write your function and assume that the
input is a proper string, then add support for lazy translation objects at the
diff --git a/docs/releases/1.3-beta-1.txt b/docs/releases/1.3-beta-1.txt
index aa7800bb94..8719221ddd 100644
--- a/docs/releases/1.3-beta-1.txt
+++ b/docs/releases/1.3-beta-1.txt
@@ -116,7 +116,7 @@ As a result, we took the following steps to rectify the issue:
(``django.core.context_processors.static``) and renamed to
:func:`~django.core.context_processors.static`.
-* :ref:`form-media-paths` now uses :setting:`STATIC_URL` as the prefix
+* :ref:`form-asset-paths` now uses :setting:`STATIC_URL` as the prefix
**if the value is not None**, and falls back to the previously used
:setting:`MEDIA_URL` setting otherwise.
diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt
index 45ebb2f1fe..060f099c52 100644
--- a/docs/releases/1.3.txt
+++ b/docs/releases/1.3.txt
@@ -650,6 +650,15 @@ Prior to Django 1.3, inactive users were able to request a password reset email
and reset their password. In Django 1.3 inactive users will receive the same
message as a nonexistent account.
+Password reset view now accepts ``from_email``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :func:`django.contrib.auth.views.password_reset` view now accepts a
+``from_email`` parameter, which is passed to the ``password_reset_form``'s
+``save()`` method as a keyword argument. If you are using this view with a
+custom password reset form, then you will need to ensure your form's ``save()``
+method accepts this keyword argument.
+
.. _deprecated-features-1.3:
Features deprecated in 1.3
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index 4219edb1a0..b8babe1843 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -124,7 +124,7 @@ be customized to use any other JS framework.
``check`` management command added for verifying compatibility
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-A ``check`` management command was added, enabling you to verify if your
+A :djadmin:`check` management command was added, enabling you to verify if your
current configuration (currently oriented at settings) is compatible with the
current version of Django.
@@ -199,6 +199,10 @@ Minor features
* The admin list columns have a ``column-<field_name>`` class in the HTML
so the columns header can be styled with CSS, e.g. to set a column width.
+* Some admin templates now have ``app-<app_name>`` and ``model-<model_name>``
+ classes in their ``<body>`` tag to allow customizing the CSS per app or per
+ model.
+
* The :ref:`isolation level<database-isolation-level>` can be customized under
PostgreSQL.
@@ -337,8 +341,8 @@ Minor features
default) to allow customizing the :attr:`~django.forms.Form.prefix` of the
form.
-* Raw queries (``Manager.raw()`` or ``cursor.execute()``) can now use the
- "pyformat" parameter style, where placeholders in the query are given as
+* Raw queries (``Manager.raw()`` or ``cursor.execute()``) can now use the
+ "pyformat" parameter style, where placeholders in the query are given as
``'%(name)s'`` and the parameters are passed as a dictionary rather than
a list (except on SQLite). This has long been possible (but not officially
supported) on MySQL and PostgreSQL, and is now also available on Oracle.
@@ -634,10 +638,10 @@ If you manually render ``label_tag`` in your templates:
{{ form.my_field.label_tag }}: {{ form.my_field }}
-you'll want to remove the semicolon (or whatever other separator you may be
+you'll want to remove the colon (or whatever other separator you may be
using) to avoid duplicating it when upgrading to Django 1.6. The following
template in Django 1.6 will render identically to the above template in Django
-1.5, except that the semicolon will appear inside the ``<label>`` element.
+1.5, except that the colon will appear inside the ``<label>`` element.
.. code-block:: html+django
@@ -762,7 +766,7 @@ Miscellaneous
``{{ obj.view_href }}`` will cause template rendering to fail if
``view_href()`` raises ``NoReverseMatch``. There is no change to the
``{% url %}`` tag, it causes template rendering to fail like always when
- ``NoReverseMatch`` is risen.
+ ``NoReverseMatch`` is raised.
* :meth:`django.test.client.Client.logout` now calls
:meth:`django.contrib.auth.logout` which will send the
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index 6955ef2030..8c5a0fb585 100644
--- a/docs/releases/1.7.txt
+++ b/docs/releases/1.7.txt
@@ -14,9 +14,64 @@ deprecation process for some features`_.
.. _`backwards incompatible changes`: `Backwards incompatible changes in 1.7`_
.. _`begun the deprecation process for some features`: `Features deprecated in 1.7`_
+Python compatibility
+====================
+
+Django 1.7 requires Python 2.7 or above, though we **highly recommend**
+the latest minor release. Support for Python 2.6 has been dropped.
+
+This change should affect only a small number of Django users, as most
+operating-system vendors today are shipping Python 2.7 or newer as their default
+version. If you're still using Python 2.6, however, you'll need to stick to
+Django 1.6 until you can upgrade your Python version. Per :doc:`our support
+policy </internals/release-process>`, Django 1.6 will continue to receive
+security support until the release of Django 1.8.
+
What's new in Django 1.7
========================
+Admin shortcuts support time zones
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The "today" and "now" shortcuts next to date and time input widgets in the
+admin are now operating in the :ref:`current time zone
+<default-current-time-zone>`. Previously, they used the browser time zone,
+which could result in saving the wrong value when it didn't match the current
+time zone on the server.
+
+In addition, the widgets now display a help message when the browser and
+server time zone are different, to clarify how the value inserted in the field
+will be interpreted.
+
+Minor features
+~~~~~~~~~~~~~~
+
+* The new :attr:`UploadedFile.content_type_extra
+ <django.core.files.uploadedfile.UploadedFile.content_type_extra>` attribute
+ contains extra parameters passed to the ``content-type`` header on a file
+ upload.
+
+* The ``enter`` argument was added to the
+ :data:`~django.test.signals.setting_changed` signal.
+
+* The :meth:`QuerySet.update_or_create()
+ <django.db.models.query.QuerySet.update_or_create>` method was added.
+
+* :attr:`~django.db.models.Options.app_label` is no longer required for models
+ that are defined in a ``models`` package within an app.
+
+* The :meth:`Context.push() <django.template.Context.push>` method now returns
+ a context manager which automatically calls :meth:`pop()
+ <django.template.Context.pop>` upon exiting the ``with`` statement.
+ Additionally, :meth:`push() <django.template.Context.push>` now accepts
+ parameters that are passed to the ``dict`` constructor used to build the new
+ context level.
+
+* The :class:`~django.utils.feedgenerator.Atom1Feed` syndication feed's
+ ``updated`` element now utilizes `updateddate` instead of ``pubdate``,
+ allowing the ``published`` element to be included in the feed (which
+ relies on ``pubdate``).
+
Backwards incompatible changes in 1.7
=====================================
@@ -28,6 +83,14 @@ Backwards incompatible changes in 1.7
deprecation timeline for a given feature, its removal may appear as a
backwards incompatible change.
+Miscellaneous
+~~~~~~~~~~~~~
+
+* The :meth:`django.core.files.uploadhandler.FileUploadHandler.new_file()`
+ method is now passed an additional ``content_type_extra`` parameter. If you
+ have a custom :class:`~django.core.files.uploadhandler.FileUploadHandler`
+ that implements ``new_file()``, be sure it accepts this new parameter.
+
Features deprecated in 1.7
==========================
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index 85b3d211a8..39439ff9aa 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -17,6 +17,13 @@ Final releases
.. _development_release_notes:
+1.7 release
+-----------
+.. toctree::
+ :maxdepth: 1
+
+ 1.7
+
1.6 release
-----------
.. toctree::
diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt
index 24c622f77f..e27f257f80 100644
--- a/docs/topics/auth/customizing.txt
+++ b/docs/topics/auth/customizing.txt
@@ -589,8 +589,9 @@ The following methods are available on any subclass of
been called for this user.
You should also define a custom manager for your ``User`` model. If your
-``User`` model defines ``username`` and ``email`` fields the same as Django's
-default ``User``, you can just install Django's
+``User`` model defines ``username``, ``email``, ``is_staff``, ``is_active``,
+``is_superuser``, ``last_login``, and ``date_joined`` fields the same as
+Django's default ``User``, you can just install Django's
:class:`~django.contrib.auth.models.UserManager`; however, if your ``User``
model defines different fields, you will need to define a custom manager that
extends :class:`~django.contrib.auth.models.BaseUserManager` providing two
@@ -967,6 +968,7 @@ authentication app::
# The user is identified by their email address
return self.email
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.email
@@ -1058,6 +1060,8 @@ code would be required in the app's ``admin.py`` file::
('Personal info', {'fields': ('date_of_birth',)}),
('Permissions', {'fields': ('is_admin',)}),
)
+ # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
+ # overrides get_fieldsets to use this attribute when creating a user.
add_fieldsets = (
(None, {
'classes': ('wide',),
diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt
index e2fa0c287e..b7f679bf28 100644
--- a/docs/topics/auth/default.txt
+++ b/docs/topics/auth/default.txt
@@ -234,10 +234,11 @@ a model's ``Meta`` class, you can also create permissions directly. For
example, you can create the ``can_publish`` permission for a ``BlogPost`` model
in ``myapp``::
+ from myapp.models import BlogPost
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
- content_type = ContentType.objects.get(app_label='myapp', model='BlogPost')
+ content_type = ContentType.objects.get_for_model(BlogPost)
permission = Permission.objects.create(codename='can_publish',
name='Can Publish Posts',
content_type=content_type)
@@ -317,7 +318,7 @@ If you have an authenticated user you want to attach to the current session
which authentication backend successfully authenticated that user (see the
:ref:`backends documentation <authentication-backends>` for details), and
this information is needed later during the login process. An error will be
- raise if you try to login a user object retrieved from the database
+ raised if you try to login a user object retrieved from the database
directly.
How to log a user out
diff --git a/docs/topics/class-based-views/generic-display.txt b/docs/topics/class-based-views/generic-display.txt
index 8c2d0db041..2f8d561541 100644
--- a/docs/topics/class-based-views/generic-display.txt
+++ b/docs/topics/class-based-views/generic-display.txt
@@ -89,6 +89,7 @@ We'll be using these models::
class Meta:
ordering = ["-name"]
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.name
@@ -98,6 +99,7 @@ We'll be using these models::
email = models.EmailField()
headshot = models.ImageField(upload_to='author_headshots')
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.name
diff --git a/docs/topics/db/examples/many_to_many.txt b/docs/topics/db/examples/many_to_many.txt
index 2076427768..42fa9cc1a1 100644
--- a/docs/topics/db/examples/many_to_many.txt
+++ b/docs/topics/db/examples/many_to_many.txt
@@ -16,6 +16,7 @@ objects, and a ``Publication`` has multiple ``Article`` objects:
class Publication(models.Model):
title = models.CharField(max_length=30)
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.title
@@ -26,6 +27,7 @@ objects, and a ``Publication`` has multiple ``Article`` objects:
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication)
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.headline
diff --git a/docs/topics/db/examples/many_to_one.txt b/docs/topics/db/examples/many_to_one.txt
index c869362d16..af112144b3 100644
--- a/docs/topics/db/examples/many_to_one.txt
+++ b/docs/topics/db/examples/many_to_one.txt
@@ -15,6 +15,7 @@ To define a many-to-one relationship, use :class:`~django.db.models.ForeignKey`.
last_name = models.CharField(max_length=30)
email = models.EmailField()
+ # On Python 3: def __str__(self):
def __unicode__(self):
return u"%s %s" % (self.first_name, self.last_name)
@@ -23,6 +24,7 @@ To define a many-to-one relationship, use :class:`~django.db.models.ForeignKey`.
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter)
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.headline
@@ -56,9 +58,9 @@ Article objects have access to their related Reporter objects::
>>> r = a.reporter
-These are strings instead of unicode strings because that's what was used in
-the creation of this reporter (and we haven't refreshed the data from the
-database, which always returns unicode strings)::
+On Python 2, these are strings of type ``str`` instead of unicode strings
+because that's what was used in the creation of this reporter (and we haven't
+refreshed the data from the database, which always returns unicode strings)::
>>> r.first_name, r.last_name
('John', 'Smith')
diff --git a/docs/topics/db/examples/one_to_one.txt b/docs/topics/db/examples/one_to_one.txt
index 09634c84c7..a86e5ed0ac 100644
--- a/docs/topics/db/examples/one_to_one.txt
+++ b/docs/topics/db/examples/one_to_one.txt
@@ -16,6 +16,7 @@ In this example, a ``Place`` optionally can be a ``Restaurant``:
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
+ # On Python 3: def __str__(self):
def __unicode__(self):
return u"%s the place" % self.name
@@ -24,6 +25,7 @@ In this example, a ``Place`` optionally can be a ``Restaurant``:
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
+ # On Python 3: def __str__(self):
def __unicode__(self):
return u"%s the restaurant" % self.place.name
@@ -31,6 +33,7 @@ In this example, a ``Place`` optionally can be a ``Restaurant``:
restaurant = models.ForeignKey(Restaurant)
name = models.CharField(max_length=50)
+ # On Python 3: def __str__(self):
def __unicode__(self):
return u"%s the waiter at %s" % (self.name, self.restaurant)
diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt
index c0ba53ddd7..2b565758e7 100644
--- a/docs/topics/db/models.txt
+++ b/docs/topics/db/models.txt
@@ -416,6 +416,7 @@ something like this::
class Person(models.Model):
name = models.CharField(max_length=128)
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.name
@@ -423,6 +424,7 @@ something like this::
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.name
@@ -709,7 +711,10 @@ of :ref:`methods automatically given to each model <model-instance-methods>`.
You can override most of these -- see `overriding predefined model methods`_,
below -- but there are a couple that you'll almost always want to define:
-:meth:`~Model.__unicode__`
+:meth:`~Model.__str__` (Python 3)
+ Python 3 equivalent of ``__unicode__()``.
+
+:meth:`~Model.__unicode__` (Python 2)
A Python "magic method" that returns a unicode "representation" of any
object. This is what Python and Django will use whenever a model
instance needs to be coerced and displayed as a plain string. Most
diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt
index bdbdd3fa2a..1d6052f938 100644
--- a/docs/topics/db/queries.txt
+++ b/docs/topics/db/queries.txt
@@ -23,6 +23,7 @@ models, which comprise a Weblog application:
name = models.CharField(max_length=100)
tagline = models.TextField()
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.name
@@ -30,6 +31,7 @@ models, which comprise a Weblog application:
name = models.CharField(max_length=50)
email = models.EmailField()
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.name
@@ -44,6 +46,7 @@ models, which comprise a Weblog application:
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.headline
@@ -57,10 +60,6 @@ class represents a particular record in the database table.
To create an object, instantiate it using keyword arguments to the model class,
then call :meth:`~django.db.models.Model.save` to save it to the database.
-You import the model class from wherever it lives on the Python path, as you
-may expect. (We point this out here because previous Django versions required
-funky model importing.)
-
Assuming models live in a file ``mysite/blog/models.py``, here's an example::
>>> from blog.models import Blog
diff --git a/docs/topics/email.txt b/docs/topics/email.txt
index b3d7254e7f..8bf501c62a 100644
--- a/docs/topics/email.txt
+++ b/docs/topics/email.txt
@@ -27,7 +27,8 @@ Mail is sent using the SMTP host and port specified in the
:setting:`EMAIL_HOST` and :setting:`EMAIL_PORT` settings. The
:setting:`EMAIL_HOST_USER` and :setting:`EMAIL_HOST_PASSWORD` settings, if
set, are used to authenticate to the SMTP server, and the
-:setting:`EMAIL_USE_TLS` setting controls whether a secure connection is used.
+:setting:`EMAIL_USE_TLS` and :setting:`EMAIL_USE_SSL` settings control whether
+a secure connection is used.
.. note::
@@ -408,8 +409,8 @@ SMTP backend
This is the default backend. Email will be sent through a SMTP server.
The server address and authentication credentials are set in the
:setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`,
-:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your
-settings file.
+:setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS` and
+:setting:`EMAIL_USE_SSL` settings in your settings file.
The SMTP backend is the default configuration inherited by Django. If you
want to specify it explicitly, put the following in your settings::
diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt
index 29139c5dea..470d9f52e4 100644
--- a/docs/topics/forms/formsets.txt
+++ b/docs/topics/forms/formsets.txt
@@ -413,7 +413,7 @@ happen when the user changes these values::
Default: ``False``
-Lets you create a formset with the ability to delete::
+Lets you create a formset with the ability to select forms for deletion::
>>> from django.forms.formsets import formset_factory
>>> from myapp.forms import ArticleForm
@@ -461,6 +461,13 @@ delete fields you can access them with ``deleted_forms``::
>>> [form.cleaned_data for form in formset.deleted_forms]
[{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': u'Article #1'}]
+If you are using a :class:`ModelFormSet<django.forms.models.BaseModelFormSet>`,
+model instances for deleted forms will be deleted when you call
+``formset.save()``. On the other hand, if you are using a plain ``FormSet``,
+it's up to you to handle ``formset.deleted_forms``, perhaps in your formset's
+``save()`` method, as there's no general notion of what it means to delete a
+form.
+
Adding additional fields to a formset
-------------------------------------
@@ -559,8 +566,8 @@ If you manually render fields in the template, you can render
</form>
-Similarly, if the formset has the ability to order (``can_order=True``), it is possible to render it
-with ``{{ form.ORDER }}``.
+Similarly, if the formset has the ability to order (``can_order=True``), it is
+possible to render it with ``{{ form.ORDER }}``.
Using more than one formset in a view
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt
index 7d5a0ed411..1039a60d02 100644
--- a/docs/topics/forms/index.txt
+++ b/docs/topics/forms/index.txt
@@ -42,7 +42,7 @@ The library deals with these concepts:
A collection of fields that knows how to validate itself and
display itself as HTML.
- Form Media
+ Form Assets (the ``Media`` class)
The CSS and JavaScript resources that are required to render a form.
The library is decoupled from the other Django components, such as the database
@@ -321,7 +321,7 @@ attributes, which can be useful in your templates:
.. versionchanged:: 1.6
This includes the form's :attr:`~django.forms.Form.label_suffix`. For
- example, the default ``label_suffix`` is a semicolon::
+ example, the default ``label_suffix`` is a colon::
<label for="id_email">Email address:</label>
diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt
index a1fd533241..09184fe15f 100644
--- a/docs/topics/forms/media.txt
+++ b/docs/topics/forms/media.txt
@@ -1,5 +1,5 @@
-Form Media
-==========
+Form Assets (the ``Media`` class)
+=================================
Rendering an attractive and easy-to-use Web form requires more than just
HTML - it also requires CSS stylesheets, and if you want to use fancy
@@ -7,23 +7,24 @@ HTML - it also requires CSS stylesheets, and if you want to use fancy
page. The exact combination of CSS and JavaScript that is required for
any given page will depend upon the widgets that are in use on that page.
-This is where Django media definitions come in. Django allows you to
-associate different media files with the forms and widgets that require
-that media. For example, if you want to use a calendar to render DateFields,
-you can define a custom Calendar widget. This widget can then be associated
-with the CSS and JavaScript that is required to render the calendar. When
-the Calendar widget is used on a form, Django is able to identify the CSS and
+This is where asset definitions come in. Django allows you to
+associate different files -- like stylesheets and scripts -- with the
+forms and widgets that require those assets. For example, if you want
+to use a calendar to render DateFields, you can define a custom
+Calendar widget. This widget can then be associated with the CSS and
+JavaScript that is required to render the calendar. When the Calendar
+widget is used on a form, Django is able to identify the CSS and
JavaScript files that are required, and provide the list of file names
in a form suitable for easy inclusion on your Web page.
-.. admonition:: Media and Django Admin
+.. admonition:: Assets and Django Admin
- The Django Admin application defines a number of customized widgets
- for calendars, filtered selections, and so on. These widgets define
- media requirements, and the Django Admin uses the custom widgets
- in place of the Django defaults. The Admin templates will only include
- those media files that are required to render the widgets on any
- given page.
+ The Django Admin application defines a number of customized
+ widgets for calendars, filtered selections, and so on. These
+ widgets define asset requirements, and the Django Admin uses the
+ custom widgets in place of the Django defaults. The Admin
+ templates will only include those files that are required to
+ render the widgets on any given page.
If you like the widgets that the Django Admin application uses,
feel free to use them in your own application! They're all stored
@@ -38,14 +39,14 @@ in a form suitable for easy inclusion on your Web page.
whichever toolkit suits your requirements. Django is able to integrate
with any JavaScript toolkit.
-.. _media-as-a-static-definition:
+.. _assets-as-a-static-definition:
-Media as a static definition
-----------------------------
+Assets as a static definition
+-----------------------------
-The easiest way to define media is as a static definition. Using this method,
-the media declaration is an inner class. The properties of the inner class
-define the media requirements.
+The easiest way to define assets is as a static definition. Using this
+method, the declaration is an inner ``Media`` class. The properties of the
+inner class define the requirements.
Here's a simple example::
@@ -63,9 +64,9 @@ Every time the CalendarWidget is used on a form, that form will be directed
to include the CSS file ``pretty.css``, and the JavaScript files
``animations.js`` and ``actions.js``.
-This static media definition is converted at runtime into a widget property
-named ``media``. The media for a CalendarWidget instance can be retrieved
-through this property::
+This static definition is converted at runtime into a widget property
+named ``media``. The list of assets for a ``CalendarWidget`` instance
+can be retrieved through this property::
>>> w = CalendarWidget()
>>> print(w.media)
@@ -82,8 +83,8 @@ A dictionary describing the CSS files required for various forms of output
media.
The values in the dictionary should be a tuple/list of file names. See
-:ref:`the section on media paths <form-media-paths>` for details of how to
-specify paths to media files.
+:ref:`the section on paths <form-asset-paths>` for details of how to
+specify paths to these files.
The keys in the dictionary are the output media types. These are the same
types accepted by CSS files in media declarations: 'all', 'aural', 'braille',
@@ -119,19 +120,20 @@ If this last CSS definition were to be rendered, it would become the following H
``js``
~~~~~~
-A tuple describing the required JavaScript files. See :ref:`the section on
-media paths <form-media-paths>` for details of how to specify paths to media
-files.
+A tuple describing the required JavaScript files. See :ref:`the
+section on paths <form-asset-paths>` for details of how to specify
+paths to these files.
``extend``
~~~~~~~~~~
-A boolean defining inheritance behavior for media declarations.
+A boolean defining inheritance behavior for ``Media`` declarations.
-By default, any object using a static media definition will inherit all the
-media associated with the parent widget. This occurs regardless of how the
-parent defines its media requirements. For example, if we were to extend our
-basic Calendar widget from the example above::
+By default, any object using a static ``Media`` definition will
+inherit all the assets associated with the parent widget. This occurs
+regardless of how the parent defines its own requirements. For
+example, if we were to extend our basic Calendar widget from the
+example above::
>>> class FancyCalendarWidget(CalendarWidget):
... class Media:
@@ -148,9 +150,9 @@ basic Calendar widget from the example above::
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>
-The FancyCalendar widget inherits all the media from its parent widget. If
-you don't want media to be inherited in this way, add an ``extend=False``
-declaration to the media declaration::
+The FancyCalendar widget inherits all the assets from its parent
+widget. If you don't want ``Media`` to be inherited in this way, add
+an ``extend=False`` declaration to the ``Media`` declaration::
>>> class FancyCalendarWidget(CalendarWidget):
... class Media:
@@ -165,23 +167,24 @@ declaration to the media declaration::
<link href="http://static.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>
-If you require even more control over media inheritance, define your media
-using a :ref:`dynamic property <dynamic-property>`. Dynamic properties give
-you complete control over which media files are inherited, and which are not.
+If you require even more control over inheritance, define your assets using a
+:ref:`dynamic property <dynamic-property>`. Dynamic properties give you
+complete control over which files are inherited, and which are not.
.. _dynamic-property:
-Media as a dynamic property
----------------------------
+``Media`` as a dynamic property
+-------------------------------
-If you need to perform some more sophisticated manipulation of media
-requirements, you can define the media property directly. This is done
-by defining a widget property that returns an instance of ``forms.Media``.
-The constructor for ``forms.Media`` accepts ``css`` and ``js`` keyword
-arguments in the same format as that used in a static media definition.
+If you need to perform some more sophisticated manipulation of asset
+requirements, you can define the ``media`` property directly. This is
+done by defining a widget property that returns an instance of
+``forms.Media``. The constructor for ``forms.Media`` accepts ``css``
+and ``js`` keyword arguments in the same format as that used in a
+static media definition.
-For example, the static media definition for our Calendar Widget could
-also be defined in a dynamic fashion::
+For example, the static definition for our Calendar Widget could also
+be defined in a dynamic fashion::
class CalendarWidget(forms.TextInput):
def _media(self):
@@ -190,17 +193,17 @@ also be defined in a dynamic fashion::
media = property(_media)
See the section on `Media objects`_ for more details on how to construct
-return values for dynamic media properties.
+return values for dynamic ``media`` properties.
-.. _form-media-paths:
+.. _form-asset-paths:
-Paths in media definitions
+Paths in asset definitions
--------------------------
-Paths used to specify media can be either relative or absolute. If a path
-starts with ``/``, ``http://`` or ``https://``, it will be interpreted as an
-absolute path, and left as-is. All other paths will be prepended with the value
-of the appropriate prefix.
+Paths used to specify assets can be either relative or absolute. If a
+path starts with ``/``, ``http://`` or ``https://``, it will be
+interpreted as an absolute path, and left as-is. All other paths will
+be prepended with the value of the appropriate prefix.
As part of the introduction of the
:doc:`staticfiles app </ref/contrib/staticfiles>` two new settings were added
@@ -236,21 +239,22 @@ But if :setting:`STATIC_URL` is ``'http://static.example.com/'``::
<script type="text/javascript" src="http://othersite.com/actions.js"></script>
-Media objects
--------------
+``Media`` objects
+-----------------
-When you interrogate the media attribute of a widget or form, the value that
-is returned is a ``forms.Media`` object. As we have already seen, the string
-representation of a Media object is the HTML required to include media
-in the ``<head>`` block of your HTML page.
+When you interrogate the ``media`` attribute of a widget or form, the
+value that is returned is a ``forms.Media`` object. As we have already
+seen, the string representation of a ``Media`` object is the HTML
+required to include the relevant files in the ``<head>`` block of your
+HTML page.
-However, Media objects have some other interesting properties.
+However, ``Media`` objects have some other interesting properties.
-Media subsets
-~~~~~~~~~~~~~
+Subsets of assets
+~~~~~~~~~~~~~~~~~
-If you only want media of a particular type, you can use the subscript operator
-to filter out a medium of interest. For example::
+If you only want files of a particular type, you can use the subscript
+operator to filter out a medium of interest. For example::
>>> w = CalendarWidget()
>>> print(w.media)
@@ -258,17 +262,18 @@ to filter out a medium of interest. For example::
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
- >>> print(w.media)['css']
+ >>> print(w.media['css'])
<link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
-When you use the subscript operator, the value that is returned is a new
-Media object -- but one that only contains the media of interest.
+When you use the subscript operator, the value that is returned is a
+new ``Media`` object -- but one that only contains the media of interest.
-Combining media objects
-~~~~~~~~~~~~~~~~~~~~~~~
+Combining ``Media`` objects
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Media objects can also be added together. When two media objects are added,
-the resulting Media object contains the union of the media from both files::
+``Media`` objects can also be added together. When two ``Media`` objects are
+added, the resulting ``Media`` object contains the union of the assets
+specified by both::
>>> from django import forms
>>> class CalendarWidget(forms.TextInput):
@@ -290,17 +295,19 @@ the resulting Media object contains the union of the media from both files::
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>
-Media on Forms
---------------
+``Media`` on Forms
+------------------
-Widgets aren't the only objects that can have media definitions -- forms
-can also define media. The rules for media definitions on forms are the
-same as the rules for widgets: declarations can be static or dynamic;
-path and inheritance rules for those declarations are exactly the same.
+Widgets aren't the only objects that can have ``media`` definitions --
+forms can also define ``media``. The rules for ``media`` definitions
+on forms are the same as the rules for widgets: declarations can be
+static or dynamic; path and inheritance rules for those declarations
+are exactly the same.
-Regardless of whether you define a media declaration, *all* Form objects
-have a media property. The default value for this property is the result
-of adding the media definitions for all widgets that are part of the form::
+Regardless of whether you define a ``media`` declaration, *all* Form
+objects have a ``media`` property. The default value for this property
+is the result of adding the ``media`` definitions for all widgets that
+are part of the form::
>>> from django import forms
>>> class ContactForm(forms.Form):
@@ -314,8 +321,9 @@ of adding the media definitions for all widgets that are part of the form::
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>
-If you want to associate additional media with a form -- for example, CSS for form
-layout -- simply add a media declaration to the form::
+If you want to associate additional assets with a form -- for example,
+CSS for form layout -- simply add a ``Media`` declaration to the
+form::
>>> class ContactForm(forms.Form):
... date = DateField(widget=CalendarWidget)
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
index bd9e14aea4..0f3c5bb815 100644
--- a/docs/topics/forms/modelforms.txt
+++ b/docs/topics/forms/modelforms.txt
@@ -162,6 +162,7 @@ Consider this set of models::
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
+ # On Python 3: def __str__(self):
def __unicode__(self):
return self.name
@@ -196,29 +197,89 @@ we'll discuss in a moment.)::
name = forms.CharField(max_length=100)
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
-.. _modelform-is-valid-and-errors:
+.. _validation-on-modelform:
-The ``is_valid()`` method and ``errors``
-----------------------------------------
+Validation on a ``ModelForm``
+-----------------------------
+
+There are two main steps involved in validating a ``ModelForm``:
+
+1. :ref:`Validating the form <form-and-field-validation>`
+2. :ref:`Validating the model instance <validating-objects>`
+
+Just like normal form validation, model form validation is triggered implicitly
+when calling :meth:`~django.forms.Form.is_valid()` or accessing the
+:attr:`~django.forms.Form.errors` attribute and explicitly when calling
+``full_clean()``, although you will typically not use the latter method in
+practice.
+
+``Model`` validation (:meth:`Model.full_clean()
+<django.db.models.Model.full_clean()>`) is triggered from within the form
+validation step, right after the form's ``clean()`` method is called.
+
+.. warning::
+
+ The cleaning process modifies the model instance passed to the
+ ``ModelForm`` constructor in various ways. For instance, any date fields on
+ the model are converted into actual date objects. Failed validation may
+ leave the underlying model instance in an inconsistent state and therefore
+ it's not recommended to reuse it.
+
+.. _overriding-modelform-clean-method:
+
+Overriding the clean() method
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+You can override the ``clean()`` method on a model form to provide additional
+validation in the same way you can on a normal form.
+
+A model form instance bound to a model object will contain an ``instance``
+attribute that gives its methods access to that specific model instance.
-The first time you call ``is_valid()`` or access the ``errors`` attribute of a
-``ModelForm`` triggers :ref:`form validation <form-and-field-validation>` as
-well as :ref:`model validation <validating-objects>`. This has the side-effect
-of cleaning the model you pass to the ``ModelForm`` constructor. For instance,
-calling ``is_valid()`` on your form will convert any date fields on your model
-to actual date objects. If form validation fails, only some of the updates
-may be applied. For this reason, you'll probably want to avoid reusing the
-model instance passed to the form, especially if validation fails.
+.. warning::
+ The ``ModelForm.clean()`` method sets a flag that makes the :ref:`model
+ validation <validating-objects>` step validate the uniqueness of model
+ fields that are marked as ``unique``, ``unique_together`` or
+ ``unique_for_date|month|year``.
+
+ If you would like to override the ``clean()`` method and maintain this
+ validation, you must call the parent class's ``clean()`` method.
+
+Interaction with model validation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+As part of the validation process, ``ModelForm`` will call the ``clean()``
+method of each field on your model that has a corresponding field on your form.
+If you have excluded any model fields, validation will not be run on those
+fields. See the :doc:`form validation </ref/forms/validation>` documentation
+for more on how field cleaning and validation work.
+
+The model's ``clean()`` method will be called before any uniqueness checks are
+made. See :ref:`Validating objects <validating-objects>` for more information
+on the model's ``clean()`` hook.
+
+Considerations regarding fields' ``error_messages``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Error messages defined at the
+:attr:`form field <django.forms.Field.error_messages>` level or at the
+:ref:`form Meta <modelforms-overriding-default-fields>` level always take
+precedence over the error messages defined at the
+:attr:`model field <django.db.models.Field.error_messages>` level.
+Error messages defined on :attr:`model fields
+<django.db.models.Field.error_messages>` are only used when the
+``ValidationError`` is raised during the :ref:`model validation
+<validating-objects>` step and no corresponding error messages are defined at
+the form level.
The ``save()`` method
---------------------
-Every form produced by ``ModelForm`` also has a ``save()``
-method. This method creates and saves a database object from the data
-bound to the form. A subclass of ``ModelForm`` can accept an existing
-model instance as the keyword argument ``instance``; if this is
-supplied, ``save()`` will update that instance. If it's not supplied,
+Every ``ModelForm`` also has a ``save()`` method. This method creates and saves
+a database object from the data bound to the form. A subclass of ``ModelForm``
+can accept an existing model instance as the keyword argument ``instance``; if
+this is supplied, ``save()`` will update that instance. If it's not supplied,
``save()`` will create a new instance of the specified model:
.. code-block:: python
@@ -239,7 +300,7 @@ supplied, ``save()`` will update that instance. If it's not supplied,
>>> f.save()
Note that if the form :ref:`hasn't been validated
-<modelform-is-valid-and-errors>`, calling ``save()`` will do so by checking
+<validation-on-modelform>`, calling ``save()`` will do so by checking
``form.errors``. A ``ValueError`` will be raised if the data in the form
doesn't validate -- i.e., if ``form.errors`` evaluates to ``True``.
@@ -262,7 +323,9 @@ exists in the database.
To work around this problem, every time you save a form using ``commit=False``,
Django adds a ``save_m2m()`` method to your ``ModelForm`` subclass. After
you've manually saved the instance produced by the form, you can invoke
-``save_m2m()`` to save the many-to-many form data. For example::
+``save_m2m()`` to save the many-to-many form data. For example:
+
+.. code-block:: python
# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)
@@ -282,7 +345,9 @@ you've manually saved the instance produced by the form, you can invoke
Calling ``save_m2m()`` is only required if you use ``save(commit=False)``.
When you use a simple ``save()`` on a form, all data -- including
many-to-many data -- is saved without the need for any additional method calls.
-For example::
+For example:
+
+.. code-block:: python
# Create a form instance with POST data.
>>> a = Author()
@@ -536,27 +601,6 @@ attribute on the ``Meta`` class.
If ``localized_fields`` is set to the special value ``'__all__'``, all fields
will be localized.
-.. _overriding-modelform-clean-method:
-
-Overriding the clean() method
------------------------------
-
-You can override the ``clean()`` method on a model form to provide additional
-validation in the same way you can on a normal form.
-
-In this regard, model forms have two specific characteristics when compared to
-forms:
-
-By default the ``clean()`` method validates the uniqueness of fields that are
-marked as ``unique``, ``unique_together`` or ``unique_for_date|month|year`` on
-the model. Therefore, if you would like to override the ``clean()`` method and
-maintain the default validation, you must call the parent class's ``clean()``
-method.
-
-Also, a model form instance bound to a model object will contain a
-``self.instance`` attribute that gives model form methods access to that
-specific model instance.
-
Form inheritance
----------------
@@ -595,18 +639,6 @@ There are a couple of things to note, however.
Chances are these notes won't affect you unless you're trying to do something
tricky with subclassing.
-Interaction with model validation
----------------------------------
-
-As part of its validation process, ``ModelForm`` will call the ``clean()``
-method of each field on your model that has a corresponding field on your form.
-If you have excluded any model fields, validation will not be run on those
-fields. See the :doc:`form validation </ref/forms/validation>` documentation
-for more on how field cleaning and validation work. Also, your model's
-``clean()`` method will be called before any uniqueness checks are made. See
-:ref:`Validating objects <validating-objects>` for more information on the
-model's ``clean()`` hook.
-
.. _modelforms-factory:
ModelForm factory function
@@ -856,14 +888,16 @@ than that of a "normal" formset. The only difference is that we call
``formset.save()`` to save the data into the database. (This was described
above, in :ref:`saving-objects-in-the-formset`.)
-Overiding ``clean()`` on a ``model_formset``
+.. _model-formsets-overriding-clean:
+
+Overriding ``clean()`` on a ``ModelFormSet``
--------------------------------------------
Just like with ``ModelForms``, by default the ``clean()`` method of a
-``model_formset`` will validate that none of the items in the formset violate
+``ModelFormSet`` will validate that none of the items in the formset violate
the unique constraints on your model (either ``unique``, ``unique_together`` or
``unique_for_date|month|year``). If you want to override the ``clean()`` method
-on a ``model_formset`` and maintain this validation, you must call the parent
+on a ``ModelFormSet`` and maintain this validation, you must call the parent
class's ``clean`` method::
from django.forms.models import BaseModelFormSet
@@ -968,6 +1002,8 @@ primary key that isn't called ``id``, make sure it gets rendered.)
Inline formsets
===============
+.. class:: models.BaseInlineFormSet
+
Inline formsets is a small abstraction layer on top of model formsets. These
simplify the case of working with related objects via a foreign key. Suppose
you have these two models::
@@ -999,6 +1035,13 @@ a particular author, you could do this::
:ref:`Manually rendered can_delete and can_order <manually-rendered-can-delete-and-can-order>`.
+Overriding ``clean()`` on an ``InlineFormSet``
+----------------------------------------------
+
+See :ref:`model-formsets-overriding-clean`, but subclass
+:class:`~models.BaseInlineFormSet` rather than
+:class:`~models.BaseModelFormSet`.
+
More than one foreign key to the same model
-------------------------------------------
diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt
index f6fa27e27c..2cdab9ea9b 100644
--- a/docs/topics/http/file-uploads.txt
+++ b/docs/topics/http/file-uploads.txt
@@ -240,6 +240,18 @@ In addition to those inherited from :class:`~django.core.files.File`, all
need to validate that the file contains the content that the content-type
header claims -- "trust but verify."
+.. attribute:: UploadedFile.content_type_extra
+
+ .. versionadded:: 1.7
+
+ A dictionary containing extra parameters passed to the ``content-type``
+ header. This is typically provided by services, such as Google App Engine,
+ that intercept and handle file uploads on your behalf. As a result your
+ handler may not receive the uploaded file content, but instead a URL or
+ other pointer to the file. (see `RFC 2388`_ section 5.3).
+
+ .. _RFC 2388: http://www.ietf.org/rfc/rfc2388.txt
+
.. attribute:: UploadedFile.charset
For :mimetype:`text/*` content-types, the character set (i.e. ``utf8``)
@@ -350,6 +362,10 @@ list::
Writing custom upload handlers
------------------------------
+.. currentmodule:: django.core.files.uploadhandler
+
+.. class:: FileUploadHandler
+
All file upload handlers should be subclasses of
``django.core.files.uploadhandler.FileUploadHandler``. You can define upload
handlers wherever you wish.
@@ -359,7 +375,8 @@ Required methods
Custom file upload handlers **must** define the following methods:
-``FileUploadHandler.receive_data_chunk(self, raw_data, start)``
+.. method:: FileUploadHandler.receive_data_chunk(self, raw_data, start)
+
Receives a "chunk" of data from the file upload.
``raw_data`` is a byte string containing the uploaded data.
@@ -379,7 +396,8 @@ Custom file upload handlers **must** define the following methods:
If you raise a ``StopUpload`` or a ``SkipFile`` exception, the upload
will abort or the file will be completely skipped.
-``FileUploadHandler.file_complete(self, file_size)``
+.. method:: FileUploadHandler.file_complete(self, file_size)
+
Called when a file has finished uploading.
The handler should return an ``UploadedFile`` object that will be stored
@@ -392,7 +410,8 @@ Optional methods
Custom upload handlers may also define any of the following optional methods or
attributes:
-``FileUploadHandler.chunk_size``
+.. attribute:: FileUploadHandler.chunk_size
+
Size, in bytes, of the "chunks" Django should store into memory and feed
into the handler. That is, this attribute controls the size of chunks
fed into ``FileUploadHandler.receive_data_chunk``.
@@ -404,7 +423,8 @@ attributes:
The default is 64*2\ :sup:`10` bytes, or 64 KB.
-``FileUploadHandler.new_file(self, field_name, file_name, content_type, content_length, charset)``
+.. method:: FileUploadHandler.new_file(self, field_name, file_name, content_type, content_length, charset, content_type_extra)
+
Callback signaling that a new file upload is starting. This is called
before any data has been fed to any upload handlers.
@@ -421,13 +441,23 @@ attributes:
``charset`` is the character set (i.e. ``utf8``) given by the browser.
Like ``content_length``, this sometimes won't be provided.
+ ``content_type_extra`` is extra information about the file from the
+ ``content-type`` header. See :attr:`UploadedFile.content_type_extra
+ <django.core.files.uploadedfile.UploadedFile.content_type_extra>`.
+
This method may raise a ``StopFutureHandlers`` exception to prevent
future handlers from handling this file.
-``FileUploadHandler.upload_complete(self)``
+ .. versionadded:: 1.7
+
+ The ``content_type_extra`` parameter was added.
+
+.. method:: FileUploadHandler.upload_complete(self)
+
Callback signaling that the entire upload (all files) has completed.
-``FileUploadHandler.handle_raw_input(self, input_data, META, content_length, boundary, encoding)``
+.. method:: FileUploadHandler.handle_raw_input(self, input_data, META, content_length, boundary, encoding)
+
Allows the handler to completely override the parsing of the raw
HTTP input.
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index ce6697908f..e724837ce1 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -29,9 +29,7 @@ use internationalization, you should take the two seconds to set
:setting:`USE_I18N = False <USE_I18N>` in your settings file. Then Django will
make some optimizations so as not to load the internationalization machinery.
You'll probably also want to remove ``'django.core.context_processors.i18n'``
-from your :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and
-``'django.middleware.locale.LocaleMiddleware'`` from your
-:setting:`MIDDLEWARE_CLASSES` setting.
+from your :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting.
.. note::
@@ -1238,13 +1236,16 @@ German.
The script should be run from one of two places:
-* The root directory of your Django project.
-* The root directory of your Django app.
+* The root directory of your Django project (the one that contains
+ ``manage.py``).
+* The root directory of one of your Django apps.
The script runs over your project source tree or your application source tree
-and pulls out all strings marked for translation. It creates (or updates) a
-message file in the directory ``locale/LANG/LC_MESSAGES``. In the ``de``
-example, the file will be ``locale/de/LC_MESSAGES/django.po``.
+and pulls out all strings marked for translation (see
+:ref:`how-django-discovers-translations` and be sure :setting:`LOCALE_PATHS`
+is configured correctly). It creates (or updates) a message file in the
+directory ``locale/LANG/LC_MESSAGES``. In the ``de`` example, the file will be
+``locale/de/LC_MESSAGES/django.po``.
By default :djadmin:`django-admin.py makemessages <makemessages>` examines every
file that has the ``.html`` or ``.txt`` file extension. In case you want to
@@ -1560,15 +1561,9 @@ If you want to let each individual user specify which language he or she
prefers, use ``LocaleMiddleware``. ``LocaleMiddleware`` enables language
selection based on data from the request. It customizes content for each user.
-``LocaleMiddleware`` is enabled in the default settings file: the
-:setting:`MIDDLEWARE_CLASSES` setting contains
-``'django.middleware.locale.LocaleMiddleware'``.
-
-.. versionchanged:: 1.6
-
- In previous versions, ``LocaleMiddleware`` wasn't enabled by default.
-
-Because middleware order matters, you should follow these guidelines:
+To use ``LocaleMiddleware``, add ``'django.middleware.locale.LocaleMiddleware'``
+to your :setting:`MIDDLEWARE_CLASSES` setting. Because middleware order
+matters, you should follow these guidelines:
* Make sure it's one of the first middlewares installed.
* It should come after ``SessionMiddleware``, because ``LocaleMiddleware``
diff --git a/docs/topics/localflavor.txt b/docs/topics/localflavor.txt
index 8ae435463d..8bc8b2f9e3 100644
--- a/docs/topics/localflavor.txt
+++ b/docs/topics/localflavor.txt
@@ -48,7 +48,7 @@ The following countries have django-localflavor- packages.
* Chile: https://github.com/django/django-localflavor-cl
* China: https://github.com/django/django-localflavor-cn
* Colombia: https://github.com/django/django-localflavor-co
-* Croatia: https://github.com/django/django-localflavor-cr
+* Croatia: https://github.com/django/django-localflavor-hr
* Czech Republic: https://github.com/django/django-localflavor-cz
* Ecuador: https://github.com/django/django-localflavor-ec
* Finland: https://github.com/django/django-localflavor-fi
diff --git a/docs/topics/signing.txt b/docs/topics/signing.txt
index 68afd6962a..480af18db7 100644
--- a/docs/topics/signing.txt
+++ b/docs/topics/signing.txt
@@ -37,8 +37,6 @@ generate their own signed values.
Using the low-level API
=======================
-.. class:: Signer
-
Django's signing methods live in the ``django.core.signing`` module.
To sign a value, first instantiate a ``Signer`` instance::
@@ -74,6 +72,11 @@ generate signatures. You can use a different secret by passing it to the
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
+.. class:: Signer(key=None, sep=':', salt=None)
+
+ Returns a signer which uses ``key`` to generate signatures and ``sep``
+ to separate values.
+
Using the salt argument
-----------------------
@@ -105,8 +108,6 @@ secret.
Verifying timestamped values
----------------------------
-.. class:: TimestampSigner
-
``TimestampSigner`` is a subclass of :class:`~Signer` that appends a signed
timestamp to the value. This allows you to confirm that a signed value was
created within a specified period of time::
@@ -124,6 +125,17 @@ created within a specified period of time::
>>> signer.unsign(value, max_age=20)
u'hello'
+.. class:: TimestampSigner(key=None, sep=':', salt=None)
+
+ .. method:: sign(value)
+
+ Sign ``value`` and append current timestamp to it.
+
+ .. method:: unsign(value, max_age=None)
+
+ Checks if ``value`` was signed less than ``max_age`` seconds ago,
+ otherwise raises ``SignatureExpired``.
+
Protecting complex data structures
----------------------------------
@@ -142,8 +154,10 @@ to execute arbitrary commands by exploiting the pickle format.::
.. function:: dumps(obj, key=None, salt='django.core.signing', compress=False)
- Returns URL-safe, sha1 signed base64 compressed JSON string.
+ Returns URL-safe, sha1 signed base64 compressed JSON string. Serialized
+ object is signed using :class:`~TimestampSigner`.
.. function:: loads(string, key=None, salt='django.core.signing', max_age=None)
- Reverse of dumps(), raises ``BadSignature`` if signature fails.
+ Reverse of ``dumps()``, raises ``BadSignature`` if signature fails.
+ Checks ``max_age`` (in seconds) if given.
diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt
index b8e70b592b..2417274ab5 100644
--- a/docs/topics/testing/advanced.txt
+++ b/docs/topics/testing/advanced.txt
@@ -37,18 +37,25 @@ Example
The following is a simple unit test using the request factory::
- import unittest
+ from django.contrib.auth.models import User
+ from django.test import TestCase
from django.test.client import RequestFactory
- class SimpleTest(unittest.TestCase):
+ class SimpleTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
+ self.user = User.objects.create_user(
+ first_name='jacob', email='jacob@…', password='top_secret')
def test_details(self):
# Create an instance of a GET request.
request = self.factory.get('/customer/details')
+ # Recall that middleware are not suported. You can simulate a
+ # logged-in user by setting request.user manually.
+ request.user = self.user
+
# Test my_view() as if it were deployed at /customer/details
response = my_view(request)
self.assertEqual(response.status_code, 200)
diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt
index 6f75a3b7a8..0380d6931b 100644
--- a/docs/topics/testing/overview.txt
+++ b/docs/topics/testing/overview.txt
@@ -1403,6 +1403,23 @@ The decorator can also be applied to test case classes::
the original ``LoginTestCase`` is still equally affected by the
decorator.
+.. warning::
+
+ The settings file contains some settings that are only consulted during
+ initialization of Django internals. If you change them with
+ ``override_settings``, the setting is changed if you access it via the
+ ``django.conf.settings`` module, however, Django's internals access it
+ differently. Effectively, using ``override_settings`` with these settings
+ is probably not going to do what you expect it to do.
+
+ We do not recommend using ``override_settings`` with :setting:`DATABASES`.
+ Using ``override_settings`` with :setting:`CACHES` is possible, but a bit
+ tricky if you are using internals that make using of caching, like
+ :mod:`django.contrib.sessions`. For example, you will have to reinitialize
+ the session backend in a test that uses cached sessions and overrides
+ :setting:`CACHES`.
+
+
You can also simulate the absence of a setting by deleting it after settings
have been overriden, like this::
@@ -1787,7 +1804,8 @@ for skipping tests.
.. function:: skipIfDBFeature(feature_name_string)
-Skip the decorated test if the named database feature is supported.
+Skip the decorated test or ``TestCase`` if the named database feature is
+supported.
For example, the following test will not be executed if the database
supports transactions (e.g., it would *not* run under PostgreSQL, but
@@ -1798,9 +1816,13 @@ it would under MySQL with MyISAM tables)::
def test_transaction_behavior(self):
# ... conditional test code
+.. versionchanged:: 1.7
+
+ ``skipIfDBFeature`` can now be used to decorate a ``TestCase`` class.
+
.. function:: skipUnlessDBFeature(feature_name_string)
-Skip the decorated test if the named database feature is *not*
+Skip the decorated test or ``TestCase`` if the named database feature is *not*
supported.
For example, the following test will only be executed if the database
@@ -1811,3 +1833,7 @@ under MySQL with MyISAM tables)::
@skipUnlessDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
+
+.. versionchanged:: 1.7
+
+ ``skipUnlessDBFeature`` can now be used to decorate a ``TestCase`` class.
diff --git a/scripts/manage_translations.py b/scripts/manage_translations.py
index 3e58040187..d1390c0181 100644
--- a/scripts/manage_translations.py
+++ b/scripts/manage_translations.py
@@ -114,6 +114,9 @@ def lang_stats(resources=None, languages=None):
if p.returncode == 0:
# msgfmt output stats on stderr
print("%s: %s" % (lang, errors.strip()))
+ else:
+ print("Errors happened when checking %s translation for %s:\n%s" % (
+ lang, name, errors))
def fetch(resources=None, languages=None):
@@ -121,6 +124,7 @@ def fetch(resources=None, languages=None):
Fetch translations from Transifex, wrap long lines, generate mo files.
"""
locale_dirs = _get_locale_dirs()
+ errors = []
for name, dir_ in locale_dirs:
if resources and not name in resources:
@@ -140,8 +144,14 @@ def fetch(resources=None, languages=None):
po_path = '%(path)s/%(lang)s/LC_MESSAGES/django%(ext)s.po' % {
'path': dir_, 'lang': lang, 'ext': 'js' if name.endswith('-js') else ''}
call('msgcat -o %s %s' % (po_path, po_path), shell=True)
- mo_path = '%s.mo' % po_path[:-3]
- call('msgfmt -o %s %s' % (mo_path, po_path), shell=True)
+ res = call('msgfmt -c -o %s.mo %s' % (po_path[:-3], po_path), shell=True)
+ if res != 0:
+ errors.append((name, lang))
+ if errors:
+ print("\nWARNING: Errors have occurred in following cases:")
+ for resource, lang in errors:
+ print("\tResource %s for language %s" % (resource, lang))
+ exit(1)
if __name__ == "__main__":
diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py
index 2f88248ca4..62f9e04e5b 100644
--- a/tests/admin_inlines/admin.py
+++ b/tests/admin_inlines/admin.py
@@ -12,8 +12,26 @@ class BookInline(admin.TabularInline):
model = Author.books.through
+class NonAutoPKBookTabularInline(admin.TabularInline):
+ model = NonAutoPKBook
+
+
+class NonAutoPKBookStackedInline(admin.StackedInline):
+ model = NonAutoPKBook
+
+
+class EditablePKBookTabularInline(admin.TabularInline):
+ model = EditablePKBook
+
+
+class EditablePKBookStackedInline(admin.StackedInline):
+ model = EditablePKBook
+
+
class AuthorAdmin(admin.ModelAdmin):
- inlines = [BookInline]
+ inlines = [BookInline,
+ NonAutoPKBookTabularInline, NonAutoPKBookStackedInline,
+ EditablePKBookTabularInline, EditablePKBookStackedInline]
class InnerInline(admin.StackedInline):
diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py
index d4ba0ab6bc..dde5cf79e3 100644
--- a/tests/admin_inlines/models.py
+++ b/tests/admin_inlines/models.py
@@ -3,6 +3,7 @@ Testing of admin inline formsets.
"""
from __future__ import unicode_literals
+import random
from django.db import models
from django.contrib.contenttypes.models import ContentType
@@ -48,6 +49,25 @@ class Author(models.Model):
books = models.ManyToManyField(Book)
+class NonAutoPKBook(models.Model):
+ rand_pk = models.IntegerField(primary_key=True, editable=False)
+ author = models.ForeignKey(Author)
+ title = models.CharField(max_length=50)
+
+ def save(self, *args, **kwargs):
+ while not self.rand_pk:
+ test_pk = random.randint(1, 99999)
+ if not NonAutoPKBook.objects.filter(rand_pk=test_pk).exists():
+ self.rand_pk = test_pk
+ super(NonAutoPKBook, self).save(*args, **kwargs)
+
+
+class EditablePKBook(models.Model):
+ manual_pk = models.IntegerField(primary_key=True)
+ author = models.ForeignKey(Author)
+ title = models.CharField(max_length=50)
+
+
class Holder(models.Model):
dummy = models.IntegerField()
diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py
index 41dc0e32de..465b224d4f 100644
--- a/tests/admin_inlines/tests.py
+++ b/tests/admin_inlines/tests.py
@@ -211,6 +211,24 @@ class TestInline(TestCase):
self.assertContains(response, max_forms_input % 2)
self.assertContains(response, total_forms_hidden)
+ def test_inline_nonauto_noneditable_pk(self):
+ response = self.client.get('/admin/admin_inlines/author/add/')
+ self.assertContains(response,
+ '<input id="id_nonautopkbook_set-0-rand_pk" name="nonautopkbook_set-0-rand_pk" type="hidden" />',
+ html=True)
+ self.assertContains(response,
+ '<input id="id_nonautopkbook_set-2-0-rand_pk" name="nonautopkbook_set-2-0-rand_pk" type="hidden" />',
+ html=True)
+
+ def test_inline_editable_pk(self):
+ response = self.client.get('/admin/admin_inlines/author/add/')
+ self.assertContains(response,
+ '<input class="vIntegerField" id="id_editablepkbook_set-0-manual_pk" name="editablepkbook_set-0-manual_pk" type="text" />',
+ html=True, count=1)
+ self.assertContains(response,
+ '<input class="vIntegerField" id="id_editablepkbook_set-2-0-manual_pk" name="editablepkbook_set-2-0-manual_pk" type="text" />',
+ html=True, count=1)
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class TestInlineMedia(TestCase):
diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py
index a6ad7cc0bc..039abb819b 100644
--- a/tests/admin_views/admin.py
+++ b/tests/admin_views/admin.py
@@ -777,3 +777,8 @@ from django.contrib.auth.models import User, Group
from django.contrib.auth.admin import UserAdmin, GroupAdmin
site.register(User, UserAdmin)
site.register(Group, GroupAdmin)
+
+# Used to test URL namespaces
+site2 = admin.AdminSite(name="namespaced_admin")
+site2.register(User, UserAdmin)
+site2.register(Group, GroupAdmin)
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 101fcd90f6..7decf6f471 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -26,7 +26,6 @@ from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.models import Group, User, Permission
from django.contrib.contenttypes.models import ContentType
-from django.core.urlresolvers import reverse
from django.db import connection
from django.forms.util import ErrorList
from django.template.response import TemplateResponse
@@ -53,6 +52,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject,
Simple, UndeletableObject, Choice, ShortMessage, Telegram)
+from .admin import site, site2
ERROR_MESSAGE = "Please enter the correct username and password \
@@ -3824,6 +3824,59 @@ class CSSTest(TestCase):
self.assertContains(response, '<tr class="model-actor">')
self.assertContains(response, '<tr class="model-album">')
+ def testAppModelInFormBodyClass(self):
+ """
+ Ensure app and model tag are correcly read by change_form template
+ """
+ response = self.client.get('/test_admin/admin/admin_views/section/add/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response,
+ '<body class="app-admin_views model-section ')
+
+ def testAppModelInListBodyClass(self):
+ """
+ Ensure app and model tag are correcly read by change_list template
+ """
+ response = self.client.get('/test_admin/admin/admin_views/section/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response,
+ '<body class="app-admin_views model-section ')
+
+ def testAppModelInDeleteConfirmationBodyClass(self):
+ """
+ Ensure app and model tag are correcly read by delete_confirmation
+ template
+ """
+ response = self.client.get(
+ '/test_admin/admin/admin_views/section/1/delete/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response,
+ '<body class="app-admin_views model-section ')
+
+ def testAppModelInAppIndexBodyClass(self):
+ """
+ Ensure app and model tag are correcly read by app_index template
+ """
+ response = self.client.get('/test_admin/admin/admin_views/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, '<body class="app-admin_views ')
+
+ def testAppModelInDeleteSelectedConfirmationBodyClass(self):
+ """
+ Ensure app and model tag are correcly read by
+ delete_selected_confirmation template
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ 'action': 'delete_selected',
+ 'index': 0,
+ }
+ response = self.client.post('/test_admin/admin/admin_views/section/',
+ action_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response,
+ '<body class="app-admin_views model-section ')
+
try:
import docutils
except ImportError:
@@ -4186,6 +4239,7 @@ class AdminUserMessageTest(TestCase):
class AdminKeepChangeListFiltersTests(TestCase):
urls = "admin_views.urls"
fixtures = ['admin-views-users.xml']
+ admin_site = site
def setUp(self):
self.client.login(username='super', password='secret')
@@ -4209,13 +4263,15 @@ class AdminKeepChangeListFiltersTests(TestCase):
def get_changelist_url(self):
return '%s?%s' % (
- reverse('admin:auth_user_changelist'),
+ reverse('admin:auth_user_changelist',
+ current_app=self.admin_site.name),
self.get_changelist_filters_querystring(),
)
def get_add_url(self):
return '%s?%s' % (
- reverse('admin:auth_user_add'),
+ reverse('admin:auth_user_add',
+ current_app=self.admin_site.name),
self.get_preserved_filters_querystring(),
)
@@ -4223,7 +4279,8 @@ class AdminKeepChangeListFiltersTests(TestCase):
if user_id is None:
user_id = self.get_sample_user_id()
return "%s?%s" % (
- reverse('admin:auth_user_change', args=(user_id,)),
+ reverse('admin:auth_user_change', args=(user_id,),
+ current_app=self.admin_site.name),
self.get_preserved_filters_querystring(),
)
@@ -4231,7 +4288,8 @@ class AdminKeepChangeListFiltersTests(TestCase):
if user_id is None:
user_id = self.get_sample_user_id()
return "%s?%s" % (
- reverse('admin:auth_user_history', args=(user_id,)),
+ reverse('admin:auth_user_history', args=(user_id,),
+ current_app=self.admin_site.name),
self.get_preserved_filters_querystring(),
)
@@ -4239,7 +4297,8 @@ class AdminKeepChangeListFiltersTests(TestCase):
if user_id is None:
user_id = self.get_sample_user_id()
return "%s?%s" % (
- reverse('admin:auth_user_delete', args=(user_id,)),
+ reverse('admin:auth_user_delete', args=(user_id,),
+ current_app=self.admin_site.name),
self.get_preserved_filters_querystring(),
)
@@ -4333,3 +4392,6 @@ class AdminKeepChangeListFiltersTests(TestCase):
# Test redirect on "Delete".
response = self.client.post(self.get_delete_url(), {'post': 'yes'})
self.assertRedirects(response, self.get_changelist_url())
+
+class NamespacedAdminKeepChangeListFiltersTests(AdminKeepChangeListFiltersTests):
+ admin_site = site2
diff --git a/tests/admin_views/urls.py b/tests/admin_views/urls.py
index 441834cd2a..763c83a450 100644
--- a/tests/admin_views/urls.py
+++ b/tests/admin_views/urls.py
@@ -12,4 +12,5 @@ urlpatterns = patterns('',
(r'^test_admin/admin2/', include(customadmin.site.urls)),
(r'^test_admin/admin3/', include(admin.site.urls), dict(form_url='pony')),
(r'^test_admin/admin4/', include(customadmin.simple_site.urls)),
+ (r'^test_admin/admin5/', include(admin.site2.urls)),
)
diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
index 8edb2f5904..870aa6d455 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -1,7 +1,7 @@
# encoding: utf-8
from __future__ import absolute_import, unicode_literals
-from datetime import datetime
+from datetime import datetime, timedelta
from unittest import TestCase
from django import forms
@@ -526,6 +526,64 @@ class DateTimePickerSeleniumIETests(DateTimePickerSeleniumFirefoxTests):
webdriver_class = 'selenium.webdriver.ie.webdriver.WebDriver'
+@override_settings(TIME_ZONE='Asia/Singapore')
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
+class DateTimePickerShortcutsSeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
+ available_apps = ['admin_widgets'] + AdminSeleniumWebDriverTestCase.available_apps
+ fixtures = ['admin-widgets-users.xml']
+ urls = "admin_widgets.urls"
+ webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver'
+
+ def test_date_time_picker_shortcuts(self):
+ """
+ Ensure that date/time/datetime picker shortcuts work in the current time zone.
+ Refs #20663.
+
+ This test case is fairly tricky, it relies on selenium still running the browser
+ in the default time zone "America/Chicago" despite `override_settings` changing
+ the time zone to "Asia/Singapore".
+ """
+ self.admin_login(username='super', password='secret', login_url='/')
+
+ now = datetime.now()
+ error_margin = timedelta(seconds=10)
+
+ self.selenium.get('%s%s' % (self.live_server_url,
+ '/admin_widgets/member/add/'))
+
+ self.selenium.find_element_by_id('id_name').send_keys('test')
+
+ # Click on the "today" and "now" shortcuts.
+ shortcuts = self.selenium.find_elements_by_css_selector(
+ '.field-birthdate .datetimeshortcuts')
+
+ for shortcut in shortcuts:
+ shortcut.find_element_by_tag_name('a').click()
+
+ # Check that there is a time zone mismatch warning.
+ # Warning: This would effectively fail if the TIME_ZONE defined in the
+ # settings has the same UTC offset as "Asia/Singapore" because the
+ # mismatch warning would be rightfully missing from the page.
+ self.selenium.find_elements_by_css_selector(
+ '.field-birthdate .timezonewarning')
+
+ # Submit the form.
+ self.selenium.find_element_by_tag_name('form').submit()
+ self.wait_page_loaded()
+
+ # Make sure that "now" in javascript is within 10 seconds
+ # from "now" on the server side.
+ member = models.Member.objects.get(name='test')
+ self.assertGreater(member.birthdate, now - error_margin)
+ self.assertLess(member.birthdate, now + error_margin)
+
+class DateTimePickerShortcutsSeleniumChromeTests(DateTimePickerShortcutsSeleniumFirefoxTests):
+ webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
+
+class DateTimePickerShortcutsSeleniumIETests(DateTimePickerShortcutsSeleniumFirefoxTests):
+ webdriver_class = 'selenium.webdriver.ie.webdriver.WebDriver'
+
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class HorizontalVerticalFilterSeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
diff --git a/tests/backends/tests.py b/tests/backends/tests.py
index b1bfa4cb21..ec592f0188 100644
--- a/tests/backends/tests.py
+++ b/tests/backends/tests.py
@@ -641,8 +641,7 @@ class FkConstraintsTests(TransactionTestCase):
"""
When constraint checks are disabled, should be able to write bad data without IntegrityErrors.
"""
- transaction.set_autocommit(False)
- try:
+ with transaction.atomic():
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
# Retrive it from the DB
@@ -654,17 +653,13 @@ class FkConstraintsTests(TransactionTestCase):
connection.enable_constraint_checking()
except IntegrityError:
self.fail("IntegrityError should not have occurred.")
- finally:
- transaction.rollback()
- finally:
- transaction.set_autocommit(True)
+ transaction.set_rollback(True)
def test_disable_constraint_checks_context_manager(self):
"""
When constraint checks are disabled (using context manager), should be able to write bad data without IntegrityErrors.
"""
- transaction.set_autocommit(False)
- try:
+ with transaction.atomic():
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
# Retrive it from the DB
@@ -675,31 +670,23 @@ class FkConstraintsTests(TransactionTestCase):
a.save()
except IntegrityError:
self.fail("IntegrityError should not have occurred.")
- finally:
- transaction.rollback()
- finally:
- transaction.set_autocommit(True)
+ transaction.set_rollback(True)
def test_check_constraints(self):
"""
Constraint checks should raise an IntegrityError when bad data is in the DB.
"""
- try:
- transaction.set_autocommit(False)
+ with transaction.atomic():
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
# Retrive it from the DB
a = models.Article.objects.get(headline="Test article")
a.reporter_id = 30
- try:
- with connection.constraint_checks_disabled():
- a.save()
- with self.assertRaises(IntegrityError):
- connection.check_constraints()
- finally:
- transaction.rollback()
- finally:
- transaction.set_autocommit(True)
+ with connection.constraint_checks_disabled():
+ a.save()
+ with self.assertRaises(IntegrityError):
+ connection.check_constraints()
+ transaction.set_rollback(True)
class ThreadTests(TestCase):
diff --git a/tests/basic/tests.py b/tests/basic/tests.py
index 4b79c47be5..fb21b11279 100644
--- a/tests/basic/tests.py
+++ b/tests/basic/tests.py
@@ -6,7 +6,7 @@ import threading
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.db import connections, DEFAULT_DB_ALIAS
from django.db.models.fields import Field, FieldDoesNotExist
-from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet
+from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet, MAX_GET_RESULTS
from django.test import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
from django.utils import six
from django.utils.translation import ugettext_lazy
@@ -155,6 +155,28 @@ class ModelTest(TestCase):
pub_date__month=7,
)
+ 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',
+ )
+
def test_object_creation(self):
# Create an Article.
a = Article(
diff --git a/tests/db_backends/__init__.py b/tests/db_backends/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/db_backends/__init__.py
diff --git a/tests/db_backends/tests.py b/tests/db_backends/tests.py
new file mode 100644
index 0000000000..052ee87254
--- /dev/null
+++ b/tests/db_backends/tests.py
@@ -0,0 +1,36 @@
+from django.test import TestCase
+from django.db.backends import BaseDatabaseWrapper
+
+
+class DummyDatabaseWrapper(BaseDatabaseWrapper):
+ pass
+
+
+class DummyObject(object):
+ alias = None
+
+
+class DbBackendTests(TestCase):
+ def test_compare_db_wrapper_with_another_object(self):
+ wrapper = BaseDatabaseWrapper({})
+ self.assertFalse(wrapper == 'not-a-db-wrapper')
+
+ def test_compare_db_wrapper_with_another_object_with_alias(self):
+ wrapper = BaseDatabaseWrapper({})
+ obj = DummyObject()
+ obj.alias = wrapper.alias = 'foobar'
+ self.assertFalse(wrapper == obj)
+
+ def test_negate_compare_db_wrapper_with_another_object(self):
+ wrapper = BaseDatabaseWrapper({})
+ self.assertTrue(wrapper != 'not-a-db-wrapper')
+
+ def test_compare_db_wrappers(self):
+ wrapper1 = DummyDatabaseWrapper({})
+ wrapper2 = BaseDatabaseWrapper({})
+
+ wrapper1.alias = wrapper2.alias = 'foo'
+ self.assertTrue(wrapper1 == wrapper2)
+
+ wrapper1.alias = 'bar'
+ self.assertFalse(wrapper1 == wrapper2)
diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py
index 66bc4465a2..f5a9c10be4 100644
--- a/tests/file_uploads/tests.py
+++ b/tests/file_uploads/tests.py
@@ -187,6 +187,27 @@ class FileUploadTests(TestCase):
got = json.loads(self.client.request(**r).content.decode('utf-8'))
self.assertTrue(len(got['file']) < 256, "Got a long file name (%s characters)." % len(got['file']))
+ def test_content_type_extra(self):
+ """Uploaded files may have content type parameters available."""
+ tdir = tempfile.gettempdir()
+
+ no_content_type = tempfile.NamedTemporaryFile(suffix=".ctype_extra", dir=tdir)
+ no_content_type.write(b'something')
+ no_content_type.seek(0)
+
+ simple_file = tempfile.NamedTemporaryFile(suffix=".ctype_extra", dir=tdir)
+ simple_file.write(b'something')
+ simple_file.seek(0)
+ simple_file.content_type = 'text/plain; test-key=test_value'
+
+ response = self.client.post('/file_uploads/echo_content_type_extra/', {
+ 'no_content_type': no_content_type,
+ 'simple_file': simple_file,
+ })
+ received = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(received['no_content_type'], {})
+ self.assertEqual(received['simple_file'], {'test-key': 'test_value'})
+
def test_truncated_multipart_handled_gracefully(self):
"""
If passed an incomplete multipart message, MultiPartParser does not
diff --git a/tests/file_uploads/urls.py b/tests/file_uploads/urls.py
index fc5576828f..96efaaa5d8 100644
--- a/tests/file_uploads/urls.py
+++ b/tests/file_uploads/urls.py
@@ -10,6 +10,7 @@ urlpatterns = patterns('',
(r'^verify/$', views.file_upload_view_verify),
(r'^unicode_name/$', views.file_upload_unicode_name),
(r'^echo/$', views.file_upload_echo),
+ (r'^echo_content_type_extra/$', views.file_upload_content_type_extra),
(r'^echo_content/$', views.file_upload_echo_content),
(r'^quota/$', views.file_upload_quota),
(r'^quota/broken/$', views.file_upload_quota_broken),
diff --git a/tests/file_uploads/views.py b/tests/file_uploads/views.py
index eb7b654c09..d1bd2cf44e 100644
--- a/tests/file_uploads/views.py
+++ b/tests/file_uploads/views.py
@@ -7,7 +7,7 @@ import os
from django.core.files.uploadedfile import UploadedFile
from django.http import HttpResponse, HttpResponseServerError
from django.utils import six
-from django.utils.encoding import force_bytes
+from django.utils.encoding import force_bytes, smart_str
from .models import FileModel
from .tests import UNICODE_FILENAME, UPLOAD_TO
@@ -136,3 +136,14 @@ def file_upload_filename_case_view(request):
obj = FileModel()
obj.testfile.save(file.name, file)
return HttpResponse('%d' % obj.pk)
+
+def file_upload_content_type_extra(request):
+ """
+ Simple view to echo back extra content-type parameters.
+ """
+ params = {}
+ for file_name, uploadedfile in request.FILES.items():
+ params[file_name] = dict([
+ (k, smart_str(v)) for k, v in uploadedfile.content_type_extra.items()
+ ])
+ return HttpResponse(json.dumps(params))
diff --git a/tests/fixtures_model_package/tests.py b/tests/fixtures_model_package/tests.py
index 4b00e6dfb9..ad82267da3 100644
--- a/tests/fixtures_model_package/tests.py
+++ b/tests/fixtures_model_package/tests.py
@@ -30,8 +30,7 @@ class TestNoInitialDataLoading(TransactionTestCase):
available_apps = ['fixtures_model_package']
def test_syncdb(self):
- transaction.set_autocommit(False)
- try:
+ with transaction.atomic():
Book.objects.all().delete()
management.call_command(
@@ -40,9 +39,6 @@ class TestNoInitialDataLoading(TransactionTestCase):
load_initial_data=False
)
self.assertQuerysetEqual(Book.objects.all(), [])
- transaction.rollback()
- finally:
- transaction.set_autocommit(True)
def test_flush(self):
@@ -54,8 +50,7 @@ class TestNoInitialDataLoading(TransactionTestCase):
lambda a: a.name
)
- transaction.set_autocommit(False)
- try:
+ with transaction.atomic():
management.call_command(
'flush',
verbosity=0,
@@ -63,9 +58,6 @@ class TestNoInitialDataLoading(TransactionTestCase):
load_initial_data=False
)
self.assertQuerysetEqual(Book.objects.all(), [])
- transaction.rollback()
- finally:
- transaction.set_autocommit(True)
class FixtureTestCase(TestCase):
diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py
index a9d67ec9a2..a6bad5716d 100644
--- a/tests/fixtures_regress/tests.py
+++ b/tests/fixtures_regress/tests.py
@@ -660,22 +660,15 @@ class TestTicket11101(TransactionTestCase):
'django.contrib.contenttypes',
]
- def ticket_11101(self):
- management.call_command(
- 'loaddata',
- 'thingy.json',
- verbosity=0,
- )
- self.assertEqual(Thingy.objects.count(), 1)
- transaction.rollback()
- self.assertEqual(Thingy.objects.count(), 0)
- transaction.commit()
-
@skipUnlessDBFeature('supports_transactions')
def test_ticket_11101(self):
"""Test that fixtures can be rolled back (ticket #11101)."""
- transaction.set_autocommit(False)
- try:
- self.ticket_11101()
- finally:
- transaction.set_autocommit(True)
+ with transaction.atomic():
+ management.call_command(
+ 'loaddata',
+ 'thingy.json',
+ verbosity=0,
+ )
+ self.assertEqual(Thingy.objects.count(), 1)
+ transaction.set_rollback(True)
+ self.assertEqual(Thingy.objects.count(), 0)
diff --git a/tests/forms_tests/tests/test_extra.py b/tests/forms_tests/tests/test_extra.py
index ea0f063c30..d439e2223c 100644
--- a/tests/forms_tests/tests/test_extra.py
+++ b/tests/forms_tests/tests/test_extra.py
@@ -391,7 +391,7 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin):
def decompress(self, value):
if value:
data = value.split(',')
- return [data[0], data[1], datetime.datetime.strptime(data[2], "%Y-%m-%d %H:%M:%S")]
+ return [data[0], list(data[1]), datetime.datetime.strptime(data[2], "%Y-%m-%d %H:%M:%S")]
return [None, None, None]
def format_output(self, rendered_widgets):
diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py
index f7d83503f4..f02593e488 100644
--- a/tests/forms_tests/tests/test_fields.py
+++ b/tests/forms_tests/tests/test_fields.py
@@ -374,6 +374,17 @@ class FieldsTests(SimpleTestCase):
self.assertEqual(f.clean('.01'), Decimal(".01"))
self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 0 digits before the decimal point.'", f.clean, '1.1')
+ def test_decimalfield_widget_attrs(self):
+ f = DecimalField(max_digits=6, decimal_places=2)
+ self.assertEqual(f.widget_attrs(Widget()), {})
+ self.assertEqual(f.widget_attrs(NumberInput()), {'step': '0.01'})
+ f = DecimalField(max_digits=10, decimal_places=0)
+ self.assertEqual(f.widget_attrs(NumberInput()), {'step': '1'})
+ f = DecimalField(max_digits=19, decimal_places=19)
+ self.assertEqual(f.widget_attrs(NumberInput()), {'step': '1e-19'})
+ f = DecimalField(max_digits=20)
+ self.assertEqual(f.widget_attrs(NumberInput()), {'step': 'any'})
+
def test_decimalfield_localized(self):
"""
Make sure localized DecimalField's widget renders to a text input with
@@ -1236,6 +1247,7 @@ class FieldsTests(SimpleTestCase):
def test_splitdatetimefield_changed(self):
f = SplitDateTimeField(input_date_formats=['%d/%m/%Y'])
+ self.assertFalse(f._has_changed(['11/01/2012', '09:18:15'], ['11/01/2012', '09:18:15']))
self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['2008-05-06', '12:40:00']))
self.assertFalse(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40']))
self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41']))
diff --git a/tests/forms_tests/views.py b/tests/forms_tests/views.py
index 4bf384363c..2081e0af14 100644
--- a/tests/forms_tests/views.py
+++ b/tests/forms_tests/views.py
@@ -6,3 +6,4 @@ from .models import Article
class ArticleFormView(UpdateView):
model = Article
success_url = '/'
+ fields = '__all__'
diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py
index 847a6dec01..0f766ab128 100644
--- a/tests/get_or_create/tests.py
+++ b/tests/get_or_create/tests.py
@@ -131,3 +131,68 @@ class GetOrCreateThroughManyToMany(TestCase):
Tag.objects.create(text='foo')
a_thing = Thing.objects.create(name='a')
self.assertRaises(IntegrityError, a_thing.tags.get_or_create, text='foo')
+
+
+class UpdateOrCreateTests(TestCase):
+
+ def test_update(self):
+ Person.objects.create(
+ first_name='John', last_name='Lennon', birthday=date(1940, 10, 9)
+ )
+ p, created = Person.objects.update_or_create(
+ first_name='John', last_name='Lennon', defaults={
+ 'birthday': date(1940, 10, 10)
+ }
+ )
+ self.assertFalse(created)
+ self.assertEqual(p.first_name, 'John')
+ self.assertEqual(p.last_name, 'Lennon')
+ self.assertEqual(p.birthday, date(1940, 10, 10))
+
+ def test_create(self):
+ p, created = Person.objects.update_or_create(
+ first_name='John', last_name='Lennon', defaults={
+ 'birthday': date(1940, 10, 10)
+ }
+ )
+ self.assertTrue(created)
+ self.assertEqual(p.first_name, 'John')
+ self.assertEqual(p.last_name, 'Lennon')
+ self.assertEqual(p.birthday, date(1940, 10, 10))
+
+ def test_create_twice(self):
+ params = {
+ 'first_name': 'John',
+ 'last_name': 'Lennon',
+ 'birthday': date(1940, 10, 10),
+ }
+ Person.objects.update_or_create(**params)
+ # If we execute the exact same statement, it won't create a Person.
+ p, created = Person.objects.update_or_create(**params)
+ self.assertFalse(created)
+
+ def test_integrity(self):
+ # If you don't specify a value or default value for all required
+ # fields, you will get an error.
+ self.assertRaises(IntegrityError,
+ Person.objects.update_or_create, first_name="Tom", last_name="Smith")
+
+ def test_mananual_primary_key_test(self):
+ # If you specify an existing primary key, but different other fields,
+ # then you will get an error and data will not be updated.
+ ManualPrimaryKeyTest.objects.create(id=1, data="Original")
+ self.assertRaises(IntegrityError,
+ ManualPrimaryKeyTest.objects.update_or_create, id=1, data="Different"
+ )
+ self.assertEqual(ManualPrimaryKeyTest.objects.get(id=1).data, "Original")
+
+ def test_error_contains_full_traceback(self):
+ # update_or_create should raise IntegrityErrors with the full traceback.
+ # This is tested by checking that a known method call is in the traceback.
+ # We cannot use assertRaises/assertRaises here because we need to inspect
+ # the actual traceback. Refs #16340.
+ try:
+ ManualPrimaryKeyTest.objects.update_or_create(id=1, data="Different")
+ except IntegrityError as e:
+ formatted_traceback = traceback.format_exc()
+ self.assertIn('obj.save', formatted_traceback)
diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py
index cae2860306..8ba4702aba 100644
--- a/tests/logging_tests/tests.py
+++ b/tests/logging_tests/tests.py
@@ -19,7 +19,6 @@ from admin_scripts.tests import AdminScriptTestCase
from .logconfig import MyEmailBackend
-PYVERS = sys.version_info[:2]
# logging config prior to using filter with mail_admins
OLD_LOGGING = {
@@ -87,7 +86,6 @@ class DefaultLoggingTest(TestCase):
self.logger.error("Hey, this is an error.")
self.assertEqual(output.getvalue(), 'Hey, this is an error.\n')
-@skipUnless(PYVERS > (2,6), "warnings captured only in Python >= 2.7")
class WarningLoggerTests(TestCase):
"""
Tests that warnings output for DeprecationWarnings is enabled
diff --git a/tests/mail/tests.py b/tests/mail/tests.py
index c90dc7e22a..822c572b0f 100644
--- a/tests/mail/tests.py
+++ b/tests/mail/tests.py
@@ -9,6 +9,8 @@ import smtpd
import sys
import tempfile
import threading
+from smtplib import SMTPException
+from ssl import SSLError
from django.core import mail
from django.core.mail import (EmailMessage, mail_admins, mail_managers,
@@ -621,11 +623,23 @@ class ConsoleBackendTests(BaseEmailBackendTests, TestCase):
self.assertTrue(s.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: '))
+class FakeSMTPChannel(smtpd.SMTPChannel):
+
+ def collect_incoming_data(self, data):
+ try:
+ super(FakeSMTPChannel, self).collect_incoming_data(data)
+ except UnicodeDecodeError:
+ # ignore decode error in SSL/TLS connection tests as we only care
+ # whether the connection attempt was made
+ pass
+
+
class FakeSMTPServer(smtpd.SMTPServer, threading.Thread):
"""
Asyncore SMTP server wrapped into a thread. Based on DummyFTPServer from:
http://svn.python.org/view/python/branches/py3k/Lib/test/test_ftplib.py?revision=86061&view=markup
"""
+ channel_class = FakeSMTPChannel
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self)
@@ -738,3 +752,44 @@ class SMTPBackendTests(BaseEmailBackendTests, TestCase):
backend.close()
except Exception as e:
self.fail("close() unexpectedly raised an exception: %s" % e)
+
+ @override_settings(EMAIL_USE_TLS=True)
+ def test_email_tls_use_settings(self):
+ backend = smtp.EmailBackend()
+ self.assertTrue(backend.use_tls)
+
+ @override_settings(EMAIL_USE_TLS=True)
+ def test_email_tls_override_settings(self):
+ backend = smtp.EmailBackend(use_tls=False)
+ self.assertFalse(backend.use_tls)
+
+ def test_email_tls_default_disabled(self):
+ backend = smtp.EmailBackend()
+ self.assertFalse(backend.use_tls)
+
+ @override_settings(EMAIL_USE_SSL=True)
+ def test_email_ssl_use_settings(self):
+ backend = smtp.EmailBackend()
+ self.assertTrue(backend.use_ssl)
+
+ @override_settings(EMAIL_USE_SSL=True)
+ def test_email_ssl_override_settings(self):
+ backend = smtp.EmailBackend(use_ssl=False)
+ self.assertFalse(backend.use_ssl)
+
+ def test_email_ssl_default_disabled(self):
+ backend = smtp.EmailBackend()
+ self.assertFalse(backend.use_ssl)
+
+ @override_settings(EMAIL_USE_TLS=True)
+ def test_email_tls_attempts_starttls(self):
+ backend = smtp.EmailBackend()
+ self.assertTrue(backend.use_tls)
+ self.assertRaisesMessage(SMTPException,
+ 'STARTTLS extension not supported by server.', backend.open)
+
+ @override_settings(EMAIL_USE_SSL=True)
+ def test_email_ssl_attempts_ssl_connection(self):
+ backend = smtp.EmailBackend()
+ self.assertTrue(backend.use_ssl)
+ self.assertRaises(SSLError, backend.open)
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py
index 6546c49ec7..1ce5eba3e9 100644
--- a/tests/model_fields/tests.py
+++ b/tests/model_fields/tests.py
@@ -468,3 +468,16 @@ class BinaryFieldTests(test.TestCase):
def test_max_length(self):
dm = DataModel(short_data=self.binary_data*4)
self.assertRaises(ValidationError, dm.full_clean)
+
+class GenericIPAddressFieldTests(test.TestCase):
+ def test_genericipaddressfield_formfield_protocol(self):
+ """
+ Test that GenericIPAddressField with a specified protocol does not
+ generate a formfield with no specified protocol. See #20740.
+ """
+ model_field = models.GenericIPAddressField(protocol='IPv4')
+ form_field = model_field.formfield()
+ self.assertRaises(ValidationError, form_field.clean, '::1')
+ model_field = models.GenericIPAddressField(protocol='IPv6')
+ form_field = model_field.formfield()
+ self.assertRaises(ValidationError, form_field.clean, '127.0.0.1')
diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py
index 09a3ba1778..4411bbf59a 100644
--- a/tests/model_formsets/tests.py
+++ b/tests/model_formsets/tests.py
@@ -576,7 +576,7 @@ class ModelFormsetTest(TestCase):
formset = AuthorBooksFormSet2(instance=author)
self.assertEqual(len(formset.forms), 1)
self.assertHTMLEqual(formset.forms[0].as_p(),
- '<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label> <input id="id_bookwithcustompk_set-0-my_pk" type="number" name="bookwithcustompk_set-0-my_pk" /></p>\n'
+ '<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label> <input id="id_bookwithcustompk_set-0-my_pk" type="number" name="bookwithcustompk_set-0-my_pk" step="1" /></p>\n'
'<p><label for="id_bookwithcustompk_set-0-title">Title:</label> <input id="id_bookwithcustompk_set-0-title" type="text" name="bookwithcustompk_set-0-title" maxlength="100" /><input type="hidden" name="bookwithcustompk_set-0-author" value="1" id="id_bookwithcustompk_set-0-author" /></p>')
data = {
diff --git a/tests/model_package/models/article.py b/tests/model_package/models/article.py
index f70c971c7e..4dfcdc1056 100644
--- a/tests/model_package/models/article.py
+++ b/tests/model_package/models/article.py
@@ -6,6 +6,3 @@ class Article(models.Model):
sites = models.ManyToManyField(Site)
headline = models.CharField(max_length=100)
publications = models.ManyToManyField("model_package.Publication", null=True, blank=True,)
-
- class Meta:
- app_label = 'model_package'
diff --git a/tests/model_package/models/publication.py b/tests/model_package/models/publication.py
index 29828b9295..b0a49a94a1 100644
--- a/tests/model_package/models/publication.py
+++ b/tests/model_package/models/publication.py
@@ -3,6 +3,3 @@ from django.db import models
class Publication(models.Model):
title = models.CharField(max_length=30)
-
- class Meta:
- app_label = 'model_package'
diff --git a/tests/model_package/tests.py b/tests/model_package/tests.py
index 5cf464858b..5d856a9608 100644
--- a/tests/model_package/tests.py
+++ b/tests/model_package/tests.py
@@ -14,9 +14,6 @@ class Advertisment(models.Model):
"model_package.Publication", null=True, blank=True
)
- class Meta:
- app_label = 'model_package'
-
class ModelPackageTests(TestCase):
def test_model_packages(self):
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index 6520eb0176..87d54b637e 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -2148,13 +2148,6 @@ class ConditionalTests(BaseQuerysetTest):
t4 = Tag.objects.create(name='t4', parent=t3)
t5 = Tag.objects.create(name='t5', parent=t3)
-
- # In Python 2.6 beta releases, exceptions raised in __len__ are swallowed
- # (Python issue 1242657), so these cases return an empty list, rather than
- # raising an exception. Not a lot we can do about that, unfortunately, due to
- # the way Python handles list() calls internally. Thus, we skip the tests for
- # Python 2.6.
- @unittest.skipIf(sys.version_info[:2] == (2, 6), "Python version is 2.6")
def test_infinite_loop(self):
# If you're not careful, it's possible to introduce infinite loops via
# default ordering on foreign keys in a cycle. We detect that.
diff --git a/tests/requests/tests.py b/tests/requests/tests.py
index b00eec87a9..8c56e48f58 100644
--- a/tests/requests/tests.py
+++ b/tests/requests/tests.py
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
from datetime import datetime, timedelta
from io import BytesIO
+from itertools import chain
import time
from unittest import skipIf
import warnings
@@ -122,221 +123,6 @@ class RequestsTests(SimpleTestCase):
self.assertEqual(request.build_absolute_uri(location="/path/with:colons"),
'http://www.example.com/path/with:colons')
- @override_settings(
- USE_X_FORWARDED_HOST=False,
- ALLOWED_HOSTS=[
- 'forward.com', 'example.com', 'internal.com', '12.34.56.78',
- '[2001:19f0:feee::dead:beef:cafe]', 'xn--4ca9at.com',
- '.multitenant.com', 'INSENSITIVE.com',
- ])
- def test_http_get_host(self):
- # Check if X_FORWARDED_HOST is provided.
- request = HttpRequest()
- request.META = {
- 'HTTP_X_FORWARDED_HOST': 'forward.com',
- 'HTTP_HOST': 'example.com',
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 80,
- }
- # X_FORWARDED_HOST is ignored.
- self.assertEqual(request.get_host(), 'example.com')
-
- # Check if X_FORWARDED_HOST isn't provided.
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': 'example.com',
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 80,
- }
- self.assertEqual(request.get_host(), 'example.com')
-
- # Check if HTTP_HOST isn't provided.
- request = HttpRequest()
- request.META = {
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 80,
- }
- self.assertEqual(request.get_host(), 'internal.com')
-
- # Check if HTTP_HOST isn't provided, and we're on a nonstandard port
- request = HttpRequest()
- request.META = {
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 8042,
- }
- self.assertEqual(request.get_host(), 'internal.com:8042')
-
- # Poisoned host headers are rejected as suspicious
- legit_hosts = [
- 'example.com',
- 'example.com:80',
- '12.34.56.78',
- '12.34.56.78:443',
- '[2001:19f0:feee::dead:beef:cafe]',
- '[2001:19f0:feee::dead:beef:cafe]:8080',
- 'xn--4ca9at.com', # Punnycode for öäü.com
- 'anything.multitenant.com',
- 'multitenant.com',
- 'insensitive.com',
- ]
-
- poisoned_hosts = [
- 'example.com@evil.tld',
- 'example.com:dr.frankenstein@evil.tld',
- 'example.com:dr.frankenstein@evil.tld:80',
- 'example.com:80/badpath',
- 'example.com: recovermypassword.com',
- 'other.com', # not in ALLOWED_HOSTS
- ]
-
- for host in legit_hosts:
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': host,
- }
- request.get_host()
-
- for host in poisoned_hosts:
- with self.assertRaises(SuspiciousOperation):
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': host,
- }
- request.get_host()
-
- @override_settings(USE_X_FORWARDED_HOST=True, ALLOWED_HOSTS=['*'])
- def test_http_get_host_with_x_forwarded_host(self):
- # Check if X_FORWARDED_HOST is provided.
- request = HttpRequest()
- request.META = {
- 'HTTP_X_FORWARDED_HOST': 'forward.com',
- 'HTTP_HOST': 'example.com',
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 80,
- }
- # X_FORWARDED_HOST is obeyed.
- self.assertEqual(request.get_host(), 'forward.com')
-
- # Check if X_FORWARDED_HOST isn't provided.
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': 'example.com',
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 80,
- }
- self.assertEqual(request.get_host(), 'example.com')
-
- # Check if HTTP_HOST isn't provided.
- request = HttpRequest()
- request.META = {
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 80,
- }
- self.assertEqual(request.get_host(), 'internal.com')
-
- # Check if HTTP_HOST isn't provided, and we're on a nonstandard port
- request = HttpRequest()
- request.META = {
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 8042,
- }
- self.assertEqual(request.get_host(), 'internal.com:8042')
-
- # Poisoned host headers are rejected as suspicious
- legit_hosts = [
- 'example.com',
- 'example.com:80',
- '12.34.56.78',
- '12.34.56.78:443',
- '[2001:19f0:feee::dead:beef:cafe]',
- '[2001:19f0:feee::dead:beef:cafe]:8080',
- 'xn--4ca9at.com', # Punnycode for öäü.com
- ]
-
- poisoned_hosts = [
- 'example.com@evil.tld',
- 'example.com:dr.frankenstein@evil.tld',
- 'example.com:dr.frankenstein@evil.tld:80',
- 'example.com:80/badpath',
- 'example.com: recovermypassword.com',
- ]
-
- for host in legit_hosts:
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': host,
- }
- request.get_host()
-
- for host in poisoned_hosts:
- with self.assertRaises(SuspiciousOperation):
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': host,
- }
- request.get_host()
-
-
- @override_settings(DEBUG=True, ALLOWED_HOSTS=[])
- def test_host_validation_disabled_in_debug_mode(self):
- """If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass."""
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': 'example.com',
- }
- self.assertEqual(request.get_host(), 'example.com')
-
-
- @override_settings(ALLOWED_HOSTS=[])
- def test_get_host_suggestion_of_allowed_host(self):
- """get_host() makes helpful suggestions if a valid-looking host is not in ALLOWED_HOSTS."""
- msg_invalid_host = "Invalid HTTP_HOST header: %r."
- msg_suggestion = msg_invalid_host + "You may need to add %r to ALLOWED_HOSTS."
-
- for host in [ # Valid-looking hosts
- 'example.com',
- '12.34.56.78',
- '[2001:19f0:feee::dead:beef:cafe]',
- 'xn--4ca9at.com', # Punnycode for öäü.com
- ]:
- request = HttpRequest()
- request.META = {'HTTP_HOST': host}
- self.assertRaisesMessage(
- SuspiciousOperation,
- msg_suggestion % (host, host),
- request.get_host
- )
-
- for domain, port in [ # Valid-looking hosts with a port number
- ('example.com', 80),
- ('12.34.56.78', 443),
- ('[2001:19f0:feee::dead:beef:cafe]', 8080),
- ]:
- host = '%s:%s' % (domain, port)
- request = HttpRequest()
- request.META = {'HTTP_HOST': host}
- self.assertRaisesMessage(
- SuspiciousOperation,
- msg_suggestion % (host, domain),
- request.get_host
- )
-
- for host in [ # Invalid hosts
- 'example.com@evil.tld',
- 'example.com:dr.frankenstein@evil.tld',
- 'example.com:dr.frankenstein@evil.tld:80',
- 'example.com:80/badpath',
- 'example.com: recovermypassword.com',
- ]:
- request = HttpRequest()
- request.META = {'HTTP_HOST': host}
- self.assertRaisesMessage(
- SuspiciousOperation,
- msg_invalid_host % host,
- request.get_host
- )
-
-
def test_near_expiration(self):
"Cookie will expire when an near expiration time is provided"
response = HttpResponse()
@@ -678,8 +464,209 @@ class RequestsTests(SimpleTestCase):
request.FILES
+class HostValidationTests(SimpleTestCase):
+ poisoned_hosts = [
+ 'example.com@evil.tld',
+ 'example.com:dr.frankenstein@evil.tld',
+ 'example.com:dr.frankenstein@evil.tld:80',
+ 'example.com:80/badpath',
+ 'example.com: recovermypassword.com',
+ ]
+
+ @override_settings(
+ USE_X_FORWARDED_HOST=False,
+ ALLOWED_HOSTS=[
+ 'forward.com', 'example.com', 'internal.com', '12.34.56.78',
+ '[2001:19f0:feee::dead:beef:cafe]', 'xn--4ca9at.com',
+ '.multitenant.com', 'INSENSITIVE.com',
+ ])
+ def test_http_get_host(self):
+ # Check if X_FORWARDED_HOST is provided.
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_X_FORWARDED_HOST': 'forward.com',
+ 'HTTP_HOST': 'example.com',
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 80,
+ }
+ # X_FORWARDED_HOST is ignored.
+ self.assertEqual(request.get_host(), 'example.com')
+
+ # Check if X_FORWARDED_HOST isn't provided.
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': 'example.com',
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 80,
+ }
+ self.assertEqual(request.get_host(), 'example.com')
+
+ # Check if HTTP_HOST isn't provided.
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 80,
+ }
+ self.assertEqual(request.get_host(), 'internal.com')
+
+ # Check if HTTP_HOST isn't provided, and we're on a nonstandard port
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 8042,
+ }
+ self.assertEqual(request.get_host(), 'internal.com:8042')
+
+ legit_hosts = [
+ 'example.com',
+ 'example.com:80',
+ '12.34.56.78',
+ '12.34.56.78:443',
+ '[2001:19f0:feee::dead:beef:cafe]',
+ '[2001:19f0:feee::dead:beef:cafe]:8080',
+ 'xn--4ca9at.com', # Punnycode for öäü.com
+ 'anything.multitenant.com',
+ 'multitenant.com',
+ 'insensitive.com',
+ ]
+
+ for host in legit_hosts:
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': host,
+ }
+ request.get_host()
+
+ # Poisoned host headers are rejected as suspicious
+ for host in chain(self.poisoned_hosts, ['other.com']):
+ with self.assertRaises(SuspiciousOperation):
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': host,
+ }
+ request.get_host()
+
+ @override_settings(USE_X_FORWARDED_HOST=True, ALLOWED_HOSTS=['*'])
+ def test_http_get_host_with_x_forwarded_host(self):
+ # Check if X_FORWARDED_HOST is provided.
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_X_FORWARDED_HOST': 'forward.com',
+ 'HTTP_HOST': 'example.com',
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 80,
+ }
+ # X_FORWARDED_HOST is obeyed.
+ self.assertEqual(request.get_host(), 'forward.com')
+
+ # Check if X_FORWARDED_HOST isn't provided.
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': 'example.com',
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 80,
+ }
+ self.assertEqual(request.get_host(), 'example.com')
+
+ # Check if HTTP_HOST isn't provided.
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 80,
+ }
+ self.assertEqual(request.get_host(), 'internal.com')
+
+ # Check if HTTP_HOST isn't provided, and we're on a nonstandard port
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 8042,
+ }
+ self.assertEqual(request.get_host(), 'internal.com:8042')
+
+ # Poisoned host headers are rejected as suspicious
+ legit_hosts = [
+ 'example.com',
+ 'example.com:80',
+ '12.34.56.78',
+ '12.34.56.78:443',
+ '[2001:19f0:feee::dead:beef:cafe]',
+ '[2001:19f0:feee::dead:beef:cafe]:8080',
+ 'xn--4ca9at.com', # Punnycode for öäü.com
+ ]
+
+ for host in legit_hosts:
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': host,
+ }
+ request.get_host()
+
+ for host in self.poisoned_hosts:
+ with self.assertRaises(SuspiciousOperation):
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': host,
+ }
+ request.get_host()
+
+
+ @override_settings(DEBUG=True, ALLOWED_HOSTS=[])
+ def test_host_validation_disabled_in_debug_mode(self):
+ """If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass."""
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': 'example.com',
+ }
+ self.assertEqual(request.get_host(), 'example.com')
+
+
+ @override_settings(ALLOWED_HOSTS=[])
+ def test_get_host_suggestion_of_allowed_host(self):
+ """get_host() makes helpful suggestions if a valid-looking host is not in ALLOWED_HOSTS."""
+ msg_invalid_host = "Invalid HTTP_HOST header: %r."
+ msg_suggestion = msg_invalid_host + "You may need to add %r to ALLOWED_HOSTS."
+
+ for host in [ # Valid-looking hosts
+ 'example.com',
+ '12.34.56.78',
+ '[2001:19f0:feee::dead:beef:cafe]',
+ 'xn--4ca9at.com', # Punnycode for öäü.com
+ ]:
+ request = HttpRequest()
+ request.META = {'HTTP_HOST': host}
+ self.assertRaisesMessage(
+ SuspiciousOperation,
+ msg_suggestion % (host, host),
+ request.get_host
+ )
+
+ for domain, port in [ # Valid-looking hosts with a port number
+ ('example.com', 80),
+ ('12.34.56.78', 443),
+ ('[2001:19f0:feee::dead:beef:cafe]', 8080),
+ ]:
+ host = '%s:%s' % (domain, port)
+ request = HttpRequest()
+ request.META = {'HTTP_HOST': host}
+ self.assertRaisesMessage(
+ SuspiciousOperation,
+ msg_suggestion % (host, domain),
+ request.get_host
+ )
+
+ for host in self.poisoned_hosts:
+ request = HttpRequest()
+ request.META = {'HTTP_HOST': host}
+ self.assertRaisesMessage(
+ SuspiciousOperation,
+ msg_invalid_host % host,
+ request.get_host
+ )
+
+
@skipIf(connection.vendor == 'sqlite'
- and connection.settings_dict['NAME'] in ('', ':memory:'),
+ and connection.settings_dict['TEST_NAME'] in (None, '', ':memory:'),
"Cannot establish two connections to an in-memory SQLite database.")
class DatabaseConnectionHandlingTests(TransactionTestCase):
diff --git a/tests/requirements/base.txt b/tests/requirements/base.txt
new file mode 100644
index 0000000000..89bd8230b2
--- /dev/null
+++ b/tests/requirements/base.txt
@@ -0,0 +1,6 @@
+bcrypt
+docutils
+Pillow
+PyYAML
+pytz
+selenium
diff --git a/tests/requirements/mysql.txt b/tests/requirements/mysql.txt
new file mode 100644
index 0000000000..c7a2347407
--- /dev/null
+++ b/tests/requirements/mysql.txt
@@ -0,0 +1 @@
+MySQL-python
diff --git a/tests/requirements/oracle.txt b/tests/requirements/oracle.txt
new file mode 100644
index 0000000000..ae5b7349cd
--- /dev/null
+++ b/tests/requirements/oracle.txt
@@ -0,0 +1 @@
+cx_oracle
diff --git a/tests/requirements/postgres.txt b/tests/requirements/postgres.txt
new file mode 100644
index 0000000000..658130bb2c
--- /dev/null
+++ b/tests/requirements/postgres.txt
@@ -0,0 +1 @@
+psycopg2
diff --git a/tests/requirements/py2.txt b/tests/requirements/py2.txt
new file mode 100644
index 0000000000..a9f6804039
--- /dev/null
+++ b/tests/requirements/py2.txt
@@ -0,0 +1,2 @@
+-r base.txt
+python-memcached
diff --git a/tests/requirements/py3.txt b/tests/requirements/py3.txt
new file mode 100644
index 0000000000..ced3eed101
--- /dev/null
+++ b/tests/requirements/py3.txt
@@ -0,0 +1,2 @@
+-r base.txt
+python3-memcached
diff --git a/tests/runtests.py b/tests/runtests.py
index b604155190..8d5b375fb3 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -22,6 +22,8 @@ TEMP_DIR = tempfile.mkdtemp(prefix='django_')
os.environ['DJANGO_TEST_TEMP_DIR'] = TEMP_DIR
SUBDIRS_TO_SKIP = [
+ 'data',
+ 'requirements',
'templates',
'test_discovery_sample',
'test_discovery_sample2',
@@ -49,10 +51,18 @@ ALWAYS_INSTALLED_APPS = [
def get_test_modules():
+ from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
modules = []
- for modpath, dirpath in (
+ discovery_paths = [
(None, RUNTESTS_DIR),
- (CONTRIB_MODULE_PATH, CONTRIB_DIR)):
+ (CONTRIB_MODULE_PATH, CONTRIB_DIR)
+ ]
+ if HAS_SPATIAL_DB:
+ discovery_paths.append(
+ ('django.contrib.gis.tests', os.path.join(CONTRIB_DIR, 'gis', 'tests'))
+ )
+
+ for modpath, dirpath in discovery_paths:
for f in os.listdir(dirpath):
if ('.' in f or
# Python 3 byte code dirs (PEP 3147)
@@ -64,6 +74,7 @@ def get_test_modules():
modules.append((modpath, f))
return modules
+
def get_installed():
from django.db.models.loading import get_apps
return [app.__name__.rsplit('.', 1)[0] for app in get_apps()]
@@ -125,14 +136,6 @@ def setup(verbosity, test_labels):
bits = bits[:1]
test_labels_set.add('.'.join(bits))
- # If GeoDjango, then we'll want to add in the test applications
- # that are a part of its test suite.
- from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
- if HAS_SPATIAL_DB:
- from django.contrib.gis.tests import geo_apps
- test_modules.extend(geo_apps())
- settings.INSTALLED_APPS.extend(['django.contrib.gis', 'django.contrib.sitemaps'])
-
for modpath, module_name in test_modules:
if modpath:
module_label = '.'.join([modpath, module_name])
diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py
index b8a8ad19bf..3204d74224 100644
--- a/tests/select_for_update/tests.py
+++ b/tests/select_for_update/tests.py
@@ -99,15 +99,8 @@ class SelectForUpdateTests(TransactionTestCase):
list(Person.objects.all().select_for_update(nowait=True))
self.assertTrue(self.has_for_update_sql(connection, nowait=True))
- # In Python 2.6 beta and some final releases, exceptions raised in __len__
- # are swallowed (Python issue 1242657), so these cases return an empty
- # list, rather than raising an exception. Not a lot we can do about that,
- # unfortunately, due to the way Python handles list() calls internally.
- # Python 2.6.1 is the "in the wild" version affected by this, so we skip
- # the test for that version.
@requires_threading
@skipUnlessDBFeature('has_select_for_update_nowait')
- @unittest.skipIf(sys.version_info[:3] == (2, 6, 1), "Python version is 2.6.1")
def test_nowait_raises_error_on_block(self):
"""
If nowait is specified, we expect an error to be raised rather
@@ -128,15 +121,8 @@ class SelectForUpdateTests(TransactionTestCase):
self.end_blocking_transaction()
self.assertIsInstance(status[-1], DatabaseError)
- # In Python 2.6 beta and some final releases, exceptions raised in __len__
- # are swallowed (Python issue 1242657), so these cases return an empty
- # list, rather than raising an exception. Not a lot we can do about that,
- # unfortunately, due to the way Python handles list() calls internally.
- # Python 2.6.1 is the "in the wild" version affected by this, so we skip
- # the test for that version.
@skipIfDBFeature('has_select_for_update_nowait')
@skipUnlessDBFeature('has_select_for_update')
- @unittest.skipIf(sys.version_info[:3] == (2, 6, 1), "Python version is 2.6.1")
def test_unsupported_nowait_raises_error(self):
"""
If a SELECT...FOR UPDATE NOWAIT is run on a database backend
diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py
index bfd8d607a9..87ed4d4f5e 100644
--- a/tests/settings_tests/tests.py
+++ b/tests/settings_tests/tests.py
@@ -203,6 +203,29 @@ class SettingsTests(TestCase):
'ALLOWED_INCLUDE_ROOTS', '/var/www/ssi/')
+class TestComplexSettingOverride(TestCase):
+ def setUp(self):
+ self.old_warn_override_settings = signals.COMPLEX_OVERRIDE_SETTINGS.copy()
+ signals.COMPLEX_OVERRIDE_SETTINGS.add('TEST_WARN')
+
+ def tearDown(self):
+ signals.COMPLEX_OVERRIDE_SETTINGS = self.old_warn_override_settings
+ self.assertFalse('TEST_WARN' in signals.COMPLEX_OVERRIDE_SETTINGS)
+
+ def test_complex_override_warning(self):
+ """Regression test for #19031"""
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+
+ override = override_settings(TEST_WARN='override')
+ override.enable()
+ self.assertEqual('override', settings.TEST_WARN)
+ override.disable()
+
+ self.assertEqual(len(w), 1)
+ self.assertEqual('Overriding setting TEST_WARN can lead to unexpected behaviour.', str(w[-1].message))
+
+
class TrailingSlashURLTests(TestCase):
"""
Tests for the MEDIA_URL and STATIC_URL settings.
diff --git a/tests/syndication/feeds.py b/tests/syndication/feeds.py
index 0956820bf0..1cd5c3d988 100644
--- a/tests/syndication/feeds.py
+++ b/tests/syndication/feeds.py
@@ -33,7 +33,10 @@ class TestRss2Feed(views.Feed):
return "Overridden description: %s" % item
def item_pubdate(self, item):
- return item.date
+ return item.published
+
+ def item_updateddate(self, item):
+ return item.updated
item_author_name = 'Sally Smith'
item_author_email = 'test@example.com'
@@ -72,6 +75,17 @@ class TestAtomFeed(TestRss2Feed):
subtitle = TestRss2Feed.description
+class TestLatestFeed(TestRss2Feed):
+ """
+ A feed where the latest entry date is an `updated` element.
+ """
+ feed_type = feedgenerator.Atom1Feed
+ subtitle = TestRss2Feed.description
+
+ def items(self):
+ return Entry.objects.exclude(pk=5)
+
+
class ArticlesFeed(TestRss2Feed):
"""
A feed to test no link being defined. Articles have no get_absolute_url()
@@ -115,7 +129,7 @@ class NaiveDatesFeed(TestAtomFeed):
A feed with naive (non-timezone-aware) dates.
"""
def item_pubdate(self, item):
- return item.date
+ return item.published
class TZAwareDatesFeed(TestAtomFeed):
@@ -126,7 +140,7 @@ class TZAwareDatesFeed(TestAtomFeed):
# Provide a weird offset so that the test can know it's getting this
# specific offset and not accidentally getting on from
# settings.TIME_ZONE.
- return item.date.replace(tzinfo=tzinfo.FixedOffset(42))
+ return item.published.replace(tzinfo=tzinfo.FixedOffset(42))
class TestFeedUrlFeed(TestAtomFeed):
diff --git a/tests/syndication/fixtures/feeddata.json b/tests/syndication/fixtures/feeddata.json
index 167115c925..52e5028fcd 100644
--- a/tests/syndication/fixtures/feeddata.json
+++ b/tests/syndication/fixtures/feeddata.json
@@ -4,7 +4,8 @@
"pk": 1,
"fields": {
"title": "My first entry",
- "date": "1850-01-01 12:30:00"
+ "updated": "1850-01-01 12:30:00",
+ "published": "1066-09-25 20:15:00"
}
},
{
@@ -12,7 +13,8 @@
"pk": 2,
"fields": {
"title": "My second entry",
- "date": "2008-01-02 12:30:00"
+ "updated": "2008-01-02 12:30:00",
+ "published": "2006-03-17 18:00:00"
}
},
{
@@ -20,7 +22,8 @@
"pk": 3,
"fields": {
"title": "My third entry",
- "date": "2008-01-02 13:30:00"
+ "updated": "2008-01-02 13:30:00",
+ "published": "2005-06-14 10:45:00"
}
},
{
@@ -28,7 +31,17 @@
"pk": 4,
"fields": {
"title": "A & B < C > D",
- "date": "2008-01-03 13:30:00"
+ "updated": "2008-01-03 13:30:00",
+ "published": "2005-11-25 12:11:23"
+ }
+ },
+ {
+ "model": "syndication.entry",
+ "pk": 5,
+ "fields": {
+ "title": "My last entry",
+ "updated": "2013-01-20 00:00:00",
+ "published": "2013-03-25 20:00:00"
}
},
{
diff --git a/tests/syndication/models.py b/tests/syndication/models.py
index 10b3fe3a0c..166acd69ca 100644
--- a/tests/syndication/models.py
+++ b/tests/syndication/models.py
@@ -5,10 +5,11 @@ from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Entry(models.Model):
title = models.CharField(max_length=200)
- date = models.DateTimeField()
+ updated = models.DateTimeField()
+ published = models.DateTimeField()
class Meta:
- ordering = ('date',)
+ ordering = ('updated',)
def __str__(self):
return self.title
@@ -24,4 +25,3 @@ class Article(models.Model):
def __str__(self):
return self.title
-
diff --git a/tests/syndication/tests.py b/tests/syndication/tests.py
index 1627f7182b..d3b0058d53 100644
--- a/tests/syndication/tests.py
+++ b/tests/syndication/tests.py
@@ -58,7 +58,7 @@ class SyndicationFeedTest(FeedTestCase):
chan = chan_elem[0]
# Find the last build date
- d = Entry.objects.latest('date').date
+ d = Entry.objects.latest('published').published
ltz = tzinfo.LocalTimezone(d)
last_build_date = rfc2822_date(d.replace(tzinfo=ltz))
@@ -88,7 +88,7 @@ class SyndicationFeedTest(FeedTestCase):
)
# Find the pubdate of the first feed item
- d = Entry.objects.get(pk=1).date
+ d = Entry.objects.get(pk=1).published
ltz = tzinfo.LocalTimezone(d)
pub_date = rfc2822_date(d.replace(tzinfo=ltz))
@@ -203,10 +203,61 @@ class SyndicationFeedTest(FeedTestCase):
entries = feed.getElementsByTagName('entry')
self.assertEqual(len(entries), Entry.objects.count())
for entry in entries:
- self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'category', 'updated', 'rights', 'author'])
+ self.assertChildNodes(entry, [
+ 'title',
+ 'link',
+ 'id',
+ 'summary',
+ 'category',
+ 'updated',
+ 'published',
+ 'rights',
+ 'author',
+ ])
summary = entry.getElementsByTagName('summary')[0]
self.assertEqual(summary.getAttribute('type'), 'html')
+ def test_atom_feed_published_and_updated_elements(self):
+ """
+ Test that the published and updated elements are not
+ the same and now adhere to RFC 4287.
+ """
+ response = self.client.get('/syndication/atom/')
+ feed = minidom.parseString(response.content).firstChild
+ entries = feed.getElementsByTagName('entry')
+
+ published = entries[0].getElementsByTagName('published')[0].firstChild.wholeText
+ updated = entries[0].getElementsByTagName('updated')[0].firstChild.wholeText
+
+ self.assertNotEqual(published, updated)
+
+ def test_latest_post_date(self):
+ """
+ Test that both the published and updated dates are
+ considered when determining the latest post date.
+ """
+ # this feed has a `published` element with the latest date
+ response = self.client.get('/syndication/atom/')
+ feed = minidom.parseString(response.content).firstChild
+ updated = feed.getElementsByTagName('updated')[0].firstChild.wholeText
+
+ d = Entry.objects.latest('published').published
+ ltz = tzinfo.LocalTimezone(d)
+ latest_published = rfc3339_date(d.replace(tzinfo=ltz))
+
+ self.assertEqual(updated, latest_published)
+
+ # this feed has an `updated` element with the latest date
+ response = self.client.get('/syndication/latest/')
+ feed = minidom.parseString(response.content).firstChild
+ updated = feed.getElementsByTagName('updated')[0].firstChild.wholeText
+
+ d = Entry.objects.exclude(pk=5).latest('updated').updated
+ ltz = tzinfo.LocalTimezone(d)
+ latest_updated = rfc3339_date(d.replace(tzinfo=ltz))
+
+ self.assertEqual(updated, latest_updated)
+
def test_custom_feed_generator(self):
response = self.client.get('/syndication/custom/')
feed = minidom.parseString(response.content).firstChild
@@ -219,7 +270,18 @@ class SyndicationFeedTest(FeedTestCase):
self.assertEqual(len(entries), Entry.objects.count())
for entry in entries:
self.assertEqual(entry.getAttribute('bacon'), 'yum')
- self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'ministry', 'rights', 'author', 'updated', 'category'])
+ self.assertChildNodes(entry, [
+ 'title',
+ 'link',
+ 'id',
+ 'summary',
+ 'ministry',
+ 'rights',
+ 'author',
+ 'updated',
+ 'published',
+ 'category',
+ ])
summary = entry.getElementsByTagName('summary')[0]
self.assertEqual(summary.getAttribute('type'), 'html')
@@ -245,7 +307,7 @@ class SyndicationFeedTest(FeedTestCase):
doc = minidom.parseString(response.content)
updated = doc.getElementsByTagName('updated')[0].firstChild.wholeText
- d = Entry.objects.latest('date').date
+ d = Entry.objects.latest('published').published
ltz = tzinfo.LocalTimezone(d)
latest = rfc3339_date(d.replace(tzinfo=ltz))
@@ -257,12 +319,12 @@ class SyndicationFeedTest(FeedTestCase):
"""
response = self.client.get('/syndication/aware-dates/')
doc = minidom.parseString(response.content)
- updated = doc.getElementsByTagName('updated')[0].firstChild.wholeText
- self.assertEqual(updated[-6:], '+00:42')
+ published = doc.getElementsByTagName('published')[0].firstChild.wholeText
+ self.assertEqual(published[-6:], '+00:42')
def test_feed_last_modified_time(self):
response = self.client.get('/syndication/naive-dates/')
- self.assertEqual(response['Last-Modified'], 'Thu, 03 Jan 2008 19:30:00 GMT')
+ self.assertEqual(response['Last-Modified'], 'Tue, 26 Mar 2013 01:00:00 GMT')
# No last-modified when feed has no item_pubdate
response = self.client.get('/syndication/no_pubdate/')
diff --git a/tests/syndication/urls.py b/tests/syndication/urls.py
index 1dd7e92332..06a75a4e68 100644
--- a/tests/syndication/urls.py
+++ b/tests/syndication/urls.py
@@ -15,6 +15,7 @@ urlpatterns = patterns('django.contrib.syndication.views',
(r'^syndication/rss091/$', feeds.TestRss091Feed()),
(r'^syndication/no_pubdate/$', feeds.TestNoPubdateFeed()),
(r'^syndication/atom/$', feeds.TestAtomFeed()),
+ (r'^syndication/latest/$', feeds.TestLatestFeed()),
(r'^syndication/custom/$', feeds.TestCustomFeed()),
(r'^syndication/naive-dates/$', feeds.NaiveDatesFeed()),
(r'^syndication/aware-dates/$', feeds.TZAwareDatesFeed()),
diff --git a/tests/template_tests/test_context.py b/tests/template_tests/test_context.py
index 224b94d060..ca167a73f3 100644
--- a/tests/template_tests/test_context.py
+++ b/tests/template_tests/test_context.py
@@ -16,3 +16,12 @@ class ContextTests(TestCase):
self.assertEqual(c.pop(), {"a": 2})
self.assertEqual(c["a"], 1)
self.assertEqual(c.get("foo", 42), 42)
+
+ with c.push():
+ c['a'] = 2
+ self.assertEqual(c['a'], 2)
+ self.assertEqual(c['a'], 1)
+
+ with c.push(a=3):
+ self.assertEqual(c['a'], 3)
+ self.assertEqual(c['a'], 1)
diff --git a/tests/test_runner/tests.py b/tests/test_runner/tests.py
index 4e9e44bc12..72200e40d3 100644
--- a/tests/test_runner/tests.py
+++ b/tests/test_runner/tests.py
@@ -291,6 +291,65 @@ class DummyBackendTest(unittest.TestCase):
db.connections = old_db_connections
+class AliasedDefaultTestSetupTest(unittest.TestCase):
+ def test_setup_aliased_default_database(self):
+ """
+ Test that setup_datebases() doesn't fail when 'default' is aliased
+ """
+ runner_instance = runner.DiscoverRunner(verbosity=0)
+ old_db_connections = db.connections
+ try:
+ db.connections = db.ConnectionHandler({
+ 'default': {
+ 'NAME': 'dummy'
+ },
+ 'aliased': {
+ 'NAME': 'dummy'
+ }
+ })
+ old_config = runner_instance.setup_databases()
+ runner_instance.teardown_databases(old_config)
+ except Exception as e:
+ self.fail("setup_databases/teardown_databases unexpectedly raised "
+ "an error: %s" % e)
+ finally:
+ db.connections = old_db_connections
+
+
+class AliasedDatabaseTeardownTest(unittest.TestCase):
+ def test_setup_aliased_databases(self):
+ from django.db.backends.dummy.base import DatabaseCreation
+
+ runner_instance = runner.DiscoverRunner(verbosity=0)
+ old_db_connections = db.connections
+ old_destroy_test_db = DatabaseCreation.destroy_test_db
+ old_create_test_db = DatabaseCreation.create_test_db
+ try:
+ destroyed_names = []
+ DatabaseCreation.destroy_test_db = lambda self, old_database_name, verbosity=1: destroyed_names.append(old_database_name)
+ DatabaseCreation.create_test_db = lambda self, verbosity=1, autoclobber=False: self._get_test_db_name()
+
+ db.connections = db.ConnectionHandler({
+ 'default': {
+ 'ENGINE': 'django.db.backends.dummy',
+ 'NAME': 'dbname',
+ },
+ 'other': {
+ 'ENGINE': 'django.db.backends.dummy',
+ 'NAME': 'dbname',
+ }
+ })
+
+ old_config = runner_instance.setup_databases()
+ runner_instance.teardown_databases(old_config)
+
+ self.assertEqual(destroyed_names.count('dbname'), 1)
+ finally:
+ DatabaseCreation.create_test_db = old_create_test_db
+ DatabaseCreation.destroy_test_db = old_destroy_test_db
+ db.connections = old_db_connections
+
+
class DeprecationDisplayTest(AdminScriptTestCase):
# tests for 19546
def setUp(self):
@@ -308,8 +367,6 @@ class DeprecationDisplayTest(AdminScriptTestCase):
self.assertIn("DeprecationWarning: warning from test", err)
self.assertIn("DeprecationWarning: module-level warning from deprecation_app", err)
- @unittest.skipIf(sys.version_info[:2] == (2, 6),
- "On Python 2.6, DeprecationWarnings are visible anyway")
def test_runner_deprecation_verbosity_zero(self):
args = ['test', '--settings=settings', '--verbosity=0']
out, err = self.run_django_admin(args)
diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py
index 2c2bc24d72..24aa433dd3 100644
--- a/tests/test_utils/tests.py
+++ b/tests/test_utils/tests.py
@@ -2,13 +2,12 @@
from __future__ import absolute_import, unicode_literals
import unittest
-from unittest import skip
from django.db import connection
from django.forms import EmailField, IntegerField
from django.http import HttpResponse
from django.template.loader import render_to_string
-from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
+from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
from django.test.html import HTMLParseError, parse_html
from django.test.utils import CaptureQueriesContext, IgnoreAllDeprecationWarningsMixin
from django.utils import six
@@ -27,6 +26,29 @@ class SkippingTestCase(TestCase):
self.assertRaises(ValueError, test_func)
+class SkippingClassTestCase(TestCase):
+ def test_skip_class_unless_db_feature(self):
+ @skipUnlessDBFeature("__class__")
+ class NotSkippedTests(unittest.TestCase):
+ def test_dummy(self):
+ return
+
+ @skipIfDBFeature("__class__")
+ class SkippedTests(unittest.TestCase):
+ def test_will_be_skipped(self):
+ self.fail("We should never arrive here.")
+
+ test_suite = unittest.TestSuite()
+ test_suite.addTest(NotSkippedTests('test_dummy'))
+ try:
+ test_suite.addTest(SkippedTests('test_will_be_skipped'))
+ except unittest.SkipTest:
+ self.fail("SkipTest should not be raised at this stage")
+ result = unittest.TextTestRunner(stream=six.StringIO()).run(test_suite)
+ self.assertEqual(result.testsRun, 2)
+ self.assertEqual(len(result.skipped), 1)
+
+
class AssertNumQueriesTests(TestCase):
urls = 'test_utils.urls'
@@ -561,7 +583,7 @@ class SkippingExtraTests(TestCase):
with self.assertNumQueries(0):
super(SkippingExtraTests, self).__call__(result)
- @skip("Fixture loading should not be performed for skipped tests.")
+ @unittest.skip("Fixture loading should not be performed for skipped tests.")
def test_fixtures_are_skipped(self):
pass
diff --git a/tests/transactions_regress/tests.py b/tests/transactions_regress/tests.py
index bd9e78b033..01f8fc4186 100644
--- a/tests/transactions_regress/tests.py
+++ b/tests/transactions_regress/tests.py
@@ -191,10 +191,9 @@ class TestTransactionClosing(IgnoreDeprecationWarningsMixin, TransactionTestCase
"""
self.test_failing_query_transaction_closed()
-@skipIf(connection.vendor == 'sqlite' and
- (connection.settings_dict['NAME'] == ':memory:' or
- not connection.settings_dict['NAME']),
- 'Test uses multiple connections, but in-memory sqlite does not support this')
+@skipIf(connection.vendor == 'sqlite'
+ and connection.settings_dict['TEST_NAME'] in (None, '', ':memory:'),
+ "Cannot establish two connections to an in-memory SQLite database.")
class TestNewConnection(IgnoreDeprecationWarningsMixin, TransactionTestCase):
"""
Check that new connections don't have special behaviour.
diff --git a/tests/validators/tests.py b/tests/validators/tests.py
index ec3e2dc8c1..9be49d7726 100644
--- a/tests/validators/tests.py
+++ b/tests/validators/tests.py
@@ -46,6 +46,7 @@ TEST_DATA = (
(validate_email, 'example@-invalid.com', ValidationError),
(validate_email, 'example@inv-.alid-.com', ValidationError),
(validate_email, 'example@inv-.-alid.com', ValidationError),
+ (validate_email, 'test@example.com\n\n<script src="x.js">', ValidationError),
# Quoted-string format (CR not allowed)
(validate_email, '"\\\011"@here.com', None),
(validate_email, '"\\\012"@here.com', ValidationError),