summaryrefslogtreecommitdiff
path: root/django
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 /django
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
Diffstat (limited to 'django')
-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
114 files changed, 979 insertions, 642 deletions
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')),