diff --git a/django/conf/__init__.py b/django/conf/__init__.py index 77454b3fb9..e1c3fd1808 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -158,7 +158,7 @@ class UserSettingsHolder(BaseSettings): return getattr(self.default_settings, name) def __dir__(self): - return self.__dict__.keys() + dir(self.default_settings) + return list(self.__dict__) + dir(self.default_settings) # For Python < 2.6: __members__ = property(lambda self: self.__dir__()) diff --git a/django/conf/locale/fi/formats.py b/django/conf/locale/fi/formats.py index 9a658eed40..e76144a9e4 100644 --- a/django/conf/locale/fi/formats.py +++ b/django/conf/locale/fi/formats.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'j. E Y' TIME_FORMAT = 'G.i.s' -# DATETIME_FORMAT = +DATETIME_FORMAT = r'j. E Y \k\e\l\l\o G.i.s' YEAR_MONTH_FORMAT = 'F Y' MONTH_DAY_FORMAT = 'j. F' SHORT_DATE_FORMAT = 'j.n.Y' diff --git a/django/conf/locale/gl/formats.py b/django/conf/locale/gl/formats.py index 1ff9f34521..ba7f6c52a0 100644 --- a/django/conf/locale/gl/formats.py +++ b/django/conf/locale/gl/formats.py @@ -1,17 +1,18 @@ # -*- encoding: utf-8 -*- # This file is distributed under the same license as the Django package. # +from __future__ import unicode_literals # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date -DATE_FORMAT = 'd F Y' +DATE_FORMAT = r'j \d\e F \d\e Y' TIME_FORMAT = 'H:i:s' -# DATETIME_FORMAT = -YEAR_MONTH_FORMAT = 'F Y' -MONTH_DAY_FORMAT = 'j F' -SHORT_DATE_FORMAT = 'j M, Y' -# SHORT_DATETIME_FORMAT = -# FIRST_DAY_OF_WEEK = +DATETIME_FORMAT = r'j \d\e F \d\e Y \á\s H:i' +YEAR_MONTH_FORMAT = r'F \d\e Y' +MONTH_DAY_FORMAT = r'j \d\e F' +SHORT_DATE_FORMAT = 'd-m-Y' +SHORT_DATETIME_FORMAT = 'd-m-Y, H:i' +FIRST_DAY_OF_WEEK = 1 # Monday # The *_INPUT_FORMATS strings use the Python strftime format syntax, # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior diff --git a/django/conf/urls/i18n.py b/django/conf/urls/i18n.py index 6e56af8271..426c2b2d30 100644 --- a/django/conf/urls/i18n.py +++ b/django/conf/urls/i18n.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.conf.urls import patterns +from django.conf.urls import patterns, url from django.core.urlresolvers import LocaleRegexURLResolver def i18n_patterns(prefix, *args): @@ -16,5 +16,5 @@ def i18n_patterns(prefix, *args): urlpatterns = patterns('', - (r'^setlang/$', 'django.views.i18n.set_language'), + url(r'^setlang/$', 'django.views.i18n.set_language', name='set_language'), ) diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py index 5b56402428..201101736e 100644 --- a/django/contrib/admin/actions.py +++ b/django/contrib/admin/actions.py @@ -7,7 +7,7 @@ from django.contrib.admin import helpers from django.contrib.admin.util import get_deleted_objects, model_ngettext from django.db import router from django.template.response import TemplateResponse -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy, ugettext as _ def delete_selected(modeladmin, request, queryset): @@ -42,7 +42,7 @@ def delete_selected(modeladmin, request, queryset): n = queryset.count() if n: for obj in queryset: - obj_display = force_unicode(obj) + obj_display = force_text(obj) modeladmin.log_deletion(request, obj, obj_display) queryset.delete() modeladmin.message_user(request, _("Successfully deleted %(count)d %(items)s.") % { @@ -52,9 +52,9 @@ def delete_selected(modeladmin, request, queryset): return None if len(queryset) == 1: - objects_name = force_unicode(opts.verbose_name) + objects_name = force_text(opts.verbose_name) else: - objects_name = force_unicode(opts.verbose_name_plural) + objects_name = force_text(opts.verbose_name_plural) if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": objects_name} diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index 538bf54df9..cecae216c0 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -9,7 +9,7 @@ import datetime from django.db import models from django.core.exceptions import ImproperlyConfigured -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ from django.utils import timezone @@ -195,7 +195,7 @@ class RelatedFieldListFilter(FieldListFilter): } for pk_val, val in self.lookup_choices: yield { - 'selected': self.lookup_val == smart_unicode(pk_val), + 'selected': self.lookup_val == smart_text(pk_val), 'query_string': cl.get_query_string({ self.lookup_kwarg: pk_val, }, [self.lookup_kwarg_isnull]), @@ -272,7 +272,7 @@ class ChoicesFieldListFilter(FieldListFilter): } for lookup, title in self.field.flatchoices: yield { - 'selected': smart_unicode(lookup) == self.lookup_val, + 'selected': smart_text(lookup) == self.lookup_val, 'query_string': cl.get_query_string({ self.lookup_kwarg: lookup}), 'display': title, @@ -381,7 +381,7 @@ class AllValuesFieldListFilter(FieldListFilter): if val is None: include_none = True continue - val = smart_unicode(val) + val = smart_text(val) yield { 'selected': self.lookup_val == val, 'query_string': cl.get_query_string({ diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 1bc843cdf9..90370bd978 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -9,7 +9,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.db.models.fields.related import ManyToManyRel from django.forms.util import flatatt from django.template.defaultfilters import capfirst -from django.utils.encoding import force_unicode, smart_unicode +from django.utils.encoding import force_text, smart_text from django.utils.html import conditional_escape, format_html from django.utils.safestring import mark_safe from django.utils import six @@ -94,7 +94,7 @@ class Fieldset(object): class Fieldline(object): def __init__(self, form, field, readonly_fields=None, model_admin=None): self.form = form # A django.forms.Form instance - if not hasattr(field, "__iter__"): + if not hasattr(field, "__iter__") or isinstance(field, six.text_type): self.fields = [field] else: self.fields = field @@ -122,7 +122,7 @@ class AdminField(object): def label_tag(self): classes = [] - contents = conditional_escape(force_unicode(self.field.label)) + contents = conditional_escape(force_text(self.field.label)) if self.is_checkbox: classes.append('vCheckboxLabel') else: @@ -166,7 +166,7 @@ class AdminReadonlyField(object): label = self.field['label'] return format_html('{1}:', flatatt(attrs), - capfirst(force_unicode(label))) + capfirst(force_text(label))) def contents(self): from django.contrib.admin.templatetags.admin_list import _boolean_icon @@ -182,7 +182,7 @@ class AdminReadonlyField(object): if boolean: result_repr = _boolean_icon(value) else: - result_repr = smart_unicode(value) + result_repr = smart_text(value) if getattr(attr, "allow_tags", False): result_repr = mark_safe(result_repr) else: @@ -325,11 +325,11 @@ class AdminErrorList(forms.util.ErrorList): """ def __init__(self, form, inline_formsets): if form.is_bound: - self.extend(form.errors.values()) + self.extend(list(six.itervalues(form.errors))) for inline_formset in inline_formsets: self.extend(inline_formset.non_form_errors()) for errors_in_inline_form in inline_formset.errors: - self.extend(errors_in_inline_form.values()) + self.extend(list(six.itervalues(errors_in_inline_form))) def normalize_fieldsets(fieldsets): """ diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py index 58bbbabfdf..e31c6d84ed 100644 --- a/django/contrib/admin/models.py +++ b/django/contrib/admin/models.py @@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import User from django.contrib.admin.util import quote from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text ADDITION = 1 CHANGE = 2 @@ -13,7 +13,7 @@ DELETION = 3 class LogEntryManager(models.Manager): def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''): - e = self.model(None, None, user_id, content_type_id, smart_unicode(object_id), object_repr[:200], action_flag, change_message) + e = self.model(None, None, user_id, content_type_id, smart_text(object_id), object_repr[:200], action_flag, change_message) e.save() class LogEntry(models.Model): @@ -34,7 +34,7 @@ class LogEntry(models.Model): ordering = ('-action_time',) def __repr__(self): - return smart_unicode(self.action_time) + return smart_text(self.action_time) def __unicode__(self): if self.action_flag == ADDITION: diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index c13a6bc5cc..081d00121b 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -28,7 +28,7 @@ from django.utils import six from django.utils.text import capfirst, get_text_list from django.utils.translation import ugettext as _ from django.utils.translation import ungettext -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text HORIZONTAL, VERTICAL = 1, 2 # returns the ', flatatt(self.attrs), format_html_join('\n', '
  • {0}
  • ', - ((force_unicode(w),) for w in self))) + ((force_text(w),) for w in self))) class AdminRadioSelect(forms.RadioSelect): renderer = AdminRadioFieldRenderer @@ -197,7 +197,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): # The related object is registered with the same AdminSite attrs['class'] = 'vManyToManyRawIdAdminField' if value: - value = ','.join([force_unicode(v) for v in value]) + value = ','.join([force_text(v) for v in value]) else: value = '' return super(ManyToManyRawIdWidget, self).render(name, value, attrs) @@ -221,7 +221,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): if len(initial) != len(data): return True for pk1, pk2 in zip(initial, data): - if force_unicode(pk1) != force_unicode(pk2): + if force_text(pk1) != force_text(pk2): return True return False diff --git a/django/contrib/admindocs/utils.py b/django/contrib/admindocs/utils.py index 4bf1250ac6..0e10eb4fa3 100644 --- a/django/contrib/admindocs/utils.py +++ b/django/contrib/admindocs/utils.py @@ -6,7 +6,7 @@ from email.errors import HeaderParseError from django.utils.safestring import mark_safe from django.core.urlresolvers import reverse -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes try: import docutils.core import docutils.nodes @@ -66,7 +66,7 @@ def parse_rst(text, default_reference_context, thing_being_parsed=None): "link_base" : reverse('django-admindocs-docroot').rstrip('/') } if thing_being_parsed: - thing_being_parsed = smart_str("<%s>" % thing_being_parsed) + thing_being_parsed = smart_bytes("<%s>" % thing_being_parsed) parts = docutils.core.publish_parts(text, source_path=thing_being_parsed, destination_path=None, writer_name='html', settings_overrides=overrides) diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index 5649398cc8..94963b4d39 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -14,6 +14,7 @@ from django.core import urlresolvers from django.contrib.admindocs import utils from django.contrib.sites.models import Site from django.utils.importlib import import_module +from django.utils import six from django.utils.translation import ugettext as _ from django.utils.safestring import mark_safe @@ -48,7 +49,7 @@ def template_tag_index(request): load_all_installed_template_libraries() tags = [] - app_libs = template.libraries.items() + app_libs = list(six.iteritems(template.libraries)) builtin_libs = [(None, lib) for lib in template.builtins] for module_name, library in builtin_libs + app_libs: for tag_name, tag_func in library.tags.items(): @@ -83,7 +84,7 @@ def template_filter_index(request): load_all_installed_template_libraries() filters = [] - app_libs = template.libraries.items() + app_libs = list(six.iteritems(template.libraries)) builtin_libs = [(None, lib) for lib in template.builtins] for module_name, library in builtin_libs + app_libs: for filter_name, filter_func in library.filters.items(): diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py index ad61904041..ccf940d16d 100644 --- a/django/contrib/auth/admin.py +++ b/django/contrib/auth/admin.py @@ -12,6 +12,7 @@ from django.template.response import TemplateResponse from django.utils.html import escape from django.utils.decorators import method_decorator from django.utils.safestring import mark_safe +from django.utils import six from django.utils.translation import ugettext, ugettext_lazy as _ from django.views.decorators.csrf import csrf_protect from django.views.decorators.debug import sensitive_post_parameters @@ -128,7 +129,7 @@ class UserAdmin(admin.ModelAdmin): else: form = self.change_password_form(user) - fieldsets = [(None, {'fields': form.base_fields.keys()})] + fieldsets = [(None, {'fields': list(form.base_fields)})] adminForm = admin.helpers.AdminForm(form, fieldsets, {}) context = { diff --git a/django/contrib/auth/context_processors.py b/django/contrib/auth/context_processors.py index 3ffab01e94..1b6c2eedd0 100644 --- a/django/contrib/auth/context_processors.py +++ b/django/contrib/auth/context_processors.py @@ -11,8 +11,9 @@ class PermLookupDict(object): def __getitem__(self, perm_name): return self.user.has_perm("%s.%s" % (self.module_name, perm_name)) - def __nonzero__(self): + def __bool__(self): return self.user.has_module_perms(self.module_name) + __nonzero__ = __bool__ # Python 2 class PermWrapper(object): diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index d17c41132e..dfd039f018 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -89,9 +89,9 @@ class UserCreationForm(forms.ModelForm): raise forms.ValidationError(self.error_messages['duplicate_username']) def clean_password2(self): - password1 = self.cleaned_data.get("password1", "") - password2 = self.cleaned_data["password2"] - if password1 != password2: + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: raise forms.ValidationError( self.error_messages['password_mismatch']) return password2 diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 96ec40ba60..45c1f88ab2 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import base64 import hashlib from django.dispatch import receiver @@ -7,7 +8,7 @@ from django.conf import settings from django.test.signals import setting_changed from django.utils import importlib from django.utils.datastructures import SortedDict -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes from django.core.exceptions import ImproperlyConfigured from django.utils.crypto import ( pbkdf2, constant_time_compare, get_random_string) @@ -218,7 +219,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher): if not iterations: iterations = self.iterations hash = pbkdf2(password, salt, iterations, digest=self.digest) - hash = hash.encode('base64').strip() + hash = base64.b64encode(hash).strip() return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash) def verify(self, password, encoded): @@ -298,7 +299,7 @@ class SHA1PasswordHasher(BasePasswordHasher): def encode(self, password, salt): assert password assert salt and '$' not in salt - hash = hashlib.sha1(smart_str(salt + password)).hexdigest() + hash = hashlib.sha1(smart_bytes(salt + password)).hexdigest() return "%s$%s$%s" % (self.algorithm, salt, hash) def verify(self, password, encoded): @@ -326,7 +327,7 @@ class MD5PasswordHasher(BasePasswordHasher): def encode(self, password, salt): assert password assert salt and '$' not in salt - hash = hashlib.md5(smart_str(salt + password)).hexdigest() + hash = hashlib.md5(smart_bytes(salt + password)).hexdigest() return "%s$%s$%s" % (self.algorithm, salt, hash) def verify(self, password, encoded): @@ -360,7 +361,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher): return '' def encode(self, password, salt): - return hashlib.md5(smart_str(password)).hexdigest() + return hashlib.md5(smart_bytes(password)).hexdigest() def verify(self, password, encoded): encoded_2 = self.encode(password, '') diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py index 100acb6c5b..7abd2abcf4 100644 --- a/django/contrib/auth/management/__init__.py +++ b/django/contrib/auth/management/__init__.py @@ -9,6 +9,7 @@ import unicodedata from django.contrib.auth import models as auth_app from django.db.models import get_models, signals from django.contrib.auth.models import User +from django.utils.six.moves import input def _get_permission_codename(action, opts): @@ -66,10 +67,10 @@ def create_superuser(app, created_models, verbosity, db, **kwargs): msg = ("\nYou just installed Django's auth system, which means you " "don't have any superusers defined.\nWould you like to create one " "now? (yes/no): ") - confirm = raw_input(msg) + confirm = input(msg) while 1: if confirm not in ('yes', 'no'): - confirm = raw_input('Please enter either "yes" or "no": ') + confirm = input('Please enter either "yes" or "no": ') continue if confirm == 'yes': call_command("createsuperuser", interactive=True, database=db) diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index f3f1a7b671..6e0d0bc754 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -12,6 +12,7 @@ from django.contrib.auth.management import get_default_username from django.core import exceptions from django.core.management.base import BaseCommand, CommandError from django.db import DEFAULT_DB_ALIAS +from django.utils.six.moves import input from django.utils.translation import ugettext as _ RE_VALID_USERNAME = re.compile('[\w.@+-]+$') @@ -76,7 +77,7 @@ class Command(BaseCommand): input_msg = 'Username' if default_username: input_msg += ' (leave blank to use %r)' % default_username - username = raw_input(input_msg + ': ') + username = input(input_msg + ': ') if default_username and username == '': username = default_username if not RE_VALID_USERNAME.match(username): @@ -94,7 +95,7 @@ class Command(BaseCommand): # Get an email while 1: if not email: - email = raw_input('E-mail address: ') + email = input('E-mail address: ') try: is_valid_email(email) except exceptions.ValidationError: diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py index 2ab895804f..594b55c633 100644 --- a/django/contrib/auth/tests/forms.py +++ b/django/contrib/auth/tests/forms.py @@ -8,7 +8,8 @@ from django.core import mail from django.forms.fields import Field, EmailField from django.test import TestCase from django.test.utils import override_settings -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text +from django.utils import six from django.utils import translation from django.utils.translation import ugettext as _ @@ -27,7 +28,7 @@ class UserCreationFormTest(TestCase): form = UserCreationForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form["username"].errors, - [force_unicode(form.error_messages['duplicate_username'])]) + [force_text(form.error_messages['duplicate_username'])]) def test_invalid_data(self): data = { @@ -38,7 +39,7 @@ class UserCreationFormTest(TestCase): form = UserCreationForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form["username"].errors, - [force_unicode(form.fields['username'].error_messages['invalid'])]) + [force_text(form.fields['username'].error_messages['invalid'])]) def test_password_verification(self): # The verification password is incorrect. @@ -50,13 +51,13 @@ class UserCreationFormTest(TestCase): form = UserCreationForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form["password2"].errors, - [force_unicode(form.error_messages['password_mismatch'])]) + [force_text(form.error_messages['password_mismatch'])]) def test_both_passwords(self): # One (or both) passwords weren't given data = {'username': 'jsmith'} form = UserCreationForm(data) - required_error = [force_unicode(Field.default_error_messages['required'])] + required_error = [force_text(Field.default_error_messages['required'])] self.assertFalse(form.is_valid()) self.assertEqual(form['password1'].errors, required_error) self.assertEqual(form['password2'].errors, required_error) @@ -65,6 +66,7 @@ class UserCreationFormTest(TestCase): form = UserCreationForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form['password1'].errors, required_error) + self.assertEqual(form['password2'].errors, []) def test_success(self): # The success case. @@ -94,7 +96,7 @@ class AuthenticationFormTest(TestCase): form = AuthenticationForm(None, data) self.assertFalse(form.is_valid()) self.assertEqual(form.non_field_errors(), - [force_unicode(form.error_messages['invalid_login'])]) + [force_text(form.error_messages['invalid_login'])]) def test_inactive_user(self): # The user is inactive. @@ -105,7 +107,7 @@ class AuthenticationFormTest(TestCase): form = AuthenticationForm(None, data) self.assertFalse(form.is_valid()) self.assertEqual(form.non_field_errors(), - [force_unicode(form.error_messages['inactive'])]) + [force_text(form.error_messages['inactive'])]) def test_inactive_user_i18n(self): with self.settings(USE_I18N=True): @@ -118,7 +120,7 @@ class AuthenticationFormTest(TestCase): form = AuthenticationForm(None, data) self.assertFalse(form.is_valid()) self.assertEqual(form.non_field_errors(), - [force_unicode(form.error_messages['inactive'])]) + [force_text(form.error_messages['inactive'])]) def test_success(self): # The success case @@ -146,7 +148,7 @@ class SetPasswordFormTest(TestCase): form = SetPasswordForm(user, data) self.assertFalse(form.is_valid()) self.assertEqual(form["new_password2"].errors, - [force_unicode(form.error_messages['password_mismatch'])]) + [force_text(form.error_messages['password_mismatch'])]) def test_success(self): user = User.objects.get(username='testclient') @@ -173,7 +175,7 @@ class PasswordChangeFormTest(TestCase): form = PasswordChangeForm(user, data) self.assertFalse(form.is_valid()) self.assertEqual(form["old_password"].errors, - [force_unicode(form.error_messages['password_incorrect'])]) + [force_text(form.error_messages['password_incorrect'])]) def test_password_verification(self): # The two new passwords do not match. @@ -186,7 +188,7 @@ class PasswordChangeFormTest(TestCase): form = PasswordChangeForm(user, data) self.assertFalse(form.is_valid()) self.assertEqual(form["new_password2"].errors, - [force_unicode(form.error_messages['password_mismatch'])]) + [force_text(form.error_messages['password_mismatch'])]) def test_success(self): # The success case. @@ -202,7 +204,7 @@ class PasswordChangeFormTest(TestCase): def test_field_order(self): # Regression test - check the order of fields: user = User.objects.get(username='testclient') - self.assertEqual(PasswordChangeForm(user, {}).fields.keys(), + self.assertEqual(list(PasswordChangeForm(user, {}).fields), ['old_password', 'new_password1', 'new_password2']) @@ -217,7 +219,7 @@ class UserChangeFormTest(TestCase): form = UserChangeForm(data, instance=user) self.assertFalse(form.is_valid()) self.assertEqual(form['username'].errors, - [force_unicode(form.fields['username'].error_messages['invalid'])]) + [force_text(form.fields['username'].error_messages['invalid'])]) def test_bug_14242(self): # A regression test, introduce by adding an optimization for the @@ -272,7 +274,7 @@ class PasswordResetFormTest(TestCase): form = PasswordResetForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form['email'].errors, - [force_unicode(EmailField.default_error_messages['invalid'])]) + [force_text(EmailField.default_error_messages['invalid'])]) def test_nonexistant_email(self): # Test nonexistant email address @@ -280,7 +282,7 @@ class PasswordResetFormTest(TestCase): form = PasswordResetForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, - {'email': [force_unicode(form.error_messages['unknown'])]}) + {'email': [force_text(form.error_messages['unknown'])]}) def test_cleaned_data(self): # Regression test diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py index e76e7dd10f..3c847f456a 100644 --- a/django/contrib/auth/tests/views.py +++ b/django/contrib/auth/tests/views.py @@ -7,7 +7,7 @@ from django.contrib.auth.models import User from django.core import mail from django.core.urlresolvers import reverse, NoReverseMatch from django.http import QueryDict -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.html import escape from django.utils.http import urlquote from django.test import TestCase @@ -46,7 +46,7 @@ class AuthViewsTestCase(TestCase): self.assertTrue(SESSION_KEY in self.client.session) def assertContainsEscaped(self, response, text, **kwargs): - return self.assertContains(response, escape(force_unicode(text)), **kwargs) + return self.assertContains(response, escape(force_text(text)), **kwargs) class AuthViewNamedURLTests(AuthViewsTestCase): diff --git a/django/contrib/comments/forms.py b/django/contrib/comments/forms.py index 830e24bca9..bd254d2733 100644 --- a/django/contrib/comments/forms.py +++ b/django/contrib/comments/forms.py @@ -5,7 +5,7 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.contrib.comments.models import Comment from django.utils.crypto import salted_hmac, constant_time_compare -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.text import get_text_list from django.utils import timezone from django.utils.translation import ungettext, ugettext, ugettext_lazy as _ @@ -133,7 +133,7 @@ class CommentDetailsForm(CommentSecurityForm): """ return dict( content_type = ContentType.objects.get_for_model(self.target_object), - object_pk = force_unicode(self.target_object._get_pk_val()), + object_pk = force_text(self.target_object._get_pk_val()), user_name = self.cleaned_data["name"], user_email = self.cleaned_data["email"], user_url = self.cleaned_data["url"], diff --git a/django/contrib/comments/managers.py b/django/contrib/comments/managers.py index 499feee6c3..bc0fc5f332 100644 --- a/django/contrib/comments/managers.py +++ b/django/contrib/comments/managers.py @@ -1,6 +1,6 @@ from django.db import models from django.contrib.contenttypes.models import ContentType -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text class CommentManager(models.Manager): @@ -18,5 +18,5 @@ class CommentManager(models.Manager): ct = ContentType.objects.get_for_model(model) qs = self.get_query_set().filter(content_type=ct) if isinstance(model, models.Model): - qs = qs.filter(object_pk=force_unicode(model._get_pk_val())) + qs = qs.filter(object_pk=force_text(model._get_pk_val())) return qs diff --git a/django/contrib/comments/templatetags/comments.py b/django/contrib/comments/templatetags/comments.py index ce1825e529..4d4eb2322f 100644 --- a/django/contrib/comments/templatetags/comments.py +++ b/django/contrib/comments/templatetags/comments.py @@ -3,7 +3,7 @@ from django.template.loader import render_to_string from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.contrib import comments -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text register = template.Library() @@ -75,7 +75,7 @@ class BaseCommentNode(template.Node): qs = self.comment_model.objects.filter( content_type = ctype, - object_pk = smart_unicode(object_pk), + object_pk = smart_text(object_pk), site__pk = settings.SITE_ID, ) diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index d5062cabf3..29e93eefe7 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -17,7 +17,7 @@ from django.forms import ModelForm from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets from django.contrib.contenttypes.models import ContentType -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text class GenericForeignKey(object): """ @@ -169,7 +169,7 @@ class GenericRelation(RelatedField, Field): def value_to_string(self, obj): qs = getattr(obj, self.name).all() - return smart_unicode([instance._get_pk_val() for instance in qs]) + return smart_text([instance._get_pk_val() for instance in qs]) def m2m_db_table(self): return self.rel.to._meta.db_table diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py index 6a23ef5287..9f287d494b 100644 --- a/django/contrib/contenttypes/management.py +++ b/django/contrib/contenttypes/management.py @@ -1,6 +1,8 @@ from django.contrib.contenttypes.models import ContentType from django.db.models import get_apps, get_models, signals -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text +from django.utils import six +from django.utils.six.moves import input def update_contenttypes(app, created_models, verbosity=2, **kwargs): """ @@ -24,17 +26,17 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs): ) to_remove = [ ct - for (model_name, ct) in content_types.iteritems() + for (model_name, ct) in six.iteritems(content_types) if model_name not in app_models ] cts = ContentType.objects.bulk_create([ ContentType( - name=smart_unicode(model._meta.verbose_name_raw), + name=smart_text(model._meta.verbose_name_raw), app_label=app_label, model=model_name, ) - for (model_name, model) in app_models.iteritems() + for (model_name, model) in six.iteritems(app_models) if model_name not in content_types ]) if verbosity >= 2: @@ -48,7 +50,7 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs): ' %s | %s' % (ct.app_label, ct.model) for ct in to_remove ]) - ok_to_delete = raw_input("""The following content types are stale and need to be deleted: + ok_to_delete = input("""The following content types are stale and need to be deleted: %s diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py index 867351f6c8..e6d547a491 100644 --- a/django/contrib/contenttypes/models.py +++ b/django/contrib/contenttypes/models.py @@ -1,6 +1,6 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode, force_unicode +from django.utils.encoding import smart_text, force_text class ContentTypeManager(models.Manager): @@ -37,13 +37,13 @@ class ContentTypeManager(models.Manager): try: ct = self._get_from_cache(opts) except KeyError: - # Load or create the ContentType entry. The smart_unicode() is + # Load or create the ContentType entry. The smart_text() is # needed around opts.verbose_name_raw because name_raw might be a # django.utils.functional.__proxy__ object. ct, created = self.get_or_create( app_label = opts.app_label, model = opts.object_name.lower(), - defaults = {'name': smart_unicode(opts.verbose_name_raw)}, + defaults = {'name': smart_text(opts.verbose_name_raw)}, ) self._add_to_cache(self.db, ct) @@ -86,7 +86,7 @@ class ContentTypeManager(models.Manager): ct = self.create( app_label=opts.app_label, model=opts.object_name.lower(), - name=smart_unicode(opts.verbose_name_raw), + name=smart_text(opts.verbose_name_raw), ) self._add_to_cache(self.db, ct) results[ct.model_class()] = ct @@ -147,7 +147,7 @@ class ContentType(models.Model): if not model or self.name != model._meta.verbose_name_raw: return self.name else: - return force_unicode(model._meta.verbose_name) + return force_text(model._meta.verbose_name) def model_class(self): "Returns the Python model class for this type of content." diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py index 687aa87f03..810e039894 100644 --- a/django/contrib/databrowse/datastructures.py +++ b/django/contrib/databrowse/datastructures.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals from django.db import models from django.utils import formats from django.utils.text import capfirst -from django.utils.encoding import smart_unicode, smart_str, iri_to_uri +from django.utils.encoding import smart_text, smart_bytes, iri_to_uri from django.db.models.query import QuerySet EMPTY_VALUE = '(None)' @@ -17,12 +17,12 @@ class EasyModel(object): def __init__(self, site, model): self.site = site self.model = model - self.model_list = site.registry.keys() + self.model_list = list(site.registry.keys()) self.verbose_name = model._meta.verbose_name self.verbose_name_plural = model._meta.verbose_name_plural def __repr__(self): - return '' % smart_str(self.model._meta.object_name) + return '' % smart_bytes(self.model._meta.object_name) def model_databrowse(self): "Returns the ModelDatabrowse class for this model." @@ -61,7 +61,7 @@ class EasyField(object): self.model, self.field = easy_model, field def __repr__(self): - return smart_str('' % (self.model.model._meta.object_name, self.field.name)) + return smart_bytes('' % (self.model.model._meta.object_name, self.field.name)) def choices(self): for value, label in self.field.choices: @@ -79,7 +79,7 @@ class EasyChoice(object): self.value, self.label = value, label def __repr__(self): - return smart_str('' % (self.model.model._meta.object_name, self.field.name)) + return smart_bytes('' % (self.model.model._meta.object_name, self.field.name)) def url(self): return '%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value)) @@ -89,10 +89,10 @@ class EasyInstance(object): self.model, self.instance = easy_model, instance def __repr__(self): - return smart_str('' % (self.model.model._meta.object_name, self.instance._get_pk_val())) + return smart_bytes('' % (self.model.model._meta.object_name, self.instance._get_pk_val())) def __unicode__(self): - val = smart_unicode(self.instance) + val = smart_text(self.instance) if len(val) > DISPLAY_SIZE: return val[:DISPLAY_SIZE] + '...' return val @@ -136,7 +136,7 @@ class EasyInstanceField(object): self.raw_value = getattr(instance.instance, field.name) def __repr__(self): - return smart_str('' % (self.model.model._meta.object_name, self.field.name)) + return smart_bytes('' % (self.model.model._meta.object_name, self.field.name)) def values(self): """ @@ -176,8 +176,6 @@ class EasyInstanceField(object): for plugin_name, plugin in self.model.model_databrowse().plugins.items(): urls = plugin.urls(plugin_name, self) if urls is not None: - #plugin_urls.append(urls) - values = self.values() return zip(self.values(), urls) if self.field.rel: m = EasyModel(self.model.site, self.field.rel.to) @@ -187,7 +185,7 @@ class EasyInstanceField(object): if value is None: continue url = '%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val())) - lst.append((smart_unicode(value), url)) + lst.append((smart_text(value), url)) else: lst = [(value, None) for value in self.values()] elif self.field.choices: @@ -196,10 +194,10 @@ class EasyInstanceField(object): url = '%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value)) lst.append((value, url)) elif isinstance(self.field, models.URLField): - val = self.values()[0] + val = list(self.values())[0] lst = [(val, iri_to_uri(val))] else: - lst = [(self.values()[0], None)] + lst = [(list(self.values())[0], None)] return lst class EasyQuerySet(QuerySet): diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py index 7bdd1e0032..a548c33c8f 100644 --- a/django/contrib/databrowse/plugins/calendars.py +++ b/django/contrib/databrowse/plugins/calendars.py @@ -7,7 +7,7 @@ from django.contrib.databrowse.sites import DatabrowsePlugin from django.shortcuts import render_to_response from django.utils.html import format_html, format_html_join from django.utils.text import capfirst -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.views.generic import dates from django.utils import datetime_safe @@ -66,7 +66,7 @@ class CalendarPlugin(DatabrowsePlugin): return '' return format_html('

    View calendar by: {0}

    ', format_html_join(', ', '{1}', - ((f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()))) + ((f.name, force_text(capfirst(f.verbose_name))) for f in fields.values()))) def urls(self, plugin_name, easy_instance_field): if isinstance(easy_instance_field.field, models.DateField): @@ -96,7 +96,7 @@ class CalendarPlugin(DatabrowsePlugin): def homepage_view(self, request): easy_model = EasyModel(self.site, self.model) - field_list = self.fields.values() + field_list = list(self.fields.values()) field_list.sort(key=lambda k:k.verbose_name) return render_to_response('databrowse/calendar_homepage.html', { 'root_url': self.site.root_url, diff --git a/django/contrib/databrowse/plugins/fieldchoices.py b/django/contrib/databrowse/plugins/fieldchoices.py index c016385ffb..dc5e9aef14 100644 --- a/django/contrib/databrowse/plugins/fieldchoices.py +++ b/django/contrib/databrowse/plugins/fieldchoices.py @@ -8,7 +8,7 @@ from django.shortcuts import render_to_response from django.utils.html import format_html, format_html_join from django.utils.http import urlquote from django.utils.text import capfirst -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text class FieldChoicePlugin(DatabrowsePlugin): @@ -35,7 +35,7 @@ class FieldChoicePlugin(DatabrowsePlugin): return '' return format_html('

    View by: {0}

    ', format_html_join(', ', '{1}', - ((f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()))) + ((f.name, force_text(capfirst(f.verbose_name))) for f in fields.values()))) def urls(self, plugin_name, easy_instance_field): if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values(): @@ -63,7 +63,7 @@ class FieldChoicePlugin(DatabrowsePlugin): def homepage_view(self, request): easy_model = EasyModel(self.site, self.model) - field_list = self.fields.values() + field_list = list(self.fields.values()) field_list.sort(key=lambda k: k.verbose_name) return render_to_response('databrowse/fieldchoice_homepage.html', {'root_url': self.site.root_url, 'model': easy_model, 'field_list': field_list}) diff --git a/django/contrib/formtools/tests/__init__.py b/django/contrib/formtools/tests/__init__.py index 3bccb55034..ee93479cbd 100644 --- a/django/contrib/formtools/tests/__init__.py +++ b/django/contrib/formtools/tests/__init__.py @@ -317,7 +317,7 @@ class WizardTests(TestCase): class WizardWithProcessStep(TestWizardClass): def process_step(self, request, form, step): - that.assertTrue(hasattr(form, 'cleaned_data')) + that.assertTrue(form.is_valid()) reached[0] = True wizard = WizardWithProcessStep([WizardPageOneForm, diff --git a/django/contrib/formtools/wizard/storage/base.py b/django/contrib/formtools/wizard/storage/base.py index 274e07ffbe..05c9f6f121 100644 --- a/django/contrib/formtools/wizard/storage/base.py +++ b/django/contrib/formtools/wizard/storage/base.py @@ -1,7 +1,8 @@ from django.core.files.uploadedfile import UploadedFile from django.utils.datastructures import MultiValueDict -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes from django.utils.functional import lazy_property +from django.utils import six from django.contrib.formtools.wizard.storage.exceptions import NoFileStorageConfigured @@ -72,9 +73,9 @@ class BaseStorage(object): raise NoFileStorageConfigured files = {} - for field, field_dict in wizard_files.iteritems(): - field_dict = dict((smart_str(k), v) - for k, v in field_dict.iteritems()) + for field, field_dict in six.iteritems(wizard_files): + field_dict = dict((smart_bytes(k), v) + for k, v in six.iteritems(field_dict)) tmp_name = field_dict.pop('tmp_name') files[field] = UploadedFile( file=self.file_storage.open(tmp_name), **field_dict) @@ -87,7 +88,7 @@ class BaseStorage(object): if step not in self.data[self.step_files_key]: self.data[self.step_files_key][step] = {} - for field, field_file in (files or {}).iteritems(): + for field, field_file in six.iteritems(files or {}): tmp_filename = self.file_storage.save(field_file.name, field_file) file_dict = { 'tmp_name': tmp_filename, diff --git a/django/contrib/formtools/wizard/views.py b/django/contrib/formtools/wizard/views.py index 466af1cac9..ea41e86852 100644 --- a/django/contrib/formtools/wizard/views.py +++ b/django/contrib/formtools/wizard/views.py @@ -44,7 +44,7 @@ class StepsHelper(object): @property def all(self): "Returns the names of all steps/forms." - return self._wizard.get_form_list().keys() + return list(self._wizard.get_form_list()) @property def count(self): @@ -164,14 +164,14 @@ class WizardView(TemplateView): init_form_list[six.text_type(i)] = form # walk through the new created list of forms - for form in init_form_list.itervalues(): + for form in six.itervalues(init_form_list): if issubclass(form, formsets.BaseFormSet): # if the element is based on BaseFormSet (FormSet/ModelFormSet) # we need to override the form variable. form = form.form # check if any form contains a FileField, if yes, we need a # file_storage added to the wizardview (by subclassing). - for field in form.base_fields.itervalues(): + for field in six.itervalues(form.base_fields): if (isinstance(field, forms.FileField) and not hasattr(cls, 'file_storage')): raise NoFileStorageConfigured @@ -196,7 +196,7 @@ class WizardView(TemplateView): could use data from other (maybe previous forms). """ form_list = SortedDict() - for form_key, form_class in self.form_list.iteritems(): + for form_key, form_class in six.iteritems(self.form_list): # try to fetch the value from condition list, by default, the form # gets passed to the new list. condition = self.condition_dict.get(form_key, True) diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py index c0e5aa6691..7152f4682d 100644 --- a/django/contrib/gis/db/backends/mysql/operations.py +++ b/django/contrib/gis/db/backends/mysql/operations.py @@ -3,6 +3,8 @@ from django.db.backends.mysql.base import DatabaseOperations from django.contrib.gis.db.backends.adapter import WKTAdapter from django.contrib.gis.db.backends.base import BaseSpatialOperations +from django.utils import six + class MySQLOperations(DatabaseOperations, BaseSpatialOperations): compiler_module = 'django.contrib.gis.db.backends.mysql.compiler' @@ -30,7 +32,7 @@ class MySQLOperations(DatabaseOperations, BaseSpatialOperations): 'within' : 'MBRWithin', } - gis_terms = dict([(term, None) for term in geometry_functions.keys() + ['isnull']]) + gis_terms = dict([(term, None) for term in list(geometry_functions) + ['isnull']]) def geo_db_type(self, f): return f.geom_type diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index 4e33942f7a..392feb129b 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -128,7 +128,7 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations): geometry_functions.update(distance_functions) gis_terms = ['isnull'] - gis_terms += geometry_functions.keys() + gis_terms += list(geometry_functions) gis_terms = dict([(term, None) for term in gis_terms]) truncate_params = {'relate' : None} diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index a6340ce22b..434d8719cc 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -163,7 +163,9 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): 'contains' : PostGISFunction(prefix, 'Contains'), 'intersects' : PostGISFunction(prefix, 'Intersects'), 'relate' : (PostGISRelate, six.string_types), - } + 'coveredby' : PostGISFunction(prefix, 'CoveredBy'), + 'covers' : PostGISFunction(prefix, 'Covers'), + } # Valid distance types and substitutions dtypes = (Decimal, Distance, float) + six.integer_types @@ -178,33 +180,12 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): 'distance_gte' : (get_dist_ops('>='), dtypes), 'distance_lt' : (get_dist_ops('<'), dtypes), 'distance_lte' : (get_dist_ops('<='), dtypes), - } - - # Versions 1.2.2+ have KML serialization support. - if version < (1, 2, 2): - ASKML = False - else: - ASKML = 'ST_AsKML' - self.geometry_functions.update( - {'coveredby' : PostGISFunction(prefix, 'CoveredBy'), - 'covers' : PostGISFunction(prefix, 'Covers'), - }) - self.distance_functions['dwithin'] = (PostGISFunctionParam(prefix, 'DWithin'), dtypes) + 'dwithin' : (PostGISFunctionParam(prefix, 'DWithin'), dtypes) + } # Adding the distance functions to the geometries lookup. self.geometry_functions.update(self.distance_functions) - # The union aggregate and topology operation use the same signature - # in versions 1.3+. - if version < (1, 3, 0): - UNIONAGG = 'GeomUnion' - UNION = 'Union' - MAKELINE = False - else: - UNIONAGG = 'ST_Union' - UNION = 'ST_Union' - MAKELINE = 'ST_MakeLine' - # Only PostGIS versions 1.3.4+ have GeoJSON serialization support. if version < (1, 3, 4): GEOJSON = False @@ -236,8 +217,8 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): # Creating a dictionary lookup of all GIS terms for PostGIS. gis_terms = ['isnull'] - gis_terms += self.geometry_operators.keys() - gis_terms += self.geometry_functions.keys() + gis_terms += list(self.geometry_operators) + gis_terms += list(self.geometry_functions) self.gis_terms = dict([(term, None) for term in gis_terms]) self.area = prefix + 'Area' @@ -256,11 +237,11 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): self.geojson = GEOJSON self.gml = prefix + 'AsGML' self.intersection = prefix + 'Intersection' - self.kml = ASKML + self.kml = prefix + 'AsKML' self.length = prefix + 'Length' self.length3d = prefix + 'Length3D' self.length_spheroid = prefix + 'length_spheroid' - self.makeline = MAKELINE + self.makeline = prefix + 'MakeLine' self.mem_size = prefix + 'mem_size' self.num_geom = prefix + 'NumGeometries' self.num_points =prefix + 'npoints' @@ -275,8 +256,8 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): self.sym_difference = prefix + 'SymDifference' self.transform = prefix + 'Transform' self.translate = prefix + 'Translate' - self.union = UNION - self.unionagg = UNIONAGG + self.union = prefix + 'Union' + self.unionagg = prefix + 'Union' def check_aggregate_support(self, aggregate): """ diff --git a/django/contrib/gis/db/backends/spatialite/creation.py b/django/contrib/gis/db/backends/spatialite/creation.py index 31f2fca1bd..d0a5f82033 100644 --- a/django/contrib/gis/db/backends/spatialite/creation.py +++ b/django/contrib/gis/db/backends/spatialite/creation.py @@ -99,14 +99,14 @@ class SpatiaLiteCreation(DatabaseCreation): """ This routine loads up the SpatiaLite SQL file. """ - if self.connection.ops.spatial_version[:2] >= (3, 0): - # Spatialite >= 3.0.x -- No need to load any SQL file, calling + if self.connection.ops.spatial_version[:2] >= (2, 4): + # Spatialite >= 2.4 -- No need to load any SQL file, calling # InitSpatialMetaData() transparently creates the spatial metadata # tables cur = self.connection._cursor() cur.execute("SELECT InitSpatialMetaData()") else: - # Spatialite < 3.0.x -- Load the initial SQL + # Spatialite < 2.4 -- Load the initial SQL # Getting the location of the SpatiaLite SQL file, and confirming # it exists. diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 1d7c4fab52..60fe0a8069 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -131,7 +131,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): # Creating the GIS terms dictionary. gis_terms = ['isnull'] - gis_terms += self.geometry_functions.keys() + gis_terms += list(self.geometry_functions) self.gis_terms = dict([(term, None) for term in gis_terms]) if version >= (2, 4, 0): diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py index dd2983aecc..cc61dfa4d2 100644 --- a/django/contrib/gis/db/models/query.py +++ b/django/contrib/gis/db/models/query.py @@ -1,5 +1,6 @@ from django.db import connections from django.db.models.query import QuerySet, ValuesQuerySet, ValuesListQuerySet +from django.utils import six from django.contrib.gis.db.models import aggregates from django.contrib.gis.db.models.fields import get_srid_info, PointField, LineStringField @@ -25,7 +26,7 @@ class GeoQuerySet(QuerySet): flat = kwargs.pop('flat', False) if kwargs: raise TypeError('Unexpected keyword arguments to values_list: %s' - % (kwargs.keys(),)) + % (list(kwargs),)) if flat and len(fields) > 1: raise TypeError("'flat' is not valid when values_list is called with more than one field.") return self._clone(klass=GeoValuesListQuerySet, setup=True, flat=flat, @@ -531,7 +532,7 @@ class GeoQuerySet(QuerySet): if settings.get('setup', True): default_args, geo_field = self._spatial_setup(att, desc=settings['desc'], field_name=field_name, geo_field_type=settings.get('geo_field_type', None)) - for k, v in default_args.iteritems(): settings['procedure_args'].setdefault(k, v) + for k, v in six.iteritems(default_args): settings['procedure_args'].setdefault(k, v) else: geo_field = settings['geo_field'] diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index d016357f1b..5c8d2647f7 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -3,6 +3,7 @@ from django.utils.six.moves import zip from django.db.backends.util import truncate_name, typecast_timestamp from django.db.models.sql import compiler from django.db.models.sql.constants import MULTI +from django.utils import six SQLCompiler = compiler.SQLCompiler @@ -24,7 +25,7 @@ class GeoSQLCompiler(compiler.SQLCompiler): qn = self.quote_name_unless_alias qn2 = self.connection.ops.quote_name result = ['(%s) AS %s' % (self.get_extra_select_format(alias) % col[0], qn2(alias)) - for alias, col in self.query.extra_select.iteritems()] + for alias, col in six.iteritems(self.query.extra_select)] aliases = set(self.query.extra_select.keys()) if with_aliases: col_aliases = aliases.copy() @@ -170,7 +171,7 @@ class GeoSQLCompiler(compiler.SQLCompiler): objects. """ values = [] - aliases = self.query.extra_select.keys() + aliases = list(self.query.extra_select) # Have to set a starting row number offset that is used for # determining the correct starting row index -- needed for diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index d752104e0a..373ece777d 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -40,7 +40,7 @@ """ # Python library requisites. import sys -from binascii import a2b_hex +from binascii import a2b_hex, b2a_hex from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p # Getting GDAL prerequisites @@ -322,8 +322,7 @@ class OGRGeometry(GDALBase): @property def hex(self): "Returns the hexadecimal representation of the WKB (a string)." - return str(self.wkb).encode('hex').upper() - #return b2a_hex(self.wkb).upper() + return b2a_hex(self.wkb).upper() @property def json(self): diff --git a/django/contrib/gis/geometry/test_data.py b/django/contrib/gis/geometry/test_data.py index f92740248e..505f0e4f4b 100644 --- a/django/contrib/gis/geometry/test_data.py +++ b/django/contrib/gis/geometry/test_data.py @@ -7,6 +7,7 @@ import json import os from django.contrib import gis +from django.utils import six # This global used to store reference geometry data. @@ -25,7 +26,7 @@ def tuplize(seq): def strconvert(d): "Converts all keys in dictionary to str type." - return dict([(str(k), v) for k, v in d.iteritems()]) + return dict([(str(k), v) for k, v in six.iteritems(d)]) def get_ds_file(name, ext): diff --git a/django/contrib/gis/measure.py b/django/contrib/gis/measure.py index ba7817e51c..6e074be355 100644 --- a/django/contrib/gis/measure.py +++ b/django/contrib/gis/measure.py @@ -143,7 +143,7 @@ class MeasureBase(object): def __rmul__(self, other): return self * other - def __div__(self, other): + def __truediv__(self, other): if isinstance(other, self.__class__): return self.standard / other.standard if isinstance(other, NUMERIC_TYPES): @@ -151,16 +151,19 @@ class MeasureBase(object): **{self.STANDARD_UNIT: (self.standard / other)}) else: raise TypeError('%(class)s must be divided with number or %(class)s' % {"class":pretty_name(self)}) + __div__ = __truediv__ # Python 2 compatibility - def __idiv__(self, other): + def __itruediv__(self, other): if isinstance(other, NUMERIC_TYPES): self.standard /= float(other) return self else: raise TypeError('%(class)s must be divided with number' % {"class":pretty_name(self)}) + __idiv__ = __itruediv__ # Python 2 compatibility - def __nonzero__(self): + def __bool__(self): return bool(self.standard) + __nonzero__ = __bool__ # Python 2 compatibility def default_units(self, kwargs): """ @@ -169,7 +172,7 @@ class MeasureBase(object): """ val = 0.0 default_unit = self.STANDARD_UNIT - for unit, value in kwargs.iteritems(): + for unit, value in six.iteritems(kwargs): if not isinstance(value, float): value = float(value) if unit in self.UNITS: val += self.UNITS[unit] * value @@ -305,12 +308,13 @@ class Area(MeasureBase): ALIAS = dict([(k, '%s%s' % (AREA_PREFIX, v)) for k, v in Distance.ALIAS.items()]) LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()]) - def __div__(self, other): + def __truediv__(self, other): if isinstance(other, NUMERIC_TYPES): return self.__class__(default_unit=self._default_unit, **{self.STANDARD_UNIT: (self.standard / other)}) else: raise TypeError('%(class)s must be divided by a number' % {"class":pretty_name(self)}) + __div__ = __truediv__ # Python 2 compatibility # Shortcuts diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index eb42d0cae8..8bcdba1b44 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -8,7 +8,8 @@ from django.core.paginator import EmptyPage, PageNotAnInteger from django.contrib.gis.db.models.fields import GeometryField from django.db import connections, DEFAULT_DB_ALIAS from django.db.models import get_model -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes +from django.utils import six from django.utils.translation import ugettext as _ from django.contrib.gis.shortcuts import render_to_kml, render_to_kmz @@ -46,7 +47,7 @@ def sitemap(request, sitemaps, section=None): raise Http404(_("No sitemap available for section: %r") % section) maps.append(sitemaps[section]) else: - maps = sitemaps.values() + maps = list(six.itervalues(sitemaps)) page = request.GET.get("p", 1) current_site = get_current_site(request) @@ -60,7 +61,7 @@ def sitemap(request, sitemaps, section=None): raise Http404(_("Page %s empty") % page) except PageNotAnInteger: raise Http404(_("No page '%s'") % page) - xml = smart_str(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls})) + xml = smart_bytes(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls})) return HttpResponse(xml, content_type='application/xml') def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB_ALIAS): diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py index a9d802d8f1..fffd7d3cab 100644 --- a/django/contrib/gis/tests/geoapp/test_regress.py +++ b/django/contrib/gis/tests/geoapp/test_regress.py @@ -12,7 +12,7 @@ from .models import City, PennsylvaniaCity, State, Truth class GeoRegressionTests(TestCase): - def test01_update(self): + def test_update(self): "Testing GeoQuerySet.update(). See #10411." pnt = City.objects.get(name='Pueblo').point bak = pnt.clone() @@ -24,7 +24,7 @@ class GeoRegressionTests(TestCase): City.objects.filter(name='Pueblo').update(point=bak) self.assertEqual(bak, City.objects.get(name='Pueblo').point) - def test02_kmz(self): + def test_kmz(self): "Testing `render_to_kmz` with non-ASCII data. See #11624." name = '\xc3\x85land Islands'.decode('iso-8859-1') places = [{'name' : name, @@ -35,7 +35,7 @@ class GeoRegressionTests(TestCase): @no_spatialite @no_mysql - def test03_extent(self): + def test_extent(self): "Testing `extent` on a table with a single point. See #11827." pnt = City.objects.get(name='Pueblo').point ref_ext = (pnt.x, pnt.y, pnt.x, pnt.y) @@ -43,14 +43,14 @@ class GeoRegressionTests(TestCase): for ref_val, val in zip(ref_ext, extent): self.assertAlmostEqual(ref_val, val, 4) - def test04_unicode_date(self): + def test_unicode_date(self): "Testing dates are converted properly, even on SpatiaLite. See #16408." founded = datetime(1857, 5, 23) mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)', founded=founded) self.assertEqual(founded, PennsylvaniaCity.objects.dates('founded', 'day')[0]) - def test05_empty_count(self): + def test_empty_count(self): "Testing that PostGISAdapter.__eq__ does check empty strings. See #13670." # contrived example, but need a geo lookup paired with an id__in lookup pueblo = City.objects.get(name='Pueblo') @@ -60,12 +60,12 @@ class GeoRegressionTests(TestCase): # .count() should not throw TypeError in __eq__ self.assertEqual(cities_within_state.count(), 1) - def test06_defer_or_only_with_annotate(self): + def test_defer_or_only_with_annotate(self): "Regression for #16409. Make sure defer() and only() work with annotate()" self.assertIsInstance(list(City.objects.annotate(Count('point')).defer('name')), list) self.assertIsInstance(list(City.objects.annotate(Count('point')).only('name')), list) - def test07_boolean_conversion(self): + def test_boolean_conversion(self): "Testing Boolean value conversion with the spatial backend, see #15169." t1 = Truth.objects.create(val=True) t2 = Truth.objects.create(val=False) diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index bcdbe734ff..b06d6b5e1b 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -15,19 +15,24 @@ from django.utils import six from .models import Country, City, PennsylvaniaCity, State, Track +from .test_feeds import GeoFeedTest +from .test_regress import GeoRegressionTests +from .test_sitemaps import GeoSitemapTest + + if not spatialite: from .models import Feature, MinusOneSRID class GeoModelTest(TestCase): - def test01_fixtures(self): + def test_fixtures(self): "Testing geographic model initialization from fixtures." # Ensuring that data was loaded from initial data fixtures. self.assertEqual(2, Country.objects.count()) self.assertEqual(8, City.objects.count()) self.assertEqual(2, State.objects.count()) - def test02_proxy(self): + def test_proxy(self): "Testing Lazy-Geometry support (using the GeometryProxy)." ## Testing on a Point pnt = Point(0, 0) @@ -95,165 +100,97 @@ class GeoModelTest(TestCase): self.assertEqual(ply, State.objects.get(name='NullState').poly) ns.delete() - def test03a_kml(self): - "Testing KML output from the database using GeoQuerySet.kml()." - # Only PostGIS and Spatialite (>=2.4.0-RC4) support KML serialization - if not (postgis or (spatialite and connection.ops.kml)): - self.assertRaises(NotImplementedError, State.objects.all().kml, field_name='poly') - return - - # Should throw a TypeError when trying to obtain KML from a - # non-geometry field. - qs = City.objects.all() - self.assertRaises(TypeError, qs.kml, 'name') - - # The reference KML depends on the version of PostGIS used - # (the output stopped including altitude in 1.3.3). - if connection.ops.spatial_version >= (1, 3, 3): - ref_kml = '-104.609252,38.255001' - else: - ref_kml = '-104.609252,38.255001,0' - - # Ensuring the KML is as expected. - ptown1 = City.objects.kml(field_name='point', precision=9).get(name='Pueblo') - ptown2 = City.objects.kml(precision=9).get(name='Pueblo') - for ptown in [ptown1, ptown2]: - self.assertEqual(ref_kml, ptown.kml) - - def test03b_gml(self): - "Testing GML output from the database using GeoQuerySet.gml()." - if mysql or (spatialite and not connection.ops.gml) : - self.assertRaises(NotImplementedError, Country.objects.all().gml, field_name='mpoly') - return - - # Should throw a TypeError when tyring to obtain GML from a - # non-geometry field. - qs = City.objects.all() - self.assertRaises(TypeError, qs.gml, field_name='name') - ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo') - ptown2 = City.objects.gml(precision=9).get(name='Pueblo') + @no_mysql + def test_lookup_insert_transform(self): + "Testing automatic transform for lookups and inserts." + # San Antonio in 'WGS84' (SRID 4326) + sa_4326 = 'POINT (-98.493183 29.424170)' + wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84 + # Oracle doesn't have SRID 3084, using 41157. if oracle: - # No precision parameter for Oracle :-/ - gml_regex = re.compile(r'^-104.60925\d+,38.25500\d+ ') - elif spatialite: - # Spatialite has extra colon in SrsName - gml_regex = re.compile(r'^-104.609251\d+,38.255001') + # San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157) + # Used the following Oracle SQL to get this value: + # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM(SDO_GEOMETRY('POINT (-98.493183 29.424170)', 4326), 41157)) FROM DUAL; + nad_wkt = 'POINT (300662.034646583 5416427.45974934)' + nad_srid = 41157 else: - gml_regex = re.compile(r'^-104\.60925\d+,38\.255001') + # San Antonio in 'NAD83(HARN) / Texas Centric Lambert Conformal' (SRID 3084) + nad_wkt = 'POINT (1645978.362408288754523 6276356.025927528738976)' # Used ogr.py in gdal 1.4.1 for this transform + nad_srid = 3084 - for ptown in [ptown1, ptown2]: - self.assertTrue(gml_regex.match(ptown.gml)) - - - def test03c_geojson(self): - "Testing GeoJSON output from the database using GeoQuerySet.geojson()." - # Only PostGIS 1.3.4+ supports GeoJSON. - if not connection.ops.geojson: - self.assertRaises(NotImplementedError, Country.objects.all().geojson, field_name='mpoly') - return - - if connection.ops.spatial_version >= (1, 4, 0): - pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}' - houston_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-95.363151,29.763374]}' - victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.305196,48.462611]}' - chicago_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}' + # Constructing & querying with a point from a different SRID. Oracle + # `SDO_OVERLAPBDYINTERSECT` operates differently from + # `ST_Intersects`, so contains is used instead. + nad_pnt = fromstr(nad_wkt, srid=nad_srid) + if oracle: + tx = Country.objects.get(mpoly__contains=nad_pnt) else: - pueblo_json = '{"type":"Point","coordinates":[-104.60925200,38.25500100]}' - houston_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"coordinates":[-95.36315100,29.76337400]}' - victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.30519600,48.46261100]}' - chicago_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}' + tx = Country.objects.get(mpoly__intersects=nad_pnt) + self.assertEqual('Texas', tx.name) - # Precision argument should only be an integer - self.assertRaises(TypeError, City.objects.geojson, precision='foo') + # Creating San Antonio. Remember the Alamo. + sa = City.objects.create(name='San Antonio', point=nad_pnt) - # Reference queries and values. - # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo'; - self.assertEqual(pueblo_json, City.objects.geojson().get(name='Pueblo').geojson) + # Now verifying that San Antonio was transformed correctly + sa = City.objects.get(name='San Antonio') + self.assertAlmostEqual(wgs_pnt.x, sa.point.x, 6) + self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6) - # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; - # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; - # This time we want to include the CRS by using the `crs` keyword. - self.assertEqual(houston_json, City.objects.geojson(crs=True, model_att='json').get(name='Houston').json) + # If the GeometryField SRID is -1, then we shouldn't perform any + # transformation if the SRID of the input geometry is different. + # SpatiaLite does not support missing SRID values. + if not spatialite: + m1 = MinusOneSRID(geom=Point(17, 23, srid=4326)) + m1.save() + self.assertEqual(-1, m1.geom.srid) - # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Victoria'; - # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; - # This time we include the bounding box by using the `bbox` keyword. - self.assertEqual(victoria_json, City.objects.geojson(bbox=True).get(name='Victoria').geojson) + def test_createnull(self): + "Testing creating a model instance and the geometry being None" + c = City() + self.assertEqual(c.point, None) - # 1.(3|4).x: SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago'; - # Finally, we set every available keyword. - self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson) + @no_spatialite # SpatiaLite does not support abstract geometry columns + def test_geometryfield(self): + "Testing the general GeometryField." + Feature(name='Point', geom=Point(1, 1)).save() + Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))).save() + Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))).save() + Feature(name='GeometryCollection', + geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)), + Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))).save() - def test03d_svg(self): - "Testing SVG output using GeoQuerySet.svg()." - if mysql or oracle: - self.assertRaises(NotImplementedError, City.objects.svg) - return + f_1 = Feature.objects.get(name='Point') + self.assertEqual(True, isinstance(f_1.geom, Point)) + self.assertEqual((1.0, 1.0), f_1.geom.tuple) + f_2 = Feature.objects.get(name='LineString') + self.assertEqual(True, isinstance(f_2.geom, LineString)) + self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple) - self.assertRaises(TypeError, City.objects.svg, precision='foo') - # SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo'; - svg1 = 'cx="-104.609252" cy="-38.255001"' - # Even though relative, only one point so it's practically the same except for - # the 'c' letter prefix on the x,y values. - svg2 = svg1.replace('c', '') - self.assertEqual(svg1, City.objects.svg().get(name='Pueblo').svg) - self.assertEqual(svg2, City.objects.svg(relative=5).get(name='Pueblo').svg) + f_3 = Feature.objects.get(name='Polygon') + self.assertEqual(True, isinstance(f_3.geom, Polygon)) + f_4 = Feature.objects.get(name='GeometryCollection') + self.assertEqual(True, isinstance(f_4.geom, GeometryCollection)) + self.assertEqual(f_3.geom, f_4.geom[2]) @no_mysql - def test04_transform(self): - "Testing the transform() GeoManager method." - # Pre-transformed points for Houston and Pueblo. - htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084) - ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774) - prec = 3 # Precision is low due to version variations in PROJ and GDAL. + def test_inherited_geofields(self): + "Test GeoQuerySet methods on inherited Geometry fields." + # Creating a Pennsylvanian city. + mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)') - # Asserting the result of the transform operation with the values in - # the pre-transformed points. Oracle does not have the 3084 SRID. - if not oracle: - h = City.objects.transform(htown.srid).get(name='Houston') - self.assertEqual(3084, h.point.srid) - self.assertAlmostEqual(htown.x, h.point.x, prec) - self.assertAlmostEqual(htown.y, h.point.y, prec) + # All transformation SQL will need to be performed on the + # _parent_ table. + qs = PennsylvaniaCity.objects.transform(32128) - p1 = City.objects.transform(ptown.srid, field_name='point').get(name='Pueblo') - p2 = City.objects.transform(srid=ptown.srid).get(name='Pueblo') - for p in [p1, p2]: - self.assertEqual(2774, p.point.srid) - self.assertAlmostEqual(ptown.x, p.point.x, prec) - self.assertAlmostEqual(ptown.y, p.point.y, prec) + self.assertEqual(1, qs.count()) + for pc in qs: self.assertEqual(32128, pc.point.srid) + + +class GeoLookupTest(TestCase): @no_mysql - @no_spatialite # SpatiaLite does not have an Extent function - def test05_extent(self): - "Testing the `extent` GeoQuerySet method." - # Reference query: - # `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');` - # => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203) - expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) - - qs = City.objects.filter(name__in=('Houston', 'Dallas')) - extent = qs.extent() - - for val, exp in zip(extent, expected): - self.assertAlmostEqual(exp, val, 4) - - # Only PostGIS has support for the MakeLine aggregate. - @no_mysql - @no_oracle - @no_spatialite - def test06_make_line(self): - "Testing the `make_line` GeoQuerySet method." - # Ensuring that a `TypeError` is raised on models without PointFields. - self.assertRaises(TypeError, State.objects.make_line) - self.assertRaises(TypeError, Country.objects.make_line) - # Reference query: - # SELECT AsText(ST_MakeLine(geoapp_city.point)) FROM geoapp_city; - ref_line = GEOSGeometry('LINESTRING(-95.363151 29.763374,-96.801611 32.782057,-97.521157 34.464642,174.783117 -41.315268,-104.609252 38.255001,-95.23506 38.971823,-87.650175 41.850385,-123.305196 48.462611)', srid=4326) - self.assertEqual(ref_line, City.objects.make_line()) - - @no_mysql - def test09_disjoint(self): + def test_disjoint_lookup(self): "Testing the `disjoint` lookup type." ptown = City.objects.get(name='Pueblo') qs1 = City.objects.filter(point__disjoint=ptown.point) @@ -263,7 +200,7 @@ class GeoModelTest(TestCase): self.assertEqual(1, qs2.count()) self.assertEqual('Kansas', qs2[0].name) - def test10_contains_contained(self): + def test_contains_contained_lookups(self): "Testing the 'contained', 'contains', and 'bbcontains' lookup types." # Getting Texas, yes we were a country -- once ;) texas = Country.objects.get(name='Texas') @@ -308,86 +245,11 @@ class GeoModelTest(TestCase): self.assertEqual(1, len(qs)) self.assertEqual('Texas', qs[0].name) - @no_mysql - def test11_lookup_insert_transform(self): - "Testing automatic transform for lookups and inserts." - # San Antonio in 'WGS84' (SRID 4326) - sa_4326 = 'POINT (-98.493183 29.424170)' - wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84 - - # Oracle doesn't have SRID 3084, using 41157. - if oracle: - # San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157) - # Used the following Oracle SQL to get this value: - # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM(SDO_GEOMETRY('POINT (-98.493183 29.424170)', 4326), 41157)) FROM DUAL; - nad_wkt = 'POINT (300662.034646583 5416427.45974934)' - nad_srid = 41157 - else: - # San Antonio in 'NAD83(HARN) / Texas Centric Lambert Conformal' (SRID 3084) - nad_wkt = 'POINT (1645978.362408288754523 6276356.025927528738976)' # Used ogr.py in gdal 1.4.1 for this transform - nad_srid = 3084 - - # Constructing & querying with a point from a different SRID. Oracle - # `SDO_OVERLAPBDYINTERSECT` operates differently from - # `ST_Intersects`, so contains is used instead. - nad_pnt = fromstr(nad_wkt, srid=nad_srid) - if oracle: - tx = Country.objects.get(mpoly__contains=nad_pnt) - else: - tx = Country.objects.get(mpoly__intersects=nad_pnt) - self.assertEqual('Texas', tx.name) - - # Creating San Antonio. Remember the Alamo. - sa = City.objects.create(name='San Antonio', point=nad_pnt) - - # Now verifying that San Antonio was transformed correctly - sa = City.objects.get(name='San Antonio') - self.assertAlmostEqual(wgs_pnt.x, sa.point.x, 6) - self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6) - - # If the GeometryField SRID is -1, then we shouldn't perform any - # transformation if the SRID of the input geometry is different. - # SpatiaLite does not support missing SRID values. - if not spatialite: - m1 = MinusOneSRID(geom=Point(17, 23, srid=4326)) - m1.save() - self.assertEqual(-1, m1.geom.srid) - - @no_mysql - def test12_null_geometries(self): - "Testing NULL geometry support, and the `isnull` lookup type." - # Creating a state with a NULL boundary. - State.objects.create(name='Puerto Rico') - - # Querying for both NULL and Non-NULL values. - nullqs = State.objects.filter(poly__isnull=True) - validqs = State.objects.filter(poly__isnull=False) - - # Puerto Rico should be NULL (it's a commonwealth unincorporated territory) - self.assertEqual(1, len(nullqs)) - self.assertEqual('Puerto Rico', nullqs[0].name) - - # The valid states should be Colorado & Kansas - self.assertEqual(2, len(validqs)) - state_names = [s.name for s in validqs] - self.assertEqual(True, 'Colorado' in state_names) - self.assertEqual(True, 'Kansas' in state_names) - - # Saving another commonwealth w/a NULL geometry. - nmi = State.objects.create(name='Northern Mariana Islands', poly=None) - self.assertEqual(nmi.poly, None) - - # Assigning a geomery and saving -- then UPDATE back to NULL. - nmi.poly = 'POLYGON((0 0,1 0,1 1,1 0,0 0))' - nmi.save() - State.objects.filter(name='Northern Mariana Islands').update(poly=None) - self.assertEqual(None, State.objects.get(name='Northern Mariana Islands').poly) - # Only PostGIS has `left` and `right` lookup types. @no_mysql @no_oracle @no_spatialite - def test13_left_right(self): + def test_left_right_lookups(self): "Testing the 'left' and 'right' lookup types." # Left: A << B => true if xmax(A) < xmin(B) # Right: A >> B => true if xmin(A) > xmax(B) @@ -423,7 +285,7 @@ class GeoModelTest(TestCase): self.assertEqual(2, len(qs)) for c in qs: self.assertEqual(True, c.name in cities) - def test14_equals(self): + def test_equals_lookups(self): "Testing the 'same_as' and 'equals' lookup types." pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326) c1 = City.objects.get(point=pnt) @@ -432,7 +294,37 @@ class GeoModelTest(TestCase): for c in [c1, c2, c3]: self.assertEqual('Houston', c.name) @no_mysql - def test15_relate(self): + def test_null_geometries(self): + "Testing NULL geometry support, and the `isnull` lookup type." + # Creating a state with a NULL boundary. + State.objects.create(name='Puerto Rico') + + # Querying for both NULL and Non-NULL values. + nullqs = State.objects.filter(poly__isnull=True) + validqs = State.objects.filter(poly__isnull=False) + + # Puerto Rico should be NULL (it's a commonwealth unincorporated territory) + self.assertEqual(1, len(nullqs)) + self.assertEqual('Puerto Rico', nullqs[0].name) + + # The valid states should be Colorado & Kansas + self.assertEqual(2, len(validqs)) + state_names = [s.name for s in validqs] + self.assertEqual(True, 'Colorado' in state_names) + self.assertEqual(True, 'Kansas' in state_names) + + # Saving another commonwealth w/a NULL geometry. + nmi = State.objects.create(name='Northern Mariana Islands', poly=None) + self.assertEqual(nmi.poly, None) + + # Assigning a geomery and saving -- then UPDATE back to NULL. + nmi.poly = 'POLYGON((0 0,1 0,1 1,1 0,0 0))' + nmi.save() + State.objects.filter(name='Northern Mariana Islands').update(poly=None) + self.assertEqual(None, State.objects.get(name='Northern Mariana Islands').poly) + + @no_mysql + def test_relate_lookup(self): "Testing the 'relate' lookup type." # To make things more interesting, we will have our Texas reference point in # different SRIDs. @@ -474,60 +366,12 @@ class GeoModelTest(TestCase): self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, intersects_mask)).name) self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name) - def test16_createnull(self): - "Testing creating a model instance and the geometry being None" - c = City() - self.assertEqual(c.point, None) + +class GeoQuerySetTest(TestCase): + # Please keep the tests in GeoQuerySet method's alphabetic order @no_mysql - def test17_unionagg(self): - "Testing the `unionagg` (aggregate union) GeoManager method." - tx = Country.objects.get(name='Texas').mpoly - # Houston, Dallas -- Oracle has different order. - union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') - union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') - qs = City.objects.filter(point__within=tx) - self.assertRaises(TypeError, qs.unionagg, 'name') - # Using `field_name` keyword argument in one query and specifying an - # order in the other (which should not be used because this is - # an aggregate method on a spatial column) - u1 = qs.unionagg(field_name='point') - u2 = qs.order_by('name').unionagg() - tol = 0.00001 - if oracle: - union = union2 - else: - union = union1 - self.assertEqual(True, union.equals_exact(u1, tol)) - self.assertEqual(True, union.equals_exact(u2, tol)) - qs = City.objects.filter(name='NotACity') - self.assertEqual(None, qs.unionagg(field_name='point')) - - @no_spatialite # SpatiaLite does not support abstract geometry columns - def test18_geometryfield(self): - "Testing the general GeometryField." - Feature(name='Point', geom=Point(1, 1)).save() - Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))).save() - Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))).save() - Feature(name='GeometryCollection', - geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)), - Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))).save() - - f_1 = Feature.objects.get(name='Point') - self.assertEqual(True, isinstance(f_1.geom, Point)) - self.assertEqual((1.0, 1.0), f_1.geom.tuple) - f_2 = Feature.objects.get(name='LineString') - self.assertEqual(True, isinstance(f_2.geom, LineString)) - self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple) - - f_3 = Feature.objects.get(name='Polygon') - self.assertEqual(True, isinstance(f_3.geom, Polygon)) - f_4 = Feature.objects.get(name='GeometryCollection') - self.assertEqual(True, isinstance(f_4.geom, GeometryCollection)) - self.assertEqual(f_3.geom, f_4.geom[2]) - - @no_mysql - def test19_centroid(self): + def test_centroid(self): "Testing the `centroid` GeoQuerySet method." qs = State.objects.exclude(poly__isnull=True).centroid() if oracle: @@ -540,84 +384,7 @@ class GeoModelTest(TestCase): self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol)) @no_mysql - def test20_pointonsurface(self): - "Testing the `point_on_surface` GeoQuerySet method." - # Reference values. - if oracle: - # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_GEOM.SDO_POINTONSURFACE(GEOAPP_COUNTRY.MPOLY, 0.05)) FROM GEOAPP_COUNTRY; - ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326), - 'Texas' : fromstr('POINT (-103.002434 36.500397)', srid=4326), - } - - elif postgis or spatialite: - # Using GEOSGeometry to compute the reference point on surface values - # -- since PostGIS also uses GEOS these should be the same. - ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface, - 'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface - } - - for c in Country.objects.point_on_surface(): - if spatialite: - # XXX This seems to be a WKT-translation-related precision issue? - tol = 0.00001 - else: - tol = 0.000000001 - self.assertEqual(True, ref[c.name].equals_exact(c.point_on_surface, tol)) - - @no_mysql - @no_oracle - def test21_scale(self): - "Testing the `scale` GeoQuerySet method." - xfac, yfac = 2, 3 - tol = 5 # XXX The low precision tolerance is for SpatiaLite - qs = Country.objects.scale(xfac, yfac, model_att='scaled') - for c in qs: - for p1, p2 in zip(c.mpoly, c.scaled): - for r1, r2 in zip(p1, p2): - for c1, c2 in zip(r1.coords, r2.coords): - self.assertAlmostEqual(c1[0] * xfac, c2[0], tol) - self.assertAlmostEqual(c1[1] * yfac, c2[1], tol) - - @no_mysql - @no_oracle - def test22_translate(self): - "Testing the `translate` GeoQuerySet method." - xfac, yfac = 5, -23 - qs = Country.objects.translate(xfac, yfac, model_att='translated') - for c in qs: - for p1, p2 in zip(c.mpoly, c.translated): - for r1, r2 in zip(p1, p2): - for c1, c2 in zip(r1.coords, r2.coords): - # XXX The low precision is for SpatiaLite - self.assertAlmostEqual(c1[0] + xfac, c2[0], 5) - self.assertAlmostEqual(c1[1] + yfac, c2[1], 5) - - @no_mysql - def test23_numgeom(self): - "Testing the `num_geom` GeoQuerySet method." - # Both 'countries' only have two geometries. - for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom) - for c in City.objects.filter(point__isnull=False).num_geom(): - # Oracle will return 1 for the number of geometries on non-collections, - # whereas PostGIS will return None. - if postgis: - self.assertEqual(None, c.num_geom) - else: - self.assertEqual(1, c.num_geom) - - @no_mysql - @no_spatialite # SpatiaLite can only count vertices in LineStrings - def test24_numpoints(self): - "Testing the `num_points` GeoQuerySet method." - for c in Country.objects.num_points(): - self.assertEqual(c.mpoly.num_points, c.num_points) - - if not oracle: - # Oracle cannot count vertices in Point geometries. - for c in City.objects.num_points(): self.assertEqual(1, c.num_points) - - @no_mysql - def test25_geoset(self): + def test_diff_intersection_union(self): "Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods." geom = Point(5, 23) tol = 1 @@ -644,22 +411,232 @@ class GeoModelTest(TestCase): self.assertEqual(c.mpoly.union(geom), c.union) @no_mysql - def test26_inherited_geofields(self): - "Test GeoQuerySet methods on inherited Geometry fields." - # Creating a Pennsylvanian city. - mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)') + @no_spatialite # SpatiaLite does not have an Extent function + def test_extent(self): + "Testing the `extent` GeoQuerySet method." + # Reference query: + # `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');` + # => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203) + expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) - # All transformation SQL will need to be performed on the - # _parent_ table. - qs = PennsylvaniaCity.objects.transform(32128) + qs = City.objects.filter(name__in=('Houston', 'Dallas')) + extent = qs.extent() - self.assertEqual(1, qs.count()) - for pc in qs: self.assertEqual(32128, pc.point.srid) + for val, exp in zip(extent, expected): + self.assertAlmostEqual(exp, val, 4) @no_mysql @no_oracle @no_spatialite - def test27_snap_to_grid(self): + def test_force_rhr(self): + "Testing GeoQuerySet.force_rhr()." + rings = ( ( (0, 0), (5, 0), (0, 5), (0, 0) ), + ( (1, 1), (1, 3), (3, 1), (1, 1) ), + ) + rhr_rings = ( ( (0, 0), (0, 5), (5, 0), (0, 0) ), + ( (1, 1), (3, 1), (1, 3), (1, 1) ), + ) + State.objects.create(name='Foo', poly=Polygon(*rings)) + s = State.objects.force_rhr().get(name='Foo') + self.assertEqual(rhr_rings, s.force_rhr.coords) + + @no_mysql + @no_oracle + @no_spatialite + def test_geohash(self): + "Testing GeoQuerySet.geohash()." + if not connection.ops.geohash: return + # Reference query: + # SELECT ST_GeoHash(point) FROM geoapp_city WHERE name='Houston'; + # SELECT ST_GeoHash(point, 5) FROM geoapp_city WHERE name='Houston'; + ref_hash = '9vk1mfq8jx0c8e0386z6' + h1 = City.objects.geohash().get(name='Houston') + h2 = City.objects.geohash(precision=5).get(name='Houston') + self.assertEqual(ref_hash, h1.geohash) + self.assertEqual(ref_hash[:5], h2.geohash) + + def test_geojson(self): + "Testing GeoJSON output from the database using GeoQuerySet.geojson()." + # Only PostGIS 1.3.4+ supports GeoJSON. + if not connection.ops.geojson: + self.assertRaises(NotImplementedError, Country.objects.all().geojson, field_name='mpoly') + return + + if connection.ops.spatial_version >= (1, 4, 0): + pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}' + houston_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-95.363151,29.763374]}' + victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.305196,48.462611]}' + chicago_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}' + else: + pueblo_json = '{"type":"Point","coordinates":[-104.60925200,38.25500100]}' + houston_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"coordinates":[-95.36315100,29.76337400]}' + victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.30519600,48.46261100]}' + chicago_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}' + + # Precision argument should only be an integer + self.assertRaises(TypeError, City.objects.geojson, precision='foo') + + # Reference queries and values. + # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo'; + self.assertEqual(pueblo_json, City.objects.geojson().get(name='Pueblo').geojson) + + # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; + # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; + # This time we want to include the CRS by using the `crs` keyword. + self.assertEqual(houston_json, City.objects.geojson(crs=True, model_att='json').get(name='Houston').json) + + # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Victoria'; + # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston'; + # This time we include the bounding box by using the `bbox` keyword. + self.assertEqual(victoria_json, City.objects.geojson(bbox=True).get(name='Victoria').geojson) + + # 1.(3|4).x: SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago'; + # Finally, we set every available keyword. + self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson) + + def test_gml(self): + "Testing GML output from the database using GeoQuerySet.gml()." + if mysql or (spatialite and not connection.ops.gml) : + self.assertRaises(NotImplementedError, Country.objects.all().gml, field_name='mpoly') + return + + # Should throw a TypeError when tyring to obtain GML from a + # non-geometry field. + qs = City.objects.all() + self.assertRaises(TypeError, qs.gml, field_name='name') + ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo') + ptown2 = City.objects.gml(precision=9).get(name='Pueblo') + + if oracle: + # No precision parameter for Oracle :-/ + gml_regex = re.compile(r'^-104.60925\d+,38.25500\d+ ') + elif spatialite: + # Spatialite has extra colon in SrsName + gml_regex = re.compile(r'^-104.609251\d+,38.255001') + else: + gml_regex = re.compile(r'^-104\.60925\d+,38\.255001') + + for ptown in [ptown1, ptown2]: + self.assertTrue(gml_regex.match(ptown.gml)) + + def test_kml(self): + "Testing KML output from the database using GeoQuerySet.kml()." + # Only PostGIS and Spatialite (>=2.4.0-RC4) support KML serialization + if not (postgis or (spatialite and connection.ops.kml)): + self.assertRaises(NotImplementedError, State.objects.all().kml, field_name='poly') + return + + # Should throw a TypeError when trying to obtain KML from a + # non-geometry field. + qs = City.objects.all() + self.assertRaises(TypeError, qs.kml, 'name') + + # The reference KML depends on the version of PostGIS used + # (the output stopped including altitude in 1.3.3). + if connection.ops.spatial_version >= (1, 3, 3): + ref_kml = '-104.609252,38.255001' + else: + ref_kml = '-104.609252,38.255001,0' + + # Ensuring the KML is as expected. + ptown1 = City.objects.kml(field_name='point', precision=9).get(name='Pueblo') + ptown2 = City.objects.kml(precision=9).get(name='Pueblo') + for ptown in [ptown1, ptown2]: + self.assertEqual(ref_kml, ptown.kml) + + # Only PostGIS has support for the MakeLine aggregate. + @no_mysql + @no_oracle + @no_spatialite + def test_make_line(self): + "Testing the `make_line` GeoQuerySet method." + # Ensuring that a `TypeError` is raised on models without PointFields. + self.assertRaises(TypeError, State.objects.make_line) + self.assertRaises(TypeError, Country.objects.make_line) + # Reference query: + # SELECT AsText(ST_MakeLine(geoapp_city.point)) FROM geoapp_city; + ref_line = GEOSGeometry('LINESTRING(-95.363151 29.763374,-96.801611 32.782057,-97.521157 34.464642,174.783117 -41.315268,-104.609252 38.255001,-95.23506 38.971823,-87.650175 41.850385,-123.305196 48.462611)', srid=4326) + self.assertEqual(ref_line, City.objects.make_line()) + + @no_mysql + def test_num_geom(self): + "Testing the `num_geom` GeoQuerySet method." + # Both 'countries' only have two geometries. + for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom) + for c in City.objects.filter(point__isnull=False).num_geom(): + # Oracle will return 1 for the number of geometries on non-collections, + # whereas PostGIS will return None. + if postgis: + self.assertEqual(None, c.num_geom) + else: + self.assertEqual(1, c.num_geom) + + @no_mysql + @no_spatialite # SpatiaLite can only count vertices in LineStrings + def test_num_points(self): + "Testing the `num_points` GeoQuerySet method." + for c in Country.objects.num_points(): + self.assertEqual(c.mpoly.num_points, c.num_points) + + if not oracle: + # Oracle cannot count vertices in Point geometries. + for c in City.objects.num_points(): self.assertEqual(1, c.num_points) + + @no_mysql + def test_point_on_surface(self): + "Testing the `point_on_surface` GeoQuerySet method." + # Reference values. + if oracle: + # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_GEOM.SDO_POINTONSURFACE(GEOAPP_COUNTRY.MPOLY, 0.05)) FROM GEOAPP_COUNTRY; + ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326), + 'Texas' : fromstr('POINT (-103.002434 36.500397)', srid=4326), + } + + elif postgis or spatialite: + # Using GEOSGeometry to compute the reference point on surface values + # -- since PostGIS also uses GEOS these should be the same. + ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface, + 'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface + } + + for c in Country.objects.point_on_surface(): + if spatialite: + # XXX This seems to be a WKT-translation-related precision issue? + tol = 0.00001 + else: + tol = 0.000000001 + self.assertEqual(True, ref[c.name].equals_exact(c.point_on_surface, tol)) + + @no_mysql + @no_spatialite + def test_reverse_geom(self): + "Testing GeoQuerySet.reverse_geom()." + coords = [ (-95.363151, 29.763374), (-95.448601, 29.713803) ] + Track.objects.create(name='Foo', line=LineString(coords)) + t = Track.objects.reverse_geom().get(name='Foo') + coords.reverse() + self.assertEqual(tuple(coords), t.reverse_geom.coords) + if oracle: + self.assertRaises(TypeError, State.objects.reverse_geom) + + @no_mysql + @no_oracle + def test_scale(self): + "Testing the `scale` GeoQuerySet method." + xfac, yfac = 2, 3 + tol = 5 # XXX The low precision tolerance is for SpatiaLite + qs = Country.objects.scale(xfac, yfac, model_att='scaled') + for c in qs: + for p1, p2 in zip(c.mpoly, c.scaled): + for r1, r2 in zip(p1, p2): + for c1, c2 in zip(r1.coords, r2.coords): + self.assertAlmostEqual(c1[0] * xfac, c2[0], tol) + self.assertAlmostEqual(c1[1] * yfac, c2[1], tol) + + @no_mysql + @no_oracle + @no_spatialite + def test_snap_to_grid(self): "Testing GeoQuerySet.snap_to_grid()." # Let's try and break snap_to_grid() with bad combinations of arguments. for bad_args in ((), range(3), range(5)): @@ -695,48 +672,78 @@ class GeoModelTest(TestCase): ref = fromstr('MULTIPOLYGON(((12.4 43.87,12.45 43.87,12.45 44.1,12.5 44.1,12.5 43.87,12.45 43.87,12.4 43.87)))') self.assertTrue(ref.equals_exact(Country.objects.snap_to_grid(0.05, 0.23, 0.5, 0.17).get(name='San Marino').snap_to_grid, tol)) + def test_svg(self): + "Testing SVG output using GeoQuerySet.svg()." + if mysql or oracle: + self.assertRaises(NotImplementedError, City.objects.svg) + return + + self.assertRaises(TypeError, City.objects.svg, precision='foo') + # SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo'; + svg1 = 'cx="-104.609252" cy="-38.255001"' + # Even though relative, only one point so it's practically the same except for + # the 'c' letter prefix on the x,y values. + svg2 = svg1.replace('c', '') + self.assertEqual(svg1, City.objects.svg().get(name='Pueblo').svg) + self.assertEqual(svg2, City.objects.svg(relative=5).get(name='Pueblo').svg) + @no_mysql - @no_spatialite - def test28_reverse(self): - "Testing GeoQuerySet.reverse_geom()." - coords = [ (-95.363151, 29.763374), (-95.448601, 29.713803) ] - Track.objects.create(name='Foo', line=LineString(coords)) - t = Track.objects.reverse_geom().get(name='Foo') - coords.reverse() - self.assertEqual(tuple(coords), t.reverse_geom.coords) + def test_transform(self): + "Testing the transform() GeoQuerySet method." + # Pre-transformed points for Houston and Pueblo. + htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084) + ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774) + prec = 3 # Precision is low due to version variations in PROJ and GDAL. + + # Asserting the result of the transform operation with the values in + # the pre-transformed points. Oracle does not have the 3084 SRID. + if not oracle: + h = City.objects.transform(htown.srid).get(name='Houston') + self.assertEqual(3084, h.point.srid) + self.assertAlmostEqual(htown.x, h.point.x, prec) + self.assertAlmostEqual(htown.y, h.point.y, prec) + + p1 = City.objects.transform(ptown.srid, field_name='point').get(name='Pueblo') + p2 = City.objects.transform(srid=ptown.srid).get(name='Pueblo') + for p in [p1, p2]: + self.assertEqual(2774, p.point.srid) + self.assertAlmostEqual(ptown.x, p.point.x, prec) + self.assertAlmostEqual(ptown.y, p.point.y, prec) + + @no_mysql + @no_oracle + def test_translate(self): + "Testing the `translate` GeoQuerySet method." + xfac, yfac = 5, -23 + qs = Country.objects.translate(xfac, yfac, model_att='translated') + for c in qs: + for p1, p2 in zip(c.mpoly, c.translated): + for r1, r2 in zip(p1, p2): + for c1, c2 in zip(r1.coords, r2.coords): + # XXX The low precision is for SpatiaLite + self.assertAlmostEqual(c1[0] + xfac, c2[0], 5) + self.assertAlmostEqual(c1[1] + yfac, c2[1], 5) + + @no_mysql + def test_unionagg(self): + "Testing the `unionagg` (aggregate union) GeoQuerySet method." + tx = Country.objects.get(name='Texas').mpoly + # Houston, Dallas -- Oracle has different order. + union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') + union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') + qs = City.objects.filter(point__within=tx) + self.assertRaises(TypeError, qs.unionagg, 'name') + # Using `field_name` keyword argument in one query and specifying an + # order in the other (which should not be used because this is + # an aggregate method on a spatial column) + u1 = qs.unionagg(field_name='point') + u2 = qs.order_by('name').unionagg() + tol = 0.00001 if oracle: - self.assertRaises(TypeError, State.objects.reverse_geom) - - @no_mysql - @no_oracle - @no_spatialite - def test29_force_rhr(self): - "Testing GeoQuerySet.force_rhr()." - rings = ( ( (0, 0), (5, 0), (0, 5), (0, 0) ), - ( (1, 1), (1, 3), (3, 1), (1, 1) ), - ) - rhr_rings = ( ( (0, 0), (0, 5), (5, 0), (0, 0) ), - ( (1, 1), (3, 1), (1, 3), (1, 1) ), - ) - State.objects.create(name='Foo', poly=Polygon(*rings)) - s = State.objects.force_rhr().get(name='Foo') - self.assertEqual(rhr_rings, s.force_rhr.coords) - - @no_mysql - @no_oracle - @no_spatialite - def test30_geohash(self): - "Testing GeoQuerySet.geohash()." - if not connection.ops.geohash: return - # Reference query: - # SELECT ST_GeoHash(point) FROM geoapp_city WHERE name='Houston'; - # SELECT ST_GeoHash(point, 5) FROM geoapp_city WHERE name='Houston'; - ref_hash = '9vk1mfq8jx0c8e0386z6' - h1 = City.objects.geohash().get(name='Houston') - h2 = City.objects.geohash(precision=5).get(name='Houston') - self.assertEqual(ref_hash, h1.geohash) - self.assertEqual(ref_hash[:5], h2.geohash) - -from .test_feeds import GeoFeedTest -from .test_regress import GeoRegressionTests -from .test_sitemaps import GeoSitemapTest + union = union2 + else: + union = union1 + self.assertEqual(True, union.equals_exact(u1, tol)) + self.assertEqual(True, union.equals_exact(u2, tol)) + qs = City.objects.filter(name='NotACity') + self.assertEqual(None, qs.unionagg(field_name='point')) diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index 8f6c2602c9..7e8f163174 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -5,7 +5,7 @@ from datetime import date, datetime from django import template from django.conf import settings from django.template import defaultfilters -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.formats import number_format from django.utils.translation import pgettext, ungettext, ugettext as _ from django.utils.timezone import is_aware, utc @@ -41,7 +41,7 @@ def intcomma(value, use_l10n=True): return intcomma(value, False) else: return number_format(value, force_grouping=True) - orig = force_unicode(value) + orig = force_text(value) new = re.sub("^(-?\d+)(\d{3})", '\g<1>,\g<2>', orig) if orig == new: return new diff --git a/django/contrib/localflavor/au/forms.py b/django/contrib/localflavor/au/forms.py index 34170fabc8..d3a00e200c 100644 --- a/django/contrib/localflavor/au/forms.py +++ b/django/contrib/localflavor/au/forms.py @@ -10,7 +10,7 @@ from django.contrib.localflavor.au.au_states import STATE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -44,7 +44,7 @@ class AUPhoneNumberField(Field): super(AUPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\(|\)|\s+|-)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+|-)', '', smart_text(value)) phone_match = PHONE_DIGITS_RE.search(value) if phone_match: return '%s' % phone_match.group(1) diff --git a/django/contrib/localflavor/br/forms.py b/django/contrib/localflavor/br/forms.py index f287d46a9a..0f957be37f 100644 --- a/django/contrib/localflavor/br/forms.py +++ b/django/contrib/localflavor/br/forms.py @@ -11,7 +11,7 @@ from django.contrib.localflavor.br.br_states import STATE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, CharField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -35,7 +35,7 @@ class BRPhoneNumberField(Field): super(BRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) @@ -68,10 +68,10 @@ class BRStateChoiceField(Field): value = super(BRStateChoiceField, self).clean(value) if value in EMPTY_VALUES: value = '' - value = smart_unicode(value) + value = smart_text(value) if value == '': return value - valid_values = set([smart_unicode(k) for k, v in self.widget.choices]) + valid_values = set([smart_text(k) for k, v in self.widget.choices]) if value not in valid_values: raise ValidationError(self.error_messages['invalid']) return value @@ -154,10 +154,10 @@ class BRCNPJField(Field): raise ValidationError(self.error_messages['max_digits']) orig_dv = value[-2:] - new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(5, 1, -1) + range(9, 1, -1))]) + new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(list(range(5, 1, -1)) + list(range(9, 1, -1)))]) new_1dv = DV_maker(new_1dv % 11) value = value[:-2] + str(new_1dv) + value[-1] - new_2dv = sum([i * int(value[idx]) for idx, i in enumerate(range(6, 1, -1) + range(9, 1, -1))]) + new_2dv = sum([i * int(value[idx]) for idx, i in enumerate(list(range(6, 1, -1)) + list(range(9, 1, -1)))]) new_2dv = DV_maker(new_2dv % 11) value = value[:-1] + str(new_2dv) if value[-2:] != orig_dv: diff --git a/django/contrib/localflavor/ca/forms.py b/django/contrib/localflavor/ca/forms.py index daa40044f9..4ebfb06c2b 100644 --- a/django/contrib/localflavor/ca/forms.py +++ b/django/contrib/localflavor/ca/forms.py @@ -9,7 +9,7 @@ import re from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, CharField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -53,7 +53,7 @@ class CAPhoneNumberField(Field): super(CAPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) diff --git a/django/contrib/localflavor/ch/forms.py b/django/contrib/localflavor/ch/forms.py index e844a3c57c..bf71eeea32 100644 --- a/django/contrib/localflavor/ch/forms.py +++ b/django/contrib/localflavor/ch/forms.py @@ -10,7 +10,7 @@ from django.contrib.localflavor.ch.ch_states import STATE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -41,7 +41,7 @@ class CHPhoneNumberField(Field): super(CHPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\.|\s|/|-)', '', smart_unicode(value)) + value = re.sub('(\.|\s|/|-)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s %s %s %s' % (value[0:3], value[3:6], value[6:8], value[8:10]) diff --git a/django/contrib/localflavor/cl/forms.py b/django/contrib/localflavor/cl/forms.py index 5991176382..a5340141ce 100644 --- a/django/contrib/localflavor/cl/forms.py +++ b/django/contrib/localflavor/cl/forms.py @@ -8,7 +8,7 @@ from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import RegexField, Select from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from .cl_regions import REGION_CHOICES @@ -75,7 +75,7 @@ class CLRutField(RegexField): Turns the RUT into one normalized format. Returns a (rut, verifier) tuple. """ - rut = smart_unicode(rut).replace(' ', '').replace('.', '').replace('-', '') + rut = smart_text(rut).replace(' ', '').replace('.', '').replace('-', '') return rut[:-1], rut[-1].upper() def _format(self, code, verifier=None): diff --git a/django/contrib/localflavor/fr/forms.py b/django/contrib/localflavor/fr/forms.py index d836dd6397..8b841fff5f 100644 --- a/django/contrib/localflavor/fr/forms.py +++ b/django/contrib/localflavor/fr/forms.py @@ -9,7 +9,7 @@ from django.contrib.localflavor.fr.fr_department import DEPARTMENT_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import CharField, RegexField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -43,7 +43,7 @@ class FRPhoneNumberField(CharField): super(FRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\.|\s)', '', smart_unicode(value)) + value = re.sub('(\.|\s)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s %s %s %s %s' % (value[0:2], value[2:4], value[4:6], value[6:8], value[8:10]) diff --git a/django/contrib/localflavor/hk/forms.py b/django/contrib/localflavor/hk/forms.py index 8cf9360e19..ab4f70f193 100644 --- a/django/contrib/localflavor/hk/forms.py +++ b/django/contrib/localflavor/hk/forms.py @@ -8,7 +8,7 @@ import re from django.core.validators import EMPTY_VALUES from django.forms import CharField from django.forms import ValidationError -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -53,7 +53,7 @@ class HKPhoneNumberField(CharField): if value in EMPTY_VALUES: return '' - value = re.sub('(\(|\)|\s+|\+)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+|\+)', '', smart_text(value)) m = hk_phone_digits_re.search(value) if not m: raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/hr/forms.py b/django/contrib/localflavor/hr/forms.py index eb4436a78c..b935fd8a3a 100644 --- a/django/contrib/localflavor/hr/forms.py +++ b/django/contrib/localflavor/hr/forms.py @@ -12,7 +12,7 @@ from django.contrib.localflavor.hr.hr_choices import ( from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, Select, RegexField -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -159,7 +159,7 @@ class HRLicensePlateField(Field): if value in EMPTY_VALUES: return '' - value = re.sub(r'[\s\-]+', '', smart_unicode(value.strip())).upper() + value = re.sub(r'[\s\-]+', '', smart_text(value.strip())).upper() matches = plate_re.search(value) if matches is None: @@ -225,7 +225,7 @@ class HRPhoneNumberField(Field): if value in EMPTY_VALUES: return '' - value = re.sub(r'[\-\s\(\)]', '', smart_unicode(value)) + value = re.sub(r'[\-\s\(\)]', '', smart_text(value)) matches = phone_re.search(value) if matches is None: diff --git a/django/contrib/localflavor/id/forms.py b/django/contrib/localflavor/id/forms.py index f22b06134e..2005dbc75c 100644 --- a/django/contrib/localflavor/id/forms.py +++ b/django/contrib/localflavor/id/forms.py @@ -11,7 +11,7 @@ from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, Select from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text postcode_re = re.compile(r'^[1-9]\d{4}$') @@ -77,10 +77,10 @@ class IDPhoneNumberField(Field): if value in EMPTY_VALUES: return '' - phone_number = re.sub(r'[\-\s\(\)]', '', smart_unicode(value)) + phone_number = re.sub(r'[\-\s\(\)]', '', smart_text(value)) if phone_re.search(phone_number): - return smart_unicode(value) + return smart_text(value) raise ValidationError(self.error_messages['invalid']) @@ -120,7 +120,7 @@ class IDLicensePlateField(Field): return '' plate_number = re.sub(r'\s+', ' ', - smart_unicode(value.strip())).upper() + smart_text(value.strip())).upper() matches = plate_re.search(plate_number) if matches is None: @@ -181,7 +181,7 @@ class IDNationalIdentityNumberField(Field): if value in EMPTY_VALUES: return '' - value = re.sub(r'[\s.]', '', smart_unicode(value)) + value = re.sub(r'[\s.]', '', smart_text(value)) if not nik_re.search(value): raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/in_/forms.py b/django/contrib/localflavor/in_/forms.py index b62ec7bdb2..5c1d009ef4 100644 --- a/django/contrib/localflavor/in_/forms.py +++ b/django/contrib/localflavor/in_/forms.py @@ -10,7 +10,7 @@ from django.contrib.localflavor.in_.in_states import STATES_NORMALIZED, STATE_CH from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, CharField, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -74,7 +74,7 @@ class INStateField(Field): pass else: try: - return smart_unicode(STATES_NORMALIZED[value.strip().lower()]) + return smart_text(STATES_NORMALIZED[value.strip().lower()]) except KeyError: pass raise ValidationError(self.error_messages['invalid']) @@ -107,7 +107,7 @@ class INPhoneNumberField(CharField): super(INPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = smart_unicode(value) + value = smart_text(value) m = phone_digits_re.match(value) if m: return '%s' % (value) diff --git a/django/contrib/localflavor/is_/forms.py b/django/contrib/localflavor/is_/forms.py index 7af9f51cfb..1ae3e012a1 100644 --- a/django/contrib/localflavor/is_/forms.py +++ b/django/contrib/localflavor/is_/forms.py @@ -9,7 +9,7 @@ from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import RegexField from django.forms.widgets import Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -58,7 +58,7 @@ class ISIdNumberField(RegexField): Takes in the value in canonical form and returns it in the common display format. """ - return smart_unicode(value[:6]+'-'+value[6:]) + return smart_text(value[:6]+'-'+value[6:]) class ISPhoneNumberField(RegexField): """ diff --git a/django/contrib/localflavor/it/forms.py b/django/contrib/localflavor/it/forms.py index 60b1eff951..916ce9bb3d 100644 --- a/django/contrib/localflavor/it/forms.py +++ b/django/contrib/localflavor/it/forms.py @@ -13,7 +13,7 @@ from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text class ITZipCodeField(RegexField): @@ -85,4 +85,4 @@ class ITVatNumberField(Field): check_digit = vat_number_check_digit(vat_number[0:10]) if not vat_number[10] == check_digit: raise ValidationError(self.error_messages['invalid']) - return smart_unicode(vat_number) + return smart_text(vat_number) diff --git a/django/contrib/localflavor/it/util.py b/django/contrib/localflavor/it/util.py index ec1b7e3f83..e1aa9c0419 100644 --- a/django/contrib/localflavor/it/util.py +++ b/django/contrib/localflavor/it/util.py @@ -1,4 +1,4 @@ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text def ssn_check_digit(value): "Calculate Italian social security number check digit." @@ -34,11 +34,11 @@ def ssn_check_digit(value): def vat_number_check_digit(vat_number): "Calculate Italian VAT number check digit." - normalized_vat_number = smart_unicode(vat_number).zfill(10) + normalized_vat_number = smart_text(vat_number).zfill(10) total = 0 for i in range(0, 10, 2): total += int(normalized_vat_number[i]) for i in range(1, 11, 2): quotient , remainder = divmod(int(normalized_vat_number[i]) * 2, 10) total += quotient + remainder - return smart_unicode((10 - total % 10) % 10) + return smart_text((10 - total % 10) % 10) diff --git a/django/contrib/localflavor/nl/forms.py b/django/contrib/localflavor/nl/forms.py index bdd769bd39..a05dd38f7f 100644 --- a/django/contrib/localflavor/nl/forms.py +++ b/django/contrib/localflavor/nl/forms.py @@ -10,7 +10,7 @@ from django.contrib.localflavor.nl.nl_provinces import PROVINCE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, Select -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -61,7 +61,7 @@ class NLPhoneNumberField(Field): if value in EMPTY_VALUES: return '' - phone_nr = re.sub('[\-\s\(\)]', '', smart_unicode(value)) + phone_nr = re.sub('[\-\s\(\)]', '', smart_text(value)) if len(phone_nr) == 10 and numeric_re.search(phone_nr): return value diff --git a/django/contrib/localflavor/pt/forms.py b/django/contrib/localflavor/pt/forms.py index b27fb577bd..01cdd101b2 100644 --- a/django/contrib/localflavor/pt/forms.py +++ b/django/contrib/localflavor/pt/forms.py @@ -8,7 +8,7 @@ import re from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ phone_digits_re = re.compile(r'^(\d{9}|(00|\+)\d*)$') @@ -29,7 +29,7 @@ class PTZipCodeField(RegexField): return '%s-%s' % (cleaned[:4],cleaned[4:]) else: return cleaned - + class PTPhoneNumberField(Field): """ Validate local Portuguese phone number (including international ones) @@ -43,7 +43,7 @@ class PTPhoneNumberField(Field): super(PTPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\.|\s)', '', smart_unicode(value)) + value = re.sub('(\.|\s)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s' % value diff --git a/django/contrib/localflavor/si/forms.py b/django/contrib/localflavor/si/forms.py index aa4b9dac5a..bab35935fd 100644 --- a/django/contrib/localflavor/si/forms.py +++ b/django/contrib/localflavor/si/forms.py @@ -41,7 +41,7 @@ class SIEMSOField(CharField): # Validate EMSO s = 0 int_values = [int(i) for i in value] - for a, b in zip(int_values, range(7, 1, -1) * 2): + for a, b in zip(int_values, list(range(7, 1, -1)) * 2): s += a * b chk = s % 11 if chk == 0: diff --git a/django/contrib/localflavor/tr/forms.py b/django/contrib/localflavor/tr/forms.py index 15bc1f99bb..c4f928e670 100644 --- a/django/contrib/localflavor/tr/forms.py +++ b/django/contrib/localflavor/tr/forms.py @@ -10,7 +10,7 @@ from django.contrib.localflavor.tr.tr_provinces import PROVINCE_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select, CharField -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -46,7 +46,7 @@ class TRPhoneNumberField(CharField): super(TRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s%s' % (m.group(2), m.group(4)) diff --git a/django/contrib/localflavor/us/forms.py b/django/contrib/localflavor/us/forms.py index ef565163cd..437bb7c466 100644 --- a/django/contrib/localflavor/us/forms.py +++ b/django/contrib/localflavor/us/forms.py @@ -9,7 +9,7 @@ import re from django.core.validators import EMPTY_VALUES from django.forms import ValidationError from django.forms.fields import Field, RegexField, Select, CharField -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -34,7 +34,7 @@ class USPhoneNumberField(CharField): super(USPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + value = re.sub('(\(|\)|\s+)', '', smart_text(value)) m = phone_digits_re.search(value) if m: return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py index 84251cf30a..af9c842f42 100644 --- a/django/contrib/markup/templatetags/markup.py +++ b/django/contrib/markup/templatetags/markup.py @@ -13,7 +13,7 @@ markup syntaxes to HTML; currently there is support for: from django import template from django.conf import settings -from django.utils.encoding import smart_str, force_unicode +from django.utils.encoding import smart_bytes, force_text from django.utils.safestring import mark_safe register = template.Library() @@ -25,9 +25,9 @@ def textile(value): except ImportError: if settings.DEBUG: raise template.TemplateSyntaxError("Error in 'textile' filter: The Python textile library isn't installed.") - return force_unicode(value) + return force_text(value) else: - return mark_safe(force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8'))) + return mark_safe(force_text(textile.textile(smart_bytes(value), encoding='utf-8', output='utf-8'))) @register.filter(is_safe=True) def markdown(value, arg=''): @@ -52,23 +52,23 @@ def markdown(value, arg=''): except ImportError: if settings.DEBUG: raise template.TemplateSyntaxError("Error in 'markdown' filter: The Python markdown library isn't installed.") - return force_unicode(value) + return force_text(value) else: markdown_vers = getattr(markdown, "version_info", 0) if markdown_vers < (2, 1): if settings.DEBUG: raise template.TemplateSyntaxError( "Error in 'markdown' filter: Django does not support versions of the Python markdown library < 2.1.") - return force_unicode(value) + return force_text(value) else: extensions = [e for e in arg.split(",") if e] if extensions and extensions[0] == "safe": extensions = extensions[1:] return mark_safe(markdown.markdown( - force_unicode(value), extensions, safe_mode=True, enable_attributes=False)) + force_text(value), extensions, safe_mode=True, enable_attributes=False)) else: return mark_safe(markdown.markdown( - force_unicode(value), extensions, safe_mode=False)) + force_text(value), extensions, safe_mode=False)) @register.filter(is_safe=True) def restructuredtext(value): @@ -77,8 +77,8 @@ def restructuredtext(value): except ImportError: if settings.DEBUG: raise template.TemplateSyntaxError("Error in 'restructuredtext' filter: The Python docutils library isn't installed.") - return force_unicode(value) + return force_text(value) else: docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {}) - parts = publish_parts(source=smart_str(value), writer_name="html4css1", settings_overrides=docutils_settings) - return mark_safe(force_unicode(parts["fragment"])) + parts = publish_parts(source=smart_bytes(value), writer_name="html4css1", settings_overrides=docutils_settings) + return mark_safe(force_text(parts["fragment"])) diff --git a/django/contrib/messages/storage/base.py b/django/contrib/messages/storage/base.py index e80818e84e..5433bbff28 100644 --- a/django/contrib/messages/storage/base.py +++ b/django/contrib/messages/storage/base.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from django.conf import settings -from django.utils.encoding import force_unicode, StrAndUnicode +from django.utils.encoding import force_text, StrAndUnicode from django.contrib.messages import constants, utils @@ -26,22 +26,22 @@ class Message(StrAndUnicode): and ``extra_tags`` to unicode in case they are lazy translations. Known "safe" types (None, int, etc.) are not converted (see Django's - ``force_unicode`` implementation for details). + ``force_text`` implementation for details). """ - self.message = force_unicode(self.message, strings_only=True) - self.extra_tags = force_unicode(self.extra_tags, strings_only=True) + self.message = force_text(self.message, strings_only=True) + self.extra_tags = force_text(self.extra_tags, strings_only=True) def __eq__(self, other): return isinstance(other, Message) and self.level == other.level and \ self.message == other.message def __unicode__(self): - return force_unicode(self.message) + return force_text(self.message) def _get_tags(self): - label_tag = force_unicode(LEVEL_TAGS.get(self.level, ''), + label_tag = force_text(LEVEL_TAGS.get(self.level, ''), strings_only=True) - extra_tags = force_unicode(self.extra_tags, strings_only=True) + extra_tags = force_text(self.extra_tags, strings_only=True) if extra_tags and label_tag: return ' '.join([extra_tags, label_tag]) elif extra_tags: diff --git a/django/contrib/messages/storage/cookie.py b/django/contrib/messages/storage/cookie.py index 07620050c7..5f64ccd0c5 100644 --- a/django/contrib/messages/storage/cookie.py +++ b/django/contrib/messages/storage/cookie.py @@ -4,6 +4,7 @@ from django.conf import settings from django.contrib.messages.storage.base import BaseStorage, Message from django.http import SimpleCookie from django.utils.crypto import salted_hmac, constant_time_compare +from django.utils import six class MessageEncoder(json.JSONEncoder): @@ -33,7 +34,7 @@ class MessageDecoder(json.JSONDecoder): return [self.process_messages(item) for item in obj] if isinstance(obj, dict): return dict([(key, self.process_messages(value)) - for key, value in obj.iteritems()]) + for key, value in six.iteritems(obj)]) return obj def decode(self, s, **kwargs): diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py index 3dd0d9516c..0cc17b44c3 100644 --- a/django/contrib/sessions/backends/db.py +++ b/django/contrib/sessions/backends/db.py @@ -1,7 +1,7 @@ from django.contrib.sessions.backends.base import SessionBase, CreateError from django.core.exceptions import SuspiciousOperation from django.db import IntegrityError, transaction, router -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils import timezone @@ -18,7 +18,7 @@ class SessionStore(SessionBase): session_key = self.session_key, expire_date__gt=timezone.now() ) - return self.decode(force_unicode(s.session_data)) + return self.decode(force_text(s.session_data)) except (Session.DoesNotExist, SuspiciousOperation): self.create() return {} diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 328b085f1e..7de2941122 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -16,6 +16,7 @@ from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.http import HttpResponse from django.test import TestCase, RequestFactory from django.test.utils import override_settings +from django.utils import six from django.utils import timezone from django.utils import unittest @@ -86,16 +87,16 @@ class SessionTestsMixin(object): self.assertFalse(self.session.modified) def test_values(self): - self.assertEqual(self.session.values(), []) + self.assertEqual(list(self.session.values()), []) self.assertTrue(self.session.accessed) self.session['some key'] = 1 - self.assertEqual(self.session.values(), [1]) + self.assertEqual(list(self.session.values()), [1]) def test_iterkeys(self): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - i = self.session.iterkeys() + i = six.iterkeys(self.session) self.assertTrue(hasattr(i, '__iter__')) self.assertTrue(self.session.accessed) self.assertFalse(self.session.modified) @@ -105,7 +106,7 @@ class SessionTestsMixin(object): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - i = self.session.itervalues() + i = six.itervalues(self.session) self.assertTrue(hasattr(i, '__iter__')) self.assertTrue(self.session.accessed) self.assertFalse(self.session.modified) @@ -115,7 +116,7 @@ class SessionTestsMixin(object): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - i = self.session.iteritems() + i = six.iteritems(self.session) self.assertTrue(hasattr(i, '__iter__')) self.assertTrue(self.session.accessed) self.assertFalse(self.session.modified) @@ -125,9 +126,9 @@ class SessionTestsMixin(object): self.session['x'] = 1 self.session.modified = False self.session.accessed = False - self.assertEqual(self.session.items(), [('x', 1)]) + self.assertEqual(list(self.session.items()), [('x', 1)]) self.session.clear() - self.assertEqual(self.session.items(), []) + self.assertEqual(list(self.session.items()), []) self.assertTrue(self.session.accessed) self.assertTrue(self.session.modified) @@ -154,10 +155,10 @@ class SessionTestsMixin(object): self.session['a'], self.session['b'] = 'c', 'd' self.session.save() prev_key = self.session.session_key - prev_data = self.session.items() + prev_data = list(self.session.items()) self.session.cycle_key() self.assertNotEqual(self.session.session_key, prev_key) - self.assertEqual(self.session.items(), prev_data) + self.assertEqual(list(self.session.items()), prev_data) def test_invalid_key(self): # Submitting an invalid session key (either by guessing, or if the db has diff --git a/django/contrib/sitemaps/views.py b/django/contrib/sitemaps/views.py index b90a39e954..cfe3aa66a9 100644 --- a/django/contrib/sitemaps/views.py +++ b/django/contrib/sitemaps/views.py @@ -3,6 +3,7 @@ from django.core import urlresolvers from django.core.paginator import EmptyPage, PageNotAnInteger from django.http import Http404 from django.template.response import TemplateResponse +from django.utils import six def index(request, sitemaps, template_name='sitemap_index.xml', mimetype='application/xml', @@ -35,7 +36,7 @@ def sitemap(request, sitemaps, section=None, raise Http404("No sitemap available for section: %r" % section) maps = [sitemaps[section]] else: - maps = sitemaps.values() + maps = list(six.itervalues(sitemaps)) page = request.GET.get("p", 1) urls = [] diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py index 766687cf7d..9b06c2cf60 100644 --- a/django/contrib/staticfiles/finders.py +++ b/django/contrib/staticfiles/finders.py @@ -6,6 +6,7 @@ from django.utils.datastructures import SortedDict from django.utils.functional import empty, memoize, LazyObject from django.utils.importlib import import_module from django.utils._os import safe_join +from django.utils import six from django.contrib.staticfiles import utils from django.contrib.staticfiles.storage import AppStaticStorage @@ -132,7 +133,7 @@ class AppDirectoriesFinder(BaseFinder): """ List all files in all app storages. """ - for storage in self.storages.itervalues(): + for storage in six.itervalues(self.storages): if storage.exists(''): # check if storage location exists for path in utils.get_files(storage, ignore_patterns): yield path, storage diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index d3977213a9..7dac0ffb4c 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -6,8 +6,9 @@ from optparse import make_option from django.core.files.storage import FileSystemStorage from django.core.management.base import CommandError, NoArgsCommand -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.datastructures import SortedDict +from django.utils.six.moves import input from django.contrib.staticfiles import finders, storage @@ -148,7 +149,7 @@ class Command(NoArgsCommand): clear_display = 'This will overwrite existing files!' if self.interactive: - confirm = raw_input(""" + confirm = input(""" You have requested to collect static files at the destination location as specified in your settings%s @@ -198,9 +199,9 @@ Type 'yes' to continue, or 'no' to cancel: """ fpath = os.path.join(path, f) if self.dry_run: self.log("Pretending to delete '%s'" % - smart_unicode(fpath), level=1) + smart_text(fpath), level=1) else: - self.log("Deleting '%s'" % smart_unicode(fpath), level=1) + self.log("Deleting '%s'" % smart_text(fpath), level=1) self.storage.delete(fpath) for d in dirs: self.clear_dir(os.path.join(path, d)) diff --git a/django/contrib/staticfiles/management/commands/findstatic.py b/django/contrib/staticfiles/management/commands/findstatic.py index 772220b342..dc1e88d778 100644 --- a/django/contrib/staticfiles/management/commands/findstatic.py +++ b/django/contrib/staticfiles/management/commands/findstatic.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import os from optparse import make_option from django.core.management.base import LabelCommand -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.contrib.staticfiles import finders @@ -19,12 +19,12 @@ class Command(LabelCommand): def handle_label(self, path, **options): verbosity = int(options.get('verbosity', 1)) result = finders.find(path, all=options['all']) - path = smart_unicode(path) + path = smart_text(path) if result: if not isinstance(result, (list, tuple)): result = [result] output = '\n '.join( - (smart_unicode(os.path.realpath(path)) for path in result)) + (smart_text(os.path.realpath(path)) for path in result)) self.stdout.write("Found '%s' here:\n %s" % (path, output)) else: if verbosity >= 1: diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index a0133e1c6a..2ca54dde71 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -16,7 +16,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.files.base import ContentFile from django.core.files.storage import FileSystemStorage, get_storage_class from django.utils.datastructures import SortedDict -from django.utils.encoding import force_unicode, smart_str +from django.utils.encoding import force_text, smart_bytes from django.utils.functional import LazyObject from django.utils.importlib import import_module @@ -112,7 +112,7 @@ class CachedFilesMixin(object): return urlunsplit(unparsed_name) def cache_key(self, name): - return 'staticfiles:%s' % hashlib.md5(smart_str(name)).hexdigest() + return 'staticfiles:%s' % hashlib.md5(smart_bytes(name)).hexdigest() def url(self, name, force=False): """ @@ -248,9 +248,9 @@ class CachedFilesMixin(object): if hashed_file_exists: self.delete(hashed_name) # then save the processed result - content_file = ContentFile(smart_str(content)) + content_file = ContentFile(smart_bytes(content)) saved_name = self._save(hashed_name, content_file) - hashed_name = force_unicode(saved_name.replace('\\', '/')) + hashed_name = force_text(saved_name.replace('\\', '/')) processed = True else: # or handle the case in which neither processing nor @@ -258,7 +258,7 @@ class CachedFilesMixin(object): if not hashed_file_exists: processed = True saved_name = self._save(hashed_name, original_file) - hashed_name = force_unicode(saved_name.replace('\\', '/')) + hashed_name = force_text(saved_name.replace('\\', '/')) # and then set the cache accordingly hashed_paths[self.cache_key(name)] = hashed_name diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py index 3c84f1f60c..bce7ef7cfb 100644 --- a/django/contrib/syndication/views.py +++ b/django/contrib/syndication/views.py @@ -6,7 +6,7 @@ from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.http import HttpResponse, Http404 from django.template import loader, TemplateDoesNotExist, RequestContext from django.utils import feedgenerator, tzinfo -from django.utils.encoding import force_unicode, iri_to_uri, smart_unicode +from django.utils.encoding import force_text, iri_to_uri, smart_text from django.utils.html import escape from django.utils.timezone import is_naive @@ -43,10 +43,10 @@ class Feed(object): def item_title(self, item): # Titles should be double escaped by default (see #6533) - return escape(force_unicode(item)) + return escape(force_text(item)) def item_description(self, item): - return force_unicode(item) + return force_text(item) def item_link(self, item): try: @@ -154,9 +154,9 @@ class Feed(object): enc_url = self.__get_dynamic_attr('item_enclosure_url', item) if enc_url: enc = feedgenerator.Enclosure( - url = smart_unicode(enc_url), - length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)), - mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item)) + url = smart_text(enc_url), + length = smart_text(self.__get_dynamic_attr('item_enclosure_length', item)), + mime_type = smart_text(self.__get_dynamic_attr('item_enclosure_mime_type', item)) ) author_name = self.__get_dynamic_attr('item_author_name', item) if author_name is not None: diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py index f7573b2e31..d527e44d8b 100644 --- a/django/core/cache/backends/base.py +++ b/django/core/cache/backends/base.py @@ -3,7 +3,7 @@ import warnings from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes from django.utils.importlib import import_module class InvalidCacheBackendError(ImproperlyConfigured): @@ -23,7 +23,7 @@ def default_key_func(key, key_prefix, version): the `key_prefix'. KEY_FUNCTION can be used to specify an alternate function with custom key making behavior. """ - return ':'.join([key_prefix, str(version), smart_str(key)]) + return ':'.join([key_prefix, str(version), smart_bytes(key)]) def get_key_func(key_func): """ @@ -62,7 +62,7 @@ class BaseCache(object): except (ValueError, TypeError): self._cull_frequency = 3 - self.key_prefix = smart_str(params.get('KEY_PREFIX', '')) + self.key_prefix = smart_bytes(params.get('KEY_PREFIX', '')) self.version = params.get('VERSION', 1) self.key_func = get_key_func(params.get('KEY_FUNCTION', None)) diff --git a/django/core/context_processors.py b/django/core/context_processors.py index 325f64d224..a503270cf4 100644 --- a/django/core/context_processors.py +++ b/django/core/context_processors.py @@ -9,7 +9,7 @@ RequestContext. from django.conf import settings from django.middleware.csrf import get_token -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes from django.utils.functional import lazy def csrf(request): @@ -25,7 +25,7 @@ def csrf(request): # instead of returning an empty dict. return b'NOTPROVIDED' else: - return smart_str(token) + return smart_bytes(token) _get_val = lazy(_get_val, str) return {'csrf_token': _get_val() } diff --git a/django/core/exceptions.py b/django/core/exceptions.py index e3d1dc9c7e..f0f14cffda 100644 --- a/django/core/exceptions.py +++ b/django/core/exceptions.py @@ -43,7 +43,7 @@ class ValidationError(Exception): """An error while validating data.""" def __init__(self, message, code=None, params=None): import operator - from django.utils.encoding import force_unicode + from django.utils.encoding import force_text """ ValidationError can be passed any object that can be printed (usually a string), a list of objects or a dictionary. @@ -54,11 +54,11 @@ class ValidationError(Exception): message = reduce(operator.add, message.values()) if isinstance(message, list): - self.messages = [force_unicode(msg) for msg in message] + self.messages = [force_text(msg) for msg in message] else: self.code = code self.params = params - message = force_unicode(message) + message = force_text(message) self.messages = [message] def __str__(self): diff --git a/django/core/files/base.py b/django/core/files/base.py index 04853fad0c..d0b25250a5 100644 --- a/django/core/files/base.py +++ b/django/core/files/base.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import os from io import BytesIO -from django.utils.encoding import smart_str, smart_unicode +from django.utils.encoding import smart_bytes, smart_text from django.core.files.utils import FileProxyMixin class File(FileProxyMixin): @@ -18,16 +18,17 @@ class File(FileProxyMixin): self.mode = file.mode def __str__(self): - return smart_str(self.name or '') + return smart_bytes(self.name or '') def __unicode__(self): - return smart_unicode(self.name or '') + return smart_text(self.name or '') def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self or "None") - def __nonzero__(self): + def __bool__(self): return bool(self.name) + __nonzero__ = __bool__ # Python 2 def __len__(self): return self.size @@ -135,8 +136,9 @@ class ContentFile(File): def __str__(self): return 'Raw content' - def __nonzero__(self): + def __bool__(self): return True + __nonzero__ = __bool__ # Python 2 def open(self, mode=None): self.seek(0) diff --git a/django/core/files/images.py b/django/core/files/images.py index 228a7118c5..7d7eac65db 100644 --- a/django/core/files/images.py +++ b/django/core/files/images.py @@ -47,13 +47,18 @@ def get_image_dimensions(file_or_path, close=False): file = open(file_or_path, 'rb') close = True try: + # Most of the time PIL only needs a small chunk to parse the image and + # get the dimensions, but with some TIFF files PIL needs to parse the + # whole file. + chunk_size = 1024 while 1: - data = file.read(1024) + data = file.read(chunk_size) if not data: break p.feed(data) if p.image: return p.image.size + chunk_size = chunk_size*2 return None finally: if close: diff --git a/django/core/files/storage.py b/django/core/files/storage.py index 5179980513..7542dcda46 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -11,7 +11,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.core.files import locks, File from django.core.files.move import file_move_safe -from django.utils.encoding import force_unicode, filepath_to_uri +from django.utils.encoding import force_text, filepath_to_uri from django.utils.functional import LazyObject from django.utils.importlib import import_module from django.utils.text import get_valid_filename @@ -48,7 +48,7 @@ class Storage(object): name = self._save(name, content) # Store filenames with forward slashes, even on Windows - return force_unicode(name.replace('\\', '/')) + return force_text(name.replace('\\', '/')) # These methods are part of the public API, with default implementations. diff --git a/django/core/files/uploadedfile.py b/django/core/files/uploadedfile.py index 97d53482e4..3a6c632975 100644 --- a/django/core/files/uploadedfile.py +++ b/django/core/files/uploadedfile.py @@ -8,7 +8,7 @@ from io import BytesIO from django.conf import settings from django.core.files.base import File from django.core.files import temp as tempfile -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes __all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile', 'SimpleUploadedFile') @@ -30,7 +30,7 @@ class UploadedFile(File): self.charset = charset def __repr__(self): - return smart_str("<%s: %s (%s)>" % ( + return smart_bytes("<%s: %s (%s)>" % ( self.__class__.__name__, self.name, self.content_type)) def _get_name(self): diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 7fd7d19c4a..5a6825f0a7 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -4,7 +4,7 @@ import sys from django import http from django.core import signals -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.importlib import import_module from django.utils.log import getLogger from django.utils import six @@ -250,7 +250,7 @@ def get_script_name(environ): """ from django.conf import settings if settings.FORCE_SCRIPT_NAME is not None: - return force_unicode(settings.FORCE_SCRIPT_NAME) + return force_text(settings.FORCE_SCRIPT_NAME) # If Apache's mod_rewrite had a whack at the URL, Apache set either # SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any @@ -261,5 +261,5 @@ def get_script_name(environ): if not script_url: script_url = environ.get('REDIRECT_URL', '') if script_url: - return force_unicode(script_url[:-len(environ.get('PATH_INFO', ''))]) - return force_unicode(environ.get('SCRIPT_NAME', '')) + return force_text(script_url[:-len(environ.get('PATH_INFO', ''))]) + return force_text(environ.get('SCRIPT_NAME', '')) diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 617068a21c..70b23f8515 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -9,7 +9,7 @@ from django.core import signals from django.core.handlers import base from django.core.urlresolvers import set_script_prefix from django.utils import datastructures -from django.utils.encoding import force_unicode, smart_str, iri_to_uri +from django.utils.encoding import force_text, smart_bytes, iri_to_uri from django.utils.log import getLogger logger = getLogger('django.request') @@ -127,7 +127,7 @@ class LimitedStream(object): class WSGIRequest(http.HttpRequest): def __init__(self, environ): script_name = base.get_script_name(environ) - path_info = force_unicode(environ.get('PATH_INFO', '/')) + path_info = force_text(environ.get('PATH_INFO', '/')) if not path_info or path_info == script_name: # Sometimes PATH_INFO exists, but is empty (e.g. accessing # the SCRIPT_NAME URL without a trailing slash). We really need to @@ -245,6 +245,6 @@ class WSGIHandler(base.BaseHandler): status = '%s %s' % (response.status_code, status_text) response_headers = [(str(k), str(v)) for k, v in response.items()] for c in response.cookies.values(): - response_headers.append((b'Set-Cookie', str(c.output(header='')))) - start_response(smart_str(status), response_headers) + response_headers.append((str('Set-Cookie'), str(c.output(header='')))) + start_response(smart_bytes(status), response_headers) return response diff --git a/django/core/mail/message.py b/django/core/mail/message.py index 629ad464f9..b332ffba04 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -11,11 +11,10 @@ from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase from email.header import Header from email.utils import formatdate, getaddresses, formataddr, parseaddr -from io import BytesIO from django.conf import settings from django.core.mail.utils import DNS_NAME -from django.utils.encoding import smart_str, force_unicode +from django.utils.encoding import force_text from django.utils import six @@ -79,38 +78,38 @@ ADDRESS_HEADERS = set([ def forbid_multi_line_headers(name, val, encoding): """Forbids multi-line headers, to prevent header injection.""" encoding = encoding or settings.DEFAULT_CHARSET - val = force_unicode(val) + val = force_text(val) if '\n' in val or '\r' in val: raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) try: - val = val.encode('ascii') + val.encode('ascii') except UnicodeEncodeError: if name.lower() in ADDRESS_HEADERS: val = ', '.join(sanitize_address(addr, encoding) for addr in getaddresses((val,))) else: - val = str(Header(val, encoding)) + val = Header(val, encoding).encode() else: if name.lower() == 'subject': - val = Header(val) - return smart_str(name), val + val = Header(val).encode() + return str(name), val def sanitize_address(addr, encoding): if isinstance(addr, six.string_types): - addr = parseaddr(force_unicode(addr)) + addr = parseaddr(force_text(addr)) nm, addr = addr - nm = str(Header(nm, encoding)) + nm = Header(nm, encoding).encode() try: - addr = addr.encode('ascii') + addr.encode('ascii') except UnicodeEncodeError: # IDN if '@' in addr: localpart, domain = addr.split('@', 1) localpart = str(Header(localpart, encoding)) - domain = domain.encode('idna') + domain = domain.encode('idna').decode('ascii') addr = '@'.join([localpart, domain]) else: - addr = str(Header(addr, encoding)) + addr = Header(addr, encoding).encode() return formataddr((nm, addr)) @@ -132,7 +131,7 @@ class SafeMIMEText(MIMEText): This overrides the default as_string() implementation to not mangle lines that begin with 'From '. See bug #13433 for details. """ - fp = BytesIO() + fp = six.StringIO() g = Generator(fp, mangle_from_ = False) g.flatten(self, unixfrom=unixfrom) return fp.getvalue() @@ -156,7 +155,7 @@ class SafeMIMEMultipart(MIMEMultipart): This overrides the default as_string() implementation to not mangle lines that begin with 'From '. See bug #13433 for details. """ - fp = BytesIO() + fp = six.StringIO() g = Generator(fp, mangle_from_ = False) g.flatten(self, unixfrom=unixfrom) return fp.getvalue() @@ -210,8 +209,7 @@ class EmailMessage(object): def message(self): encoding = self.encoding or settings.DEFAULT_CHARSET - msg = SafeMIMEText(smart_str(self.body, encoding), - self.content_subtype, encoding) + msg = SafeMIMEText(self.body, self.content_subtype, encoding) msg = self._create_message(msg) msg['Subject'] = self.subject msg['From'] = self.extra_headers.get('From', self.from_email) @@ -293,7 +291,7 @@ class EmailMessage(object): basetype, subtype = mimetype.split('/', 1) if basetype == 'text': encoding = self.encoding or settings.DEFAULT_CHARSET - attachment = SafeMIMEText(smart_str(content, encoding), subtype, encoding) + attachment = SafeMIMEText(content, subtype, encoding) else: # Encode non-text attachments with base64. attachment = MIMEBase(basetype, subtype) @@ -313,9 +311,11 @@ class EmailMessage(object): attachment = self._create_mime_attachment(content, mimetype) if filename: try: - filename = filename.encode('ascii') + filename.encode('ascii') except UnicodeEncodeError: - filename = ('utf-8', '', filename.encode('utf-8')) + if not six.PY3: + filename = filename.encode('utf-8') + filename = ('utf-8', '', filename) attachment.add_header('Content-Disposition', 'attachment', filename=filename) return attachment diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 68048e5672..268cd63c38 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -8,6 +8,7 @@ import warnings from django.core.management.base import BaseCommand, CommandError, handle_default_options from django.core.management.color import color_style from django.utils.importlib import import_module +from django.utils import six # For backwards compatibility: get_version() used to be in this module. from django import get_version @@ -228,7 +229,7 @@ class ManagementUtility(object): "Available subcommands:", ] commands_dict = collections.defaultdict(lambda: []) - for name, app in get_commands().iteritems(): + for name, app in six.iteritems(get_commands()): if app == 'django.core': app = 'django' else: @@ -294,7 +295,7 @@ class ManagementUtility(object): except IndexError: curr = '' - subcommands = get_commands().keys() + ['help'] + subcommands = list(get_commands()) + ['help'] options = [('--help', None)] # subcommand diff --git a/django/core/management/base.py b/django/core/management/base.py index a204f6f0bc..5e630d5207 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -6,7 +6,6 @@ be executed through ``django-admin.py`` or ``manage.py``). import os import sys -from io import BytesIO from optparse import make_option, OptionParser import traceback @@ -14,6 +13,7 @@ import django from django.core.exceptions import ImproperlyConfigured from django.core.management.color import color_style from django.utils.encoding import smart_str +from django.utils.six import StringIO class CommandError(Exception): @@ -273,7 +273,7 @@ class BaseCommand(object): """ from django.core.management.validation import get_validation_errors - s = BytesIO() + s = StringIO() num_errors = get_validation_errors(s, app) if num_errors: s.seek(0) diff --git a/django/core/management/commands/compilemessages.py b/django/core/management/commands/compilemessages.py index fdc3535cf6..b7392b9173 100644 --- a/django/core/management/commands/compilemessages.py +++ b/django/core/management/commands/compilemessages.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import codecs import os import sys @@ -7,7 +9,7 @@ from django.core.management.base import BaseCommand, CommandError def has_bom(fn): with open(fn, 'rb') as f: sample = f.read(4) - return sample[:3] == '\xef\xbb\xbf' or \ + return sample[:3] == b'\xef\xbb\xbf' or \ sample.startswith(codecs.BOM_UTF16_LE) or \ sample.startswith(codecs.BOM_UTF16_BE) diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index fd6dbbbd2c..411042ee76 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -4,7 +4,7 @@ from django.core.cache.backends.db import BaseDatabaseCache from django.core.management.base import LabelCommand, CommandError from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS from django.db.utils import DatabaseError -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text class Command(LabelCommand): @@ -60,7 +60,7 @@ class Command(LabelCommand): transaction.rollback_unless_managed(using=db) raise CommandError( "Cache table '%s' could not be created.\nThe error was: %s." % - (tablename, force_unicode(e))) + (tablename, force_text(e))) for statement in index_output: curs.execute(statement) transaction.commit_unless_managed(using=db) diff --git a/django/core/management/commands/diffsettings.py b/django/core/management/commands/diffsettings.py index 98b53b405d..aa7395e5ee 100644 --- a/django/core/management/commands/diffsettings.py +++ b/django/core/management/commands/diffsettings.py @@ -22,9 +22,7 @@ class Command(NoArgsCommand): default_settings = module_to_dict(global_settings) output = [] - keys = user_settings.keys() - keys.sort() - for key in keys: + for key in sorted(user_settings.keys()): if key not in default_settings: output.append("%s = %s ###" % (key, user_settings[key])) elif user_settings[key] != default_settings[key]: diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index ac7b7a3599..b8b78434ce 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -7,6 +7,7 @@ from django.core.management.base import NoArgsCommand, CommandError from django.core.management.color import no_style from django.core.management.sql import sql_flush, emit_post_sync_signal from django.utils.importlib import import_module +from django.utils.six.moves import input class Command(NoArgsCommand): @@ -45,7 +46,7 @@ class Command(NoArgsCommand): sql_list = sql_flush(self.style, connection, only_django=True, reset_sequences=reset_sequences) if interactive: - confirm = raw_input("""You have requested a flush of the database. + confirm = input("""You have requested a flush of the database. This will IRREVERSIBLY DESTROY all data currently in the %r database, and return each table to the state it was in after syncdb. Are you sure you want to do this? diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 34f8041d33..1896e53cee 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -14,7 +14,7 @@ from django.core.management.color import no_style from django.db import (connections, router, transaction, DEFAULT_DB_ALIAS, IntegrityError, DatabaseError) from django.db.models import get_apps -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from itertools import product try: @@ -189,7 +189,7 @@ class Command(BaseCommand): 'app_label': obj.object._meta.app_label, 'object_name': obj.object._meta.object_name, 'pk': obj.object.pk, - 'error_msg': force_unicode(e) + 'error_msg': force_text(e) },) raise diff --git a/django/core/serializers/__init__.py b/django/core/serializers/__init__.py index 09bcb651d5..cf7e66190f 100644 --- a/django/core/serializers/__init__.py +++ b/django/core/serializers/__init__.py @@ -18,6 +18,7 @@ To add your own serializers, use the SERIALIZATION_MODULES setting:: from django.conf import settings from django.utils import importlib +from django.utils import six from django.core.serializers.base import SerializerDoesNotExist # Built-in serializers @@ -75,12 +76,12 @@ def get_serializer(format): def get_serializer_formats(): if not _serializers: _load_serializers() - return _serializers.keys() + return list(_serializers) def get_public_serializer_formats(): if not _serializers: _load_serializers() - return [k for k, v in _serializers.iteritems() if not v.Serializer.internal_use_only] + return [k for k, v in six.iteritems(_serializers) if not v.Serializer.internal_use_only] def get_deserializer(format): if not _serializers: diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index 19886f7d53..bdb43db9f3 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -5,7 +5,7 @@ Module for abstract serializer/unserializer base classes. from io import BytesIO from django.db import models -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils import six class SerializerDoesNotExist(KeyError): @@ -136,10 +136,12 @@ class Deserializer(object): def __iter__(self): return self - def next(self): + def __next__(self): """Iteration iterface -- return the next item in the stream""" raise NotImplementedError + next = __next__ # Python 2 compatibility + class DeserializedObject(object): """ A deserialized model. diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py index 8b56d0e7b8..3bac24d33a 100644 --- a/django/core/serializers/json.py +++ b/django/core/serializers/json.py @@ -12,7 +12,7 @@ import json from django.core.serializers.base import DeserializationError from django.core.serializers.python import Serializer as PythonSerializer from django.core.serializers.python import Deserializer as PythonDeserializer -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes from django.utils import six from django.utils.timezone import is_aware diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 333161c929..a1fff6f9bb 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -8,7 +8,8 @@ from __future__ import unicode_literals from django.conf import settings from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS -from django.utils.encoding import smart_unicode, is_protected_type +from django.utils.encoding import smart_text, is_protected_type +from django.utils import six class Serializer(base.Serializer): """ @@ -33,8 +34,8 @@ class Serializer(base.Serializer): def get_dump_object(self, obj): return { - "pk": smart_unicode(obj._get_pk_val(), strings_only=True), - "model": smart_unicode(obj._meta), + "pk": smart_text(obj._get_pk_val(), strings_only=True), + "model": smart_text(obj._meta), "fields": self._current } @@ -64,7 +65,7 @@ class Serializer(base.Serializer): if self.use_natural_keys and hasattr(field.rel.to, 'natural_key'): m2m_value = lambda value: value.natural_key() else: - m2m_value = lambda value: smart_unicode(value._get_pk_val(), strings_only=True) + m2m_value = lambda value: smart_text(value._get_pk_val(), strings_only=True) self._current[field.name] = [m2m_value(related) for related in getattr(obj, field.name).iterator()] @@ -87,9 +88,9 @@ def Deserializer(object_list, **options): m2m_data = {} # Handle each field - for (field_name, field_value) in d["fields"].iteritems(): + for (field_name, field_value) in six.iteritems(d["fields"]): if isinstance(field_value, str): - field_value = smart_unicode(field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True) + field_value = smart_text(field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True) field = Model._meta.get_field(field_name) @@ -97,19 +98,19 @@ def Deserializer(object_list, **options): if field.rel and isinstance(field.rel, models.ManyToManyRel): if hasattr(field.rel.to._default_manager, 'get_by_natural_key'): def m2m_convert(value): - if hasattr(value, '__iter__'): + if hasattr(value, '__iter__') and not isinstance(value, six.text_type): return field.rel.to._default_manager.db_manager(db).get_by_natural_key(*value).pk else: - return smart_unicode(field.rel.to._meta.pk.to_python(value)) + return smart_text(field.rel.to._meta.pk.to_python(value)) else: - m2m_convert = lambda v: smart_unicode(field.rel.to._meta.pk.to_python(v)) + m2m_convert = lambda v: smart_text(field.rel.to._meta.pk.to_python(v)) m2m_data[field.name] = [m2m_convert(pk) for pk in field_value] # Handle FK fields elif field.rel and isinstance(field.rel, models.ManyToOneRel): if field_value is not None: if hasattr(field.rel.to._default_manager, 'get_by_natural_key'): - if hasattr(field_value, '__iter__'): + if hasattr(field_value, '__iter__') and not isinstance(field_value, six.text_type): obj = field.rel.to._default_manager.db_manager(db).get_by_natural_key(*field_value) value = getattr(obj, field.rel.field_name) # If this is a natural foreign key to an object that diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py index ac0e6cf82d..9be1ea4492 100644 --- a/django/core/serializers/pyyaml.py +++ b/django/core/serializers/pyyaml.py @@ -12,7 +12,7 @@ from django.db import models from django.core.serializers.base import DeserializationError from django.core.serializers.python import Serializer as PythonSerializer from django.core.serializers.python import Deserializer as PythonDeserializer -from django.utils.encoding import smart_str +from django.utils.encoding import smart_bytes from django.utils import six diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 9d9c023b64..666587dc77 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -8,7 +8,7 @@ from django.conf import settings from django.core.serializers import base from django.db import models, DEFAULT_DB_ALIAS from django.utils.xmlutils import SimplerXMLGenerator -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from xml.dom import pulldom class Serializer(base.Serializer): @@ -46,11 +46,11 @@ class Serializer(base.Serializer): self.indent(1) obj_pk = obj._get_pk_val() if obj_pk is None: - attrs = {"model": smart_unicode(obj._meta),} + attrs = {"model": smart_text(obj._meta),} else: attrs = { - "pk": smart_unicode(obj._get_pk_val()), - "model": smart_unicode(obj._meta), + "pk": smart_text(obj._get_pk_val()), + "model": smart_text(obj._meta), } self.xml.startElement("object", attrs) @@ -96,10 +96,10 @@ class Serializer(base.Serializer): # Iterable natural keys are rolled out as subelements for key_value in related: self.xml.startElement("natural", {}) - self.xml.characters(smart_unicode(key_value)) + self.xml.characters(smart_text(key_value)) self.xml.endElement("natural") else: - self.xml.characters(smart_unicode(related_att)) + self.xml.characters(smart_text(related_att)) else: self.xml.addQuickElement("None") self.xml.endElement("field") @@ -120,13 +120,13 @@ class Serializer(base.Serializer): self.xml.startElement("object", {}) for key_value in natural: self.xml.startElement("natural", {}) - self.xml.characters(smart_unicode(key_value)) + self.xml.characters(smart_text(key_value)) self.xml.endElement("natural") self.xml.endElement("object") else: def handle_m2m(value): self.xml.addQuickElement("object", attrs={ - 'pk' : smart_unicode(value._get_pk_val()) + 'pk' : smart_text(value._get_pk_val()) }) for relobj in getattr(obj, field.name).iterator(): handle_m2m(relobj) @@ -141,7 +141,7 @@ class Serializer(base.Serializer): self.xml.startElement("field", { "name" : field.name, "rel" : field.rel.__class__.__name__, - "to" : smart_unicode(field.rel.to._meta), + "to" : smart_text(field.rel.to._meta), }) class Deserializer(base.Deserializer): @@ -154,13 +154,15 @@ class Deserializer(base.Deserializer): self.event_stream = pulldom.parse(self.stream) self.db = options.pop('using', DEFAULT_DB_ALIAS) - def next(self): + def __next__(self): for event, node in self.event_stream: if event == "START_ELEMENT" and node.nodeName == "object": self.event_stream.expandNode(node) return self._handle_object(node) raise StopIteration + next = __next__ # Python 2 compatibility + def _handle_object(self, node): """ Convert an node to a DeserializedObject. diff --git a/django/core/signing.py b/django/core/signing.py index cd9759e536..9ab8c5b8b0 100644 --- a/django/core/signing.py +++ b/django/core/signing.py @@ -41,7 +41,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.utils import baseconv from django.utils.crypto import constant_time_compare, salted_hmac -from django.utils.encoding import force_unicode, smart_str +from django.utils.encoding import force_text, smart_bytes from django.utils.importlib import import_module @@ -135,7 +135,7 @@ def loads(s, key=None, salt='django.core.signing', serializer=JSONSerializer, ma """ Reverse of dumps(), raises BadSignature if signature fails """ - base64d = smart_str( + base64d = smart_bytes( TimestampSigner(key, salt=salt).unsign(s, max_age=max_age)) decompress = False if base64d[0] == '.': @@ -159,16 +159,16 @@ class Signer(object): return base64_hmac(self.salt + 'signer', value, self.key) def sign(self, value): - value = smart_str(value) + value = smart_bytes(value) return '%s%s%s' % (value, self.sep, self.signature(value)) def unsign(self, signed_value): - signed_value = smart_str(signed_value) + signed_value = smart_bytes(signed_value) if not self.sep in signed_value: raise BadSignature('No "%s" found in value' % self.sep) value, sig = signed_value.rsplit(self.sep, 1) if constant_time_compare(sig, self.signature(value)): - return force_unicode(value) + return force_text(value) raise BadSignature('Signature "%s" does not match' % sig) @@ -178,7 +178,7 @@ class TimestampSigner(Signer): return baseconv.base62.encode(int(time.time())) def sign(self, value): - value = smart_str('%s%s%s' % (value, self.sep, self.timestamp())) + value = smart_bytes('%s%s%s' % (value, self.sep, self.timestamp())) return '%s%s%s' % (value, self.sep, self.signature(value)) def unsign(self, value, max_age=None): diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 7397cf3b3d..2fe744e8eb 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -14,7 +14,7 @@ from threading import local from django.http import Http404 from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.utils.datastructures import MultiValueDict -from django.utils.encoding import iri_to_uri, force_unicode, smart_str +from django.utils.encoding import iri_to_uri, force_text, smart_bytes from django.utils.functional import memoize, lazy from django.utils.importlib import import_module from django.utils.module_loading import module_has_submodule @@ -163,7 +163,7 @@ class LocaleRegexProvider(object): if isinstance(self._regex, six.string_types): regex = self._regex else: - regex = force_unicode(self._regex) + regex = force_text(self._regex) try: compiled_regex = re.compile(regex, re.UNICODE) except re.error as e: @@ -190,7 +190,7 @@ class RegexURLPattern(LocaleRegexProvider): self.name = name def __repr__(self): - return smart_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)) + return smart_bytes('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)) def add_prefix(self, prefix): """ @@ -240,7 +240,7 @@ class RegexURLResolver(LocaleRegexProvider): self._app_dict = {} def __repr__(self): - return smart_str('<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)) + return smart_bytes('<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)) def _populate(self): lookups = MultiValueDict() @@ -373,10 +373,10 @@ class RegexURLResolver(LocaleRegexProvider): if args: if len(args) != len(params) + len(prefix_args): continue - unicode_args = [force_unicode(val) for val in args] + unicode_args = [force_text(val) for val in args] candidate = (prefix_norm + result) % dict(zip(prefix_args + params, unicode_args)) else: - if set(kwargs.keys() + defaults.keys()) != set(params + defaults.keys() + prefix_args): + if set(kwargs.keys()) | set(defaults.keys()) != set(params) | set(defaults.keys()) | set(prefix_args): continue matches = True for k, v in defaults.items(): @@ -385,7 +385,7 @@ class RegexURLResolver(LocaleRegexProvider): break if not matches: continue - unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()]) + unicode_kwargs = dict([(k, force_text(v)) for (k, v) in kwargs.items()]) candidate = (prefix_norm + result) % unicode_kwargs if re.search('^%s%s' % (_prefix, pattern), candidate, re.UNICODE): return candidate diff --git a/django/core/validators.py b/django/core/validators.py index 03ff8be3bc..456fa3cec5 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -8,7 +8,7 @@ except ImportError: # Python 2 from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.ipv6 import is_valid_ipv6_address from django.utils import six @@ -36,7 +36,7 @@ class RegexValidator(object): """ Validates that the input matches the regular expression. """ - if not self.regex.search(smart_unicode(value)): + if not self.regex.search(smart_text(value)): raise ValidationError(self.message, code=self.code) class URLValidator(RegexValidator): @@ -54,7 +54,7 @@ class URLValidator(RegexValidator): except ValidationError as e: # Trivial case failed. Try for possible IDN domain if value: - value = smart_unicode(value) + value = smart_text(value) scheme, netloc, path, query, fragment = urlsplit(value) try: netloc = netloc.encode('idna') # IDN -> ACE @@ -138,7 +138,7 @@ def ip_address_validators(protocol, unpack_ipv4): return ip_address_validator_map[protocol.lower()] except KeyError: raise ValueError("The protocol '%s' is unknown. Supported: %s" - % (protocol, ip_address_validator_map.keys())) + % (protocol, list(ip_address_validator_map))) comma_separated_int_list_re = re.compile('^[\d,]+$') validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _('Enter only digits separated by commas.'), 'invalid') diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index b9c642d093..5719f51445 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -621,16 +621,16 @@ class BaseDatabaseOperations(object): exists for database backends to provide a better implementation according to their own quoting schemes. """ - from django.utils.encoding import smart_unicode, force_unicode + from django.utils.encoding import smart_text, force_text # Convert params to contain Unicode values. - to_unicode = lambda s: force_unicode(s, strings_only=True, errors='replace') + to_unicode = lambda s: force_text(s, strings_only=True, errors='replace') if isinstance(params, (list, tuple)): u_params = tuple([to_unicode(val) for val in params]) else: u_params = dict([(to_unicode(k), to_unicode(v)) for k, v in params.items()]) - return smart_unicode(sql) % u_params + return smart_text(sql) % u_params def last_insert_id(self, cursor, table_name, pk_name): """ @@ -814,8 +814,8 @@ class BaseDatabaseOperations(object): def prep_for_like_query(self, x): """Prepares a value for use in a LIKE query.""" - from django.utils.encoding import smart_unicode - return smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_") + from django.utils.encoding import smart_text + return smart_text(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_") # Same as prep_for_like_query(), but called for "iexact" matches, which # need not necessarily be implemented using "LIKE" in the backend. diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index 4dffd78f44..0cc01cc876 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -3,6 +3,7 @@ import time from django.conf import settings from django.db.utils import load_backend +from django.utils.six.moves import input # The prefix to put on the default database name when creating # the test database. @@ -331,7 +332,7 @@ class BaseDatabaseCreation(object): sys.stderr.write( "Got an error creating the test database: %s\n" % e) if not autoclobber: - confirm = raw_input( + confirm = input( "Type 'yes' if you would like to try deleting the test " "database '%s', or 'no' to cancel: " % test_database_name) if autoclobber or confirm == 'yes': diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index e6f18b819f..6aab0b99ab 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -1,4 +1,5 @@ from django.db.backends import BaseDatabaseIntrospection +from django.utils import six from MySQLdb import ProgrammingError, OperationalError from MySQLdb.constants import FIELD_TYPE import re @@ -79,7 +80,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): """ Returns the name of the primary key column for the given table """ - for column in self.get_indexes(cursor, table_name).iteritems(): + for column in six.iteritems(self.get_indexes(cursor, table_name)): if column[1]['primary_key']: return column[0] return None diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index b08113fed7..89cad12b6c 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -53,7 +53,7 @@ from django.db.backends.signals import connection_created from django.db.backends.oracle.client import DatabaseClient from django.db.backends.oracle.creation import DatabaseCreation from django.db.backends.oracle.introspection import DatabaseIntrospection -from django.utils.encoding import smart_str, force_unicode +from django.utils.encoding import smart_bytes, force_text from django.utils import six from django.utils import timezone @@ -64,9 +64,9 @@ IntegrityError = Database.IntegrityError # Check whether cx_Oracle was compiled with the WITH_UNICODE option. This will # also be True in Python 3.0. if int(Database.version.split('.', 1)[0]) >= 5 and not hasattr(Database, 'UNICODE'): - convert_unicode = force_unicode + convert_unicode = force_text else: - convert_unicode = smart_str + convert_unicode = smart_bytes class DatabaseFeatures(BaseDatabaseFeatures): @@ -162,7 +162,7 @@ WHEN (new.%(col_name)s IS NULL) if isinstance(value, Database.LOB): value = value.read() if field and field.get_internal_type() == 'TextField': - value = force_unicode(value) + value = force_text(value) # Oracle stores empty strings as null. We need to undo this in # order to adhere to the Django convention of using the empty @@ -245,7 +245,7 @@ WHEN (new.%(col_name)s IS NULL) def process_clob(self, value): if value is None: return '' - return force_unicode(value.read()) + return force_text(value.read()) def quote_name(self, name): # SQL92 requires delimited (quoted) names to be case-sensitive. When @@ -595,9 +595,9 @@ class OracleParam(object): param = param.astimezone(timezone.utc).replace(tzinfo=None) if hasattr(param, 'bind_parameter'): - self.smart_str = param.bind_parameter(cursor) + self.smart_bytes = param.bind_parameter(cursor) else: - self.smart_str = convert_unicode(param, cursor.charset, + self.smart_bytes = convert_unicode(param, cursor.charset, strings_only) if hasattr(param, 'input_size'): # If parameter has `input_size` attribute, use that. @@ -676,7 +676,7 @@ class FormatStylePlaceholderCursor(object): self.setinputsizes(*sizes) def _param_generator(self, params): - return [p.smart_str for p in params] + return [p.smart_bytes for p in params] def execute(self, query, params=None): if params is None: @@ -774,9 +774,11 @@ class CursorIterator(object): def __iter__(self): return self - def next(self): + def __next__(self): return _rowfactory(next(self.iter), self.cursor) + next = __next__ # Python 2 compatibility + def _rowfactory(row, cursor): # Cast numeric values as the appropriate Python type based upon the @@ -831,7 +833,7 @@ def to_unicode(s): unchanged). """ if isinstance(s, six.string_types): - return force_unicode(s) + return force_text(s) return s diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index 2f096f735a..d9bf3dfea2 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -1,6 +1,7 @@ import sys import time from django.db.backends.creation import BaseDatabaseCreation +from django.utils.six.moves import input TEST_DATABASE_PREFIX = 'test_' PASSWORD = 'Im_a_lumberjack' @@ -65,7 +66,7 @@ class DatabaseCreation(BaseDatabaseCreation): except Exception as e: sys.stderr.write("Got an error creating the test database: %s\n" % e) if not autoclobber: - confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_NAME) + confirm = input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_NAME) if autoclobber or confirm == 'yes': try: if verbosity >= 1: @@ -87,7 +88,7 @@ class DatabaseCreation(BaseDatabaseCreation): except Exception as e: sys.stderr.write("Got an error creating the test user: %s\n" % e) if not autoclobber: - confirm = raw_input("It appears the test user, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_USER) + confirm = input("It appears the test user, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_USER) if autoclobber or confirm == 'yes': try: if verbosity >= 1: diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 0a97449789..0880079189 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -54,15 +54,15 @@ def adapt_datetime_with_timezone_support(value): default_timezone = timezone.get_default_timezone() value = timezone.make_aware(value, default_timezone) value = value.astimezone(timezone.utc).replace(tzinfo=None) - return value.isoformat(b" ") + return value.isoformat(str(" ")) -Database.register_converter(b"bool", lambda s: str(s) == '1') -Database.register_converter(b"time", parse_time) -Database.register_converter(b"date", parse_date) -Database.register_converter(b"datetime", parse_datetime_with_timezone_support) -Database.register_converter(b"timestamp", parse_datetime_with_timezone_support) -Database.register_converter(b"TIMESTAMP", parse_datetime_with_timezone_support) -Database.register_converter(b"decimal", util.typecast_decimal) +Database.register_converter(str("bool"), lambda s: str(s) == '1') +Database.register_converter(str("time"), parse_time) +Database.register_converter(str("date"), parse_date) +Database.register_converter(str("datetime"), parse_datetime_with_timezone_support) +Database.register_converter(str("timestamp"), parse_datetime_with_timezone_support) +Database.register_converter(str("TIMESTAMP"), parse_datetime_with_timezone_support) +Database.register_converter(str("decimal"), util.typecast_decimal) Database.register_adapter(datetime.datetime, adapt_datetime_with_timezone_support) Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal) if Database.version_info >= (2, 4, 1): diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py index efdc457be0..c022b56c85 100644 --- a/django/db/backends/sqlite3/creation.py +++ b/django/db/backends/sqlite3/creation.py @@ -1,6 +1,7 @@ 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 @@ -53,7 +54,7 @@ class DatabaseCreation(BaseDatabaseCreation): print("Destroying old test database '%s'..." % self.connection.alias) if os.access(test_database_name, os.F_OK): if not autoclobber: - confirm = raw_input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % test_database_name) + confirm = input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % test_database_name) if autoclobber or confirm == 'yes': try: os.remove(test_database_name) diff --git a/django/db/models/base.py b/django/db/models/base.py index 002e2aff65..569b8e876c 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -23,11 +23,35 @@ from django.db.models import signals from django.db.models.loading import register_models, get_model from django.utils.translation import ugettext_lazy as _ from django.utils.functional import curry -from django.utils.encoding import smart_str, force_unicode +from django.utils.encoding import smart_bytes, force_text from django.utils import six from django.utils.text import get_text_list, capfirst +def subclass_exception(name, parents, module, attached_to=None): + """ + Create exception subclass. Used by ModelBase below. + + If 'attached_to' is supplied, the exception will be created in a way that + allows it to be pickled, assuming the returned exception class will be added + as an attribute to the 'attached_to' class. + """ + class_dict = {'__module__': module} + if attached_to is not None: + def __reduce__(self): + # Exceptions are special - they've got state that isn't + # in self.__dict__. We assume it is all in self.args. + return (unpickle_inner_exception, (attached_to, name), self.args) + + def __setstate__(self, args): + self.args = args + + class_dict['__reduce__'] = __reduce__ + class_dict['__setstate__'] = __setstate__ + + return type(name, parents, class_dict) + + class ModelBase(type): """ Metaclass for all models. @@ -63,12 +87,12 @@ class ModelBase(type): new_class.add_to_class('_meta', Options(meta, **kwargs)) if not abstract: - new_class.add_to_class('DoesNotExist', subclass_exception(b'DoesNotExist', + new_class.add_to_class('DoesNotExist', subclass_exception(str('DoesNotExist'), tuple(x.DoesNotExist for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (ObjectDoesNotExist,), module, attached_to=new_class)) - new_class.add_to_class('MultipleObjectsReturned', subclass_exception(b'MultipleObjectsReturned', + new_class.add_to_class('MultipleObjectsReturned', subclass_exception(str('MultipleObjectsReturned'), tuple(x.MultipleObjectsReturned for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (MultipleObjectsReturned,), @@ -364,14 +388,14 @@ class Model(six.with_metaclass(ModelBase, object)): setattr(self, field.attname, val) if kwargs: - for prop in kwargs.keys(): + for prop in list(kwargs): try: if isinstance(getattr(self.__class__, prop), property): setattr(self, prop, kwargs.pop(prop)) except AttributeError: pass if kwargs: - raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]) + raise TypeError("'%s' is an invalid keyword argument for this function" % list(kwargs)[0]) super(Model, self).__init__() signals.post_init.send(sender=self.__class__, instance=self) @@ -380,11 +404,11 @@ class Model(six.with_metaclass(ModelBase, object)): u = six.text_type(self) except (UnicodeEncodeError, UnicodeDecodeError): u = '[Bad Unicode data]' - return smart_str('<%s: %s>' % (self.__class__.__name__, u)) + return smart_bytes('<%s: %s>' % (self.__class__.__name__, u)) def __str__(self): if hasattr(self, '__unicode__'): - return force_unicode(self).encode('utf-8') + return force_text(self).encode('utf-8') return '%s object' % self.__class__.__name__ def __eq__(self, other): @@ -605,14 +629,14 @@ class Model(six.with_metaclass(ModelBase, object)): def _get_FIELD_display(self, field): value = getattr(self, field.attname) - return force_unicode(dict(field.flatchoices).get(value, value), strings_only=True) + return force_text(dict(field.flatchoices).get(value, value), strings_only=True) def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs): if not self.pk: raise ValueError("get_next/get_previous cannot be used on unsaved objects.") op = is_next and 'gt' or 'lt' order = not is_next and '-' or '' - param = smart_str(getattr(self, field.attname)) + param = smart_bytes(getattr(self, field.attname)) q = Q(**{'%s__%s' % (field.name, op): param}) q = q|Q(**{field.name: param, 'pk__%s' % op: self.pk}) qs = self.__class__._default_manager.using(self._state.db).filter(**kwargs).filter(q).order_by('%s%s' % (order, field.name), '%spk' % order) @@ -929,29 +953,6 @@ def model_unpickle(model, attrs): return cls.__new__(cls) model_unpickle.__safe_for_unpickle__ = True -def subclass_exception(name, parents, module, attached_to=None): - """ - Create exception subclass. - - If 'attached_to' is supplied, the exception will be created in a way that - allows it to be pickled, assuming the returned exception class will be added - as an attribute to the 'attached_to' class. - """ - class_dict = {'__module__': module} - if attached_to is not None: - def __reduce__(self): - # Exceptions are special - they've got state that isn't - # in self.__dict__. We assume it is all in self.args. - return (unpickle_inner_exception, (attached_to, name), self.args) - - def __setstate__(self, args): - self.args = args - - class_dict['__reduce__'] = __reduce__ - class_dict['__setstate__'] = __setstate__ - - return type(name, parents, class_dict) - def unpickle_inner_exception(klass, exception_name): # Get the exception class from the class it is attached to: exception = getattr(klass, exception_name) diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index d8bb8f2e66..4449b75a81 100644 --- a/django/db/models/deletion.py +++ b/django/db/models/deletion.py @@ -4,6 +4,7 @@ from operator import attrgetter from django.db import connections, transaction, IntegrityError from django.db.models import signals, sql from django.utils.datastructures import SortedDict +from django.utils import six class ProtectedError(IntegrityError): @@ -157,7 +158,7 @@ class Collector(object): # Recursively collect concrete model's parent models, but not their # related objects. These will be found by meta.get_all_related_objects() concrete_model = model._meta.concrete_model - for ptr in concrete_model._meta.parents.itervalues(): + for ptr in six.itervalues(concrete_model._meta.parents): if ptr: parent_objs = [getattr(obj, ptr.name) for obj in new_objs] self.collect(parent_objs, source=model, @@ -199,14 +200,14 @@ class Collector(object): ) def instances_with_model(self): - for model, instances in self.data.iteritems(): + for model, instances in six.iteritems(self.data): for obj in instances: yield model, obj def sort(self): sorted_models = [] concrete_models = set() - models = self.data.keys() + models = list(self.data) while len(sorted_models) < len(models): found = False for model in models: @@ -241,24 +242,24 @@ class Collector(object): ) # update fields - for model, instances_for_fieldvalues in self.field_updates.iteritems(): + for model, instances_for_fieldvalues in six.iteritems(self.field_updates): query = sql.UpdateQuery(model) - for (field, value), instances in instances_for_fieldvalues.iteritems(): + for (field, value), instances in six.iteritems(instances_for_fieldvalues): query.update_batch([obj.pk for obj in instances], {field.name: value}, self.using) # reverse instance collections - for instances in self.data.itervalues(): + for instances in six.itervalues(self.data): instances.reverse() # delete batches - for model, batches in self.batches.iteritems(): + for model, batches in six.iteritems(self.batches): query = sql.DeleteQuery(model) - for field, instances in batches.iteritems(): + for field, instances in six.iteritems(batches): query.delete_batch([obj.pk for obj in instances], self.using, field) # delete instances - for model, instances in self.data.iteritems(): + for model, instances in six.iteritems(self.data): query = sql.DeleteQuery(model) pk_list = [obj.pk for obj in instances] query.delete_batch(pk_list, self.using) @@ -271,10 +272,10 @@ class Collector(object): ) # update collected instances - for model, instances_for_fieldvalues in self.field_updates.iteritems(): - for (field, value), instances in instances_for_fieldvalues.iteritems(): + for model, instances_for_fieldvalues in six.iteritems(self.field_updates): + for (field, value), instances in six.iteritems(instances_for_fieldvalues): for obj in instances: setattr(obj, field.attname, value) - for model, instances in self.data.iteritems(): + for model, instances in six.iteritems(self.data): for instance in instances: setattr(instance, model._meta.pk.attname, None) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 9606b1b843..d07851bbf5 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -19,7 +19,7 @@ from django.utils.functional import curry, total_ordering from django.utils.text import capfirst from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import smart_unicode, force_unicode +from django.utils.encoding import smart_text, force_text from django.utils.ipv6 import clean_ipv6_address from django.utils import six @@ -135,6 +135,8 @@ class Field(object): return self.creation_counter < other.creation_counter return NotImplemented + __hash__ = object.__hash__ + def __deepcopy__(self, memodict): # We don't have to deepcopy very much here, since most things are not # intended to be altered after initial creation. @@ -179,7 +181,8 @@ class Field(object): if not self.editable: # Skip validation for non-editable fields. return - if self._choices and value: + + if self._choices and value not in validators.EMPTY_VALUES: for option_key, option_value in self.choices: if isinstance(option_value, (list, tuple)): # This is an optgroup, so look inside the group for @@ -385,7 +388,7 @@ class Field(object): if self.has_default(): if callable(self.default): return self.default() - return force_unicode(self.default, strings_only=True) + return force_text(self.default, strings_only=True) if (not self.empty_strings_allowed or (self.null and not connection.features.interprets_empty_strings_as_nulls)): return None @@ -403,11 +406,11 @@ class Field(object): rel_model = self.rel.to if hasattr(self.rel, 'get_related_field'): lst = [(getattr(x, self.rel.get_related_field().attname), - smart_unicode(x)) + smart_text(x)) for x in rel_model._default_manager.complex_filter( self.rel.limit_choices_to)] else: - lst = [(x._get_pk_val(), smart_unicode(x)) + lst = [(x._get_pk_val(), smart_text(x)) for x in rel_model._default_manager.complex_filter( self.rel.limit_choices_to)] return first_choice + lst @@ -434,7 +437,7 @@ class Field(object): Returns a string value of this field from the passed obj. This is used by the serialization framework. """ - return smart_unicode(self._get_val_from_obj(obj)) + return smart_text(self._get_val_from_obj(obj)) def bind(self, fieldmapping, original, bound_field_class): return bound_field_class(self, fieldmapping, original) @@ -486,7 +489,7 @@ class Field(object): # Many of the subclass-specific formfield arguments (min_value, # max_value) don't apply for choice fields, so be sure to only pass # the values that TypedChoiceField will understand. - for k in kwargs.keys(): + for k in list(kwargs): if k not in ('coerce', 'empty_value', 'choices', 'required', 'widget', 'label', 'initial', 'help_text', 'error_messages', 'show_hidden_initial'): @@ -628,7 +631,7 @@ class CharField(Field): def to_python(self, value): if isinstance(value, six.string_types) or value is None: return value - return smart_unicode(value) + return smart_text(value) def get_prep_value(self, value): return self.to_python(value) @@ -1188,7 +1191,7 @@ class TextField(Field): def get_prep_value(self, value): if isinstance(value, six.string_types) or value is None: return value - return smart_unicode(value) + return smart_text(value) def formfield(self, **kwargs): defaults = {'widget': forms.Textarea} diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index b51ef1d5d6..ad4c36ca0d 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -8,7 +8,7 @@ from django.core.files.base import File from django.core.files.storage import default_storage from django.core.files.images import ImageFile from django.db.models import signals -from django.utils.encoding import force_unicode, smart_str +from django.utils.encoding import force_text, smart_bytes from django.utils import six from django.utils.translation import ugettext_lazy as _ @@ -280,7 +280,7 @@ class FileField(Field): setattr(cls, self.name, self.descriptor_class(self)) def get_directory_name(self): - return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to)))) + return os.path.normpath(force_text(datetime.datetime.now().strftime(smart_bytes(self.upload_to)))) def get_filename(self, filename): return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename))) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 2a2502b54f..08cc0a747f 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -9,7 +9,7 @@ from django.db.models.related import RelatedObject from django.db.models.query import QuerySet from django.db.models.query_utils import QueryWrapper from django.db.models.deletion import CASCADE -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils import six from django.utils.translation import ugettext_lazy as _, string_concat from django.utils.functional import curry, cached_property @@ -241,7 +241,7 @@ class SingleRelatedObjectDescriptor(object): rel_obj_attr = attrgetter(self.related.field.attname) instance_attr = lambda obj: obj._get_pk_val() instances_dict = dict((instance_attr(inst), inst) for inst in instances) - params = {'%s__pk__in' % self.related.field.name: instances_dict.keys()} + params = {'%s__pk__in' % self.related.field.name: list(instances_dict)} qs = self.get_query_set(instance=instances[0]).filter(**params) # Since we're going to assign directly in the cache, # we must manage the reverse relation cache manually. @@ -335,9 +335,9 @@ class ReverseSingleRelatedObjectDescriptor(object): instance_attr = attrgetter(self.field.attname) instances_dict = dict((instance_attr(inst), inst) for inst in instances) if other_field.rel: - params = {'%s__pk__in' % self.field.rel.field_name: instances_dict.keys()} + params = {'%s__pk__in' % self.field.rel.field_name: list(instances_dict)} else: - params = {'%s__in' % self.field.rel.field_name: instances_dict.keys()} + params = {'%s__in' % self.field.rel.field_name: list(instances_dict)} qs = self.get_query_set(instance=instances[0]).filter(**params) # Since we're going to assign directly in the cache, # we must manage the reverse relation cache manually. @@ -488,7 +488,7 @@ class ForeignRelatedObjectsDescriptor(object): instance_attr = attrgetter(attname) instances_dict = dict((instance_attr(inst), inst) for inst in instances) db = self._db or router.db_for_read(self.model, instance=instances[0]) - query = {'%s__%s__in' % (rel_field.name, attname): instances_dict.keys()} + query = {'%s__%s__in' % (rel_field.name, attname): list(instances_dict)} qs = super(RelatedManager, self).get_query_set().using(db).filter(**query) # Since we just bypassed this class' get_query_set(), we must manage # the reverse relation manually. @@ -999,7 +999,7 @@ class ForeignKey(RelatedField, Field): if not self.blank and self.choices: choice_list = self.get_choices_default() if len(choice_list) == 2: - return smart_unicode(choice_list[1][0]) + return smart_text(choice_list[1][0]) return Field.value_to_string(self, obj) def contribute_to_class(self, cls, name): @@ -1205,7 +1205,7 @@ class ManyToManyField(RelatedField, Field): choices_list = self.get_choices_default() if len(choices_list) == 1: data = [choices_list[0][0]] - return smart_unicode(data) + return smart_text(data) def contribute_to_class(self, cls, name): # To support multiple relations to self, it's useful to have a non-None diff --git a/django/db/models/loading.py b/django/db/models/loading.py index d651584e7a..7a9cb2cb41 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -5,6 +5,7 @@ from django.core.exceptions import ImproperlyConfigured from django.utils.datastructures import SortedDict from django.utils.importlib import import_module from django.utils.module_loading import module_has_submodule +from django.utils import six import imp import sys @@ -193,9 +194,9 @@ class AppCache(object): else: if only_installed: app_list = [self.app_models.get(app_label, SortedDict()) - for app_label in self.app_labels.iterkeys()] + for app_label in six.iterkeys(self.app_labels)] else: - app_list = self.app_models.itervalues() + app_list = six.itervalues(self.app_models) model_list = [] for app in app_list: model_list.extend( diff --git a/django/db/models/options.py b/django/db/models/options.py index 7308a15c6b..2e8ccb49ce 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -8,7 +8,7 @@ from django.db.models.fields import AutoField, FieldDoesNotExist from django.db.models.fields.proxy import OrderWrt from django.db.models.loading import get_models, app_cache_ready from django.utils.translation import activate, deactivate_all, get_language, string_concat -from django.utils.encoding import force_unicode, smart_str +from django.utils.encoding import force_text, smart_bytes from django.utils.datastructures import SortedDict from django.utils import six @@ -127,7 +127,7 @@ class Options(object): if self.parents: # Promote the first parent link in lieu of adding yet another # field. - field = next(self.parents.itervalues()) + field = next(six.itervalues(self.parents)) # Look for a local field with the same name as the # first parent link. If a local field has already been # created, use it instead of promoting the parent @@ -147,13 +147,13 @@ class Options(object): # self.duplicate_targets will map each duplicate field column to the # columns it duplicates. collections = {} - for column, target in self.duplicate_targets.iteritems(): + for column, target in six.iteritems(self.duplicate_targets): try: collections[target].add(column) except KeyError: collections[target] = set([column]) self.duplicate_targets = {} - for elt in collections.itervalues(): + for elt in six.itervalues(collections): if len(elt) == 1: continue for column in elt: @@ -199,7 +199,7 @@ class Options(object): return '' % self.object_name def __str__(self): - return "%s.%s" % (smart_str(self.app_label), smart_str(self.module_name)) + return "%s.%s" % (smart_bytes(self.app_label), smart_bytes(self.module_name)) def verbose_name_raw(self): """ @@ -209,7 +209,7 @@ class Options(object): """ lang = get_language() deactivate_all() - raw = force_unicode(self.verbose_name) + raw = force_text(self.verbose_name) activate(lang) return raw verbose_name_raw = property(verbose_name_raw) @@ -258,7 +258,7 @@ class Options(object): self._m2m_cache except AttributeError: self._fill_m2m_cache() - return self._m2m_cache.keys() + return list(self._m2m_cache) many_to_many = property(_many_to_many) def get_m2m_with_model(self): @@ -269,7 +269,7 @@ class Options(object): self._m2m_cache except AttributeError: self._fill_m2m_cache() - return self._m2m_cache.items() + return list(six.iteritems(self._m2m_cache)) def _fill_m2m_cache(self): cache = SortedDict() @@ -326,8 +326,7 @@ class Options(object): cache = self._name_map except AttributeError: cache = self.init_name_map() - names = cache.keys() - names.sort() + names = sorted(cache.keys()) # Internal-only names end with "+" (symmetrical m2m related names being # the main example). Trim them. return [val for val in names if not val.endswith('+')] @@ -417,7 +416,7 @@ class Options(object): cache = self._fill_related_many_to_many_cache() if local_only: return [k for k, v in cache.items() if not v] - return cache.keys() + return list(cache) def get_all_related_m2m_objects_with_model(self): """ @@ -428,7 +427,7 @@ class Options(object): cache = self._related_many_to_many_cache except AttributeError: cache = self._fill_related_many_to_many_cache() - return cache.items() + return list(six.iteritems(cache)) def _fill_related_many_to_many_cache(self): cache = SortedDict() diff --git a/django/db/models/query.py b/django/db/models/query.py index 8b6b42b7b1..e8d6ae2a7b 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -120,7 +120,7 @@ class QuerySet(object): if len(self._result_cache) <= pos: self._fill_cache() - def __nonzero__(self): + def __bool__(self): if self._prefetch_related_lookups and not self._prefetch_done: # We need all the results in order to be able to do the prefetch # in one go. To minimize code duplication, we use the __len__ @@ -134,6 +134,7 @@ class QuerySet(object): except StopIteration: return False return True + __nonzero__ = __bool__ # Python 2 def __contains__(self, val): # The 'in' operator works without this method, due to __iter__. This @@ -245,8 +246,8 @@ class QuerySet(object): requested = None max_depth = self.query.max_depth - extra_select = self.query.extra_select.keys() - aggregate_select = self.query.aggregate_select.keys() + extra_select = list(self.query.extra_select) + aggregate_select = list(self.query.aggregate_select) only_load = self.query.get_loaded_field_names() if not fill_cache: @@ -593,7 +594,7 @@ class QuerySet(object): flat = kwargs.pop('flat', False) if kwargs: raise TypeError('Unexpected keyword arguments to values_list: %s' - % (kwargs.keys(),)) + % (list(kwargs),)) if flat and len(fields) > 1: raise TypeError("'flat' is not valid when values_list is called with more than one field.") return self._clone(klass=ValuesListQuerySet, setup=True, flat=flat, @@ -693,7 +694,7 @@ class QuerySet(object): depth = kwargs.pop('depth', 0) if kwargs: raise TypeError('Unexpected keyword arguments to select_related: %s' - % (kwargs.keys(),)) + % (list(kwargs),)) obj = self._clone() if fields: if depth: @@ -751,7 +752,7 @@ class QuerySet(object): obj = self._clone() - obj._setup_aggregate_query(kwargs.keys()) + obj._setup_aggregate_query(list(kwargs)) # Add the aggregates to the query for (alias, aggregate_expr) in kwargs.items(): @@ -966,9 +967,9 @@ class ValuesQuerySet(QuerySet): def iterator(self): # Purge any extra columns that haven't been explicitly asked for - extra_names = self.query.extra_select.keys() + extra_names = list(self.query.extra_select) field_names = self.field_names - aggregate_names = self.query.aggregate_select.keys() + aggregate_names = list(self.query.aggregate_select) names = extra_names + field_names + aggregate_names @@ -1097,9 +1098,9 @@ class ValuesListQuerySet(ValuesQuerySet): # When extra(select=...) or an annotation is involved, the extra # cols are always at the start of the row, and we need to reorder # the fields to match the order in self._fields. - extra_names = self.query.extra_select.keys() + extra_names = list(self.query.extra_select) field_names = self.field_names - aggregate_names = self.query.aggregate_select.keys() + aggregate_names = list(self.query.aggregate_select) names = extra_names + field_names + aggregate_names @@ -1527,7 +1528,7 @@ class RawQuerySet(object): # Associate fields to values if skip: model_init_kwargs = {} - for attname, pos in model_init_field_names.iteritems(): + for attname, pos in six.iteritems(model_init_field_names): model_init_kwargs[attname] = values[pos] instance = model_cls(**model_init_kwargs) else: diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index 60bdb2bcb4..c1a690a524 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -8,6 +8,7 @@ circular import difficulties. from __future__ import unicode_literals from django.db.backends import util +from django.utils import six from django.utils import tree @@ -40,7 +41,7 @@ class Q(tree.Node): default = AND def __init__(self, *args, **kwargs): - super(Q, self).__init__(children=list(args) + kwargs.items()) + super(Q, self).__init__(children=list(args) + list(six.iteritems(kwargs))) def _combine(self, other, conn): if not isinstance(other, Q): @@ -114,7 +115,7 @@ class DeferredAttribute(object): def _check_parent_chain(self, instance, name): """ - Check if the field value can be fetched from a parent field already + Check if the field value can be fetched from a parent field already loaded in the instance. This can be done if the to-be fetched field is a primary key field. """ diff --git a/django/db/models/related.py b/django/db/models/related.py index 90995d749f..a0dcec7132 100644 --- a/django/db/models/related.py +++ b/django/db/models/related.py @@ -1,4 +1,4 @@ -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.db.models.fields import BLANK_CHOICE_DASH class BoundRelatedObject(object): @@ -34,9 +34,9 @@ class RelatedObject(object): if limit_to_currently_related: queryset = queryset.complex_filter( {'%s__isnull' % self.parent_model._meta.module_name: False}) - lst = [(x._get_pk_val(), smart_unicode(x)) for x in queryset] + lst = [(x._get_pk_val(), smart_text(x)) for x in queryset] return first_choice + lst - + def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): # Defer to the actual field definition for db prep return self.field.get_db_prep_lookup(lookup_type, value, diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 7a0afa349d..1311ea546c 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -9,6 +9,7 @@ from django.db.models.sql.datastructures import EmptyResultSet from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.query import get_order_dir, Query from django.db.utils import DatabaseError +from django.utils import six class SQLCompiler(object): @@ -82,7 +83,7 @@ class SQLCompiler(object): where, w_params = self.query.where.as_sql(qn=qn, connection=self.connection) having, h_params = self.query.having.as_sql(qn=qn, connection=self.connection) params = [] - for val in self.query.extra_select.itervalues(): + for val in six.itervalues(self.query.extra_select): params.extend(val[1]) result = ['SELECT'] @@ -177,7 +178,7 @@ class SQLCompiler(object): """ qn = self.quote_name_unless_alias qn2 = self.connection.ops.quote_name - result = ['(%s) AS %s' % (col[0], qn2(alias)) for alias, col in self.query.extra_select.iteritems()] + result = ['(%s) AS %s' % (col[0], qn2(alias)) for alias, col in six.iteritems(self.query.extra_select)] aliases = set(self.query.extra_select.keys()) if with_aliases: col_aliases = aliases.copy() @@ -553,7 +554,7 @@ class SQLCompiler(object): group_by = self.query.group_by or [] extra_selects = [] - for extra_select, extra_params in self.query.extra_select.itervalues(): + for extra_select, extra_params in six.itervalues(self.query.extra_select): extra_selects.append(extra_select) params.extend(extra_params) cols = (group_by + self.query.select + diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 53dad608bf..be257a5410 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -10,8 +10,9 @@ all about the internals of models in order to get the information it needs. import copy from django.utils.datastructures import SortedDict -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.tree import Node +from django.utils import six from django.db import connections, DEFAULT_DB_ALIAS from django.db.models import signals from django.db.models.expressions import ExpressionNode @@ -602,22 +603,22 @@ class Query(object): # slight complexity here is handling fields that exist on parent # models. workset = {} - for model, values in seen.iteritems(): + for model, values in six.iteritems(seen): for field, m in model._meta.get_fields_with_model(): if field in values: continue add_to_dict(workset, m or model, field) - for model, values in must_include.iteritems(): + for model, values in six.iteritems(must_include): # If we haven't included a model in workset, we don't add the # corresponding must_include fields for that model, since an # empty set means "include all fields". That's why there's no # "else" branch here. if model in workset: workset[model].update(values) - for model, values in workset.iteritems(): + for model, values in six.iteritems(workset): callback(target, model, values) else: - for model, values in must_include.iteritems(): + for model, values in six.iteritems(must_include): if model in seen: seen[model].update(values) else: @@ -631,7 +632,7 @@ class Query(object): for model in orig_opts.get_parent_list(): if model not in seen: seen[model] = set() - for model, values in seen.iteritems(): + for model, values in six.iteritems(seen): callback(target, model, values) @@ -770,7 +771,7 @@ class Query(object): for k, aliases in self.join_map.items(): aliases = tuple([change_map.get(a, a) for a in aliases]) self.join_map[k] = aliases - for old_alias, new_alias in change_map.iteritems(): + for old_alias, new_alias in six.iteritems(change_map): alias_data = self.alias_map[old_alias] alias_data = alias_data._replace(rhs_alias=new_alias) self.alias_refcount[new_alias] = self.alias_refcount[old_alias] @@ -792,7 +793,7 @@ class Query(object): self.included_inherited_models[key] = change_map[alias] # 3. Update any joins that refer to the old alias. - for alias, data in self.alias_map.iteritems(): + for alias, data in six.iteritems(self.alias_map): lhs = data.lhs_alias if lhs in change_map: data = data._replace(lhs_alias=change_map[lhs]) @@ -842,7 +843,7 @@ class Query(object): count. Note that after execution, the reference counts are zeroed, so tables added in compiler will not be seen by this method. """ - return len([1 for count in self.alias_refcount.itervalues() if count]) + return len([1 for count in six.itervalues(self.alias_refcount) if count]) def join(self, connection, always_create=False, exclusions=(), promote=False, outer_if_first=False, nullable=False, reuse=None): @@ -1302,7 +1303,7 @@ class Query(object): field, model, direct, m2m = opts.get_field_by_name(f.name) break else: - names = opts.get_all_field_names() + self.aggregate_select.keys() + names = opts.get_all_field_names() + list(self.aggregate_select) raise FieldError("Cannot resolve keyword %r into field. " "Choices are: %s" % (name, ", ".join(names))) @@ -1571,7 +1572,7 @@ class Query(object): # Tag.objects.exclude(parent__parent__name='t1'), a tag with no parent # would otherwise be overlooked). active_positions = [pos for (pos, count) in - enumerate(query.alias_refcount.itervalues()) if count] + enumerate(six.itervalues(query.alias_refcount)) if count] if active_positions[-1] > 1: self.add_filter(('%s__isnull' % prefix, False), negate=True, trim=True, can_reuse=can_reuse) @@ -1660,8 +1661,8 @@ class Query(object): # from the model on which the lookup failed. raise else: - names = sorted(opts.get_all_field_names() + self.extra.keys() - + self.aggregate_select.keys()) + names = sorted(opts.get_all_field_names() + list(self.extra) + + list(self.aggregate_select)) raise FieldError("Cannot resolve keyword %r into field. " "Choices are: %s" % (name, ", ".join(names))) self.remove_inherited_models() @@ -1775,7 +1776,7 @@ class Query(object): else: param_iter = iter([]) for name, entry in select.items(): - entry = force_unicode(entry) + entry = force_text(entry) entry_params = [] pos = entry.find("%s") while pos != -1: diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 7b92394e90..937505b9b0 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -10,7 +10,8 @@ from django.db.models.sql.query import Query from django.db.models.sql.where import AND, Constraint from django.utils.datastructures import SortedDict from django.utils.functional import Promise -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text +from django.utils import six __all__ = ['DeleteQuery', 'UpdateQuery', 'InsertQuery', 'DateQuery', @@ -87,7 +88,7 @@ class UpdateQuery(Query): querysets. """ values_seq = [] - for name, val in values.iteritems(): + for name, val in six.iteritems(values): field, model, direct, m2m = self.model._meta.get_field_by_name(name) if not direct or m2m: raise FieldError('Cannot update model field %r (only non-relations and foreign keys permitted).' % field) @@ -104,7 +105,7 @@ class UpdateQuery(Query): saving models. """ # Check that no Promise object passes to the query. Refs #10498. - values_seq = [(value[0], value[1], force_unicode(value[2])) + values_seq = [(value[0], value[1], force_text(value[2])) if isinstance(value[2], Promise) else value for value in values_seq] self.values.extend(values_seq) @@ -129,7 +130,7 @@ class UpdateQuery(Query): if not self.related_updates: return [] result = [] - for model, values in self.related_updates.iteritems(): + for model, values in six.iteritems(self.related_updates): query = UpdateQuery(model) query.values = values if self.related_ids is not None: @@ -170,7 +171,7 @@ class InsertQuery(Query): for obj in objs: value = getattr(obj, field.attname) if isinstance(value, Promise): - setattr(obj, field.attname, force_unicode(value)) + setattr(obj, field.attname, force_text(value)) self.objs = objs self.raw = raw diff --git a/django/dispatch/saferef.py b/django/dispatch/saferef.py index 2a2c8739fb..d8728e219a 100644 --- a/django/dispatch/saferef.py +++ b/django/dispatch/saferef.py @@ -152,9 +152,10 @@ class BoundMethodWeakref(object): __repr__ = __str__ - def __nonzero__( self ): + def __bool__( self ): """Whether we are still a valid reference""" return self() is not None + __nonzero__ = __bool__ # Python 2 def __eq__(self, other): """Compare with another reference""" diff --git a/django/forms/extras/widgets.py b/django/forms/extras/widgets.py index 4e11a4ee06..c5ca1424c8 100644 --- a/django/forms/extras/widgets.py +++ b/django/forms/extras/widgets.py @@ -79,7 +79,7 @@ class SelectDateWidget(Widget): year_val, month_val, day_val = [int(v) for v in match.groups()] choices = [(i, i) for i in self.years] year_html = self.create_select(name, self.year_field, value, year_val, choices) - choices = MONTHS.items() + choices = list(six.iteritems(MONTHS)) month_html = self.create_select(name, self.month_field, value, month_val, choices) choices = [(i, i) for i in range(1, 32)] day_html = self.create_select(name, self.day_field, value, day_val, choices) diff --git a/django/forms/fields.py b/django/forms/fields.py index c4a675da74..7f0d26d1aa 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -23,7 +23,7 @@ from django.forms.widgets import (TextInput, PasswordInput, HiddenInput, NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget, FILE_INPUT_CONTRADICTION) from django.utils import formats -from django.utils.encoding import smart_unicode, force_unicode +from django.utils.encoding import smart_text, force_text from django.utils.ipv6 import clean_ipv6_address from django.utils import six from django.utils.translation import ugettext_lazy as _ @@ -78,13 +78,13 @@ class Field(object): # validators -- List of addtional validators to use # localize -- Boolean that specifies if the field should be localized. if label is not None: - label = smart_unicode(label) + label = smart_text(label) self.required, self.label, self.initial = required, label, initial self.show_hidden_initial = show_hidden_initial if help_text is None: self.help_text = '' else: - self.help_text = smart_unicode(help_text) + self.help_text = smart_text(help_text) widget = widget or self.widget if isinstance(widget, type): widget = widget() @@ -195,7 +195,7 @@ class CharField(Field): "Returns a Unicode object." if value in validators.EMPTY_VALUES: return '' - return smart_unicode(value) + return smart_text(value) def widget_attrs(self, widget): attrs = super(CharField, self).widget_attrs(widget) @@ -288,7 +288,7 @@ class DecimalField(Field): return None if self.localize: value = formats.sanitize_separators(value) - value = smart_unicode(value).strip() + value = smart_text(value).strip() try: value = Decimal(value) except DecimalException: @@ -333,7 +333,7 @@ class BaseTemporalField(Field): def to_python(self, value): # Try to coerce the value to unicode. - unicode_value = force_unicode(value, strings_only=True) + unicode_value = force_text(value, strings_only=True) if isinstance(unicode_value, six.text_type): value = unicode_value.strip() # If unicode, try to strptime against each input format. @@ -560,20 +560,10 @@ class ImageField(FileField): file = BytesIO(data['content']) try: - # load() is the only method that can spot a truncated JPEG, - # but it cannot be called sanely after verify() - trial_image = Image.open(file) - trial_image.load() - - # Since we're about to use the file again we have to reset the - # file object if possible. - if hasattr(file, 'seek') and callable(file.seek): - file.seek(0) - - # verify() is the only method that can spot a corrupt PNG, - # but it must be called immediately after the constructor - trial_image = Image.open(file) - trial_image.verify() + # load() could spot a truncated JPEG, but it loads the entire + # image in memory, which is a DoS vector. See #3848 and #18520. + # verify() must be called immediately after the constructor. + Image.open(file).verify() except ImportError: # Under PyPy, it is possible to import PIL. However, the underlying # _imaging C module isn't available, so an ImportError will be @@ -702,7 +692,7 @@ class ChoiceField(Field): "Returns a Unicode object." if value in validators.EMPTY_VALUES: return '' - return smart_unicode(value) + return smart_text(value) def validate(self, value): """ @@ -718,10 +708,10 @@ class ChoiceField(Field): if isinstance(v, (list, tuple)): # This is an optgroup, so look inside the group for options for k2, v2 in v: - if value == smart_unicode(k2): + if value == smart_text(k2): return True else: - if value == smart_unicode(k): + if value == smart_text(k): return True return False @@ -762,7 +752,7 @@ class MultipleChoiceField(ChoiceField): return [] elif not isinstance(value, (list, tuple)): raise ValidationError(self.error_messages['invalid_list']) - return [smart_unicode(val) for val in value] + return [smart_text(val) for val in value] def validate(self, value): """ diff --git a/django/forms/forms.py b/django/forms/forms.py index 4bc3ee9d26..45b758202a 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -12,7 +12,7 @@ from django.forms.util import flatatt, ErrorDict, ErrorList from django.forms.widgets import Media, media_property, TextInput, Textarea from django.utils.datastructures import SortedDict from django.utils.html import conditional_escape, format_html -from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode +from django.utils.encoding import StrAndUnicode, smart_text, force_text from django.utils.safestring import mark_safe from django.utils import six @@ -38,7 +38,7 @@ def get_declared_fields(bases, attrs, with_base_fields=True): used. The distinction is useful in ModelForm subclassing. Also integrates any additional media definitions """ - fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] + fields = [(field_name, attrs.pop(field_name)) for field_name, obj in list(six.iteritems(attrs)) if isinstance(obj, Field)] fields.sort(key=lambda x: x[1].creation_counter) # If this class is subclassing another Form, add that Form's fields. @@ -47,11 +47,11 @@ def get_declared_fields(bases, attrs, with_base_fields=True): if with_base_fields: for base in bases[::-1]: if hasattr(base, 'base_fields'): - fields = base.base_fields.items() + fields + fields = list(six.iteritems(base.base_fields)) + fields else: for base in bases[::-1]: if hasattr(base, 'declared_fields'): - fields = base.declared_fields.items() + fields + fields = list(six.iteritems(base.declared_fields)) + fields return SortedDict(fields) @@ -150,7 +150,7 @@ class BaseForm(StrAndUnicode): bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. if bf.is_hidden: if bf_errors: - top_errors.extend(['(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) + top_errors.extend(['(Hidden field %s) %s' % (name, force_text(e)) for e in bf_errors]) hidden_fields.append(six.text_type(bf)) else: # Create a 'class="..."' atribute if the row should have any @@ -160,10 +160,10 @@ class BaseForm(StrAndUnicode): html_class_attr = ' class="%s"' % css_classes if errors_on_separate_row and bf_errors: - output.append(error_row % force_unicode(bf_errors)) + output.append(error_row % force_text(bf_errors)) if bf.label: - label = conditional_escape(force_unicode(bf.label)) + label = conditional_escape(force_text(bf.label)) # Only add the suffix if the label does not end in # punctuation. if self.label_suffix: @@ -174,20 +174,20 @@ class BaseForm(StrAndUnicode): label = '' if field.help_text: - help_text = help_text_html % force_unicode(field.help_text) + help_text = help_text_html % force_text(field.help_text) else: help_text = '' output.append(normal_row % { - 'errors': force_unicode(bf_errors), - 'label': force_unicode(label), + 'errors': force_text(bf_errors), + 'label': force_text(label), 'field': six.text_type(bf), 'help_text': help_text, 'html_class_attr': html_class_attr }) if top_errors: - output.insert(0, error_row % force_unicode(top_errors)) + output.insert(0, error_row % force_text(top_errors)) if hidden_fields: # Insert any hidden fields in the last row. str_hidden = ''.join(hidden_fields) @@ -271,8 +271,6 @@ class BaseForm(StrAndUnicode): self._clean_fields() self._clean_form() self._post_clean() - if self._errors: - del self.cleaned_data def _clean_fields(self): for name, field in self.fields.items(): @@ -537,8 +535,8 @@ class BoundField(StrAndUnicode): associated Form has specified auto_id. Returns an empty string otherwise. """ auto_id = self.form.auto_id - if auto_id and '%s' in smart_unicode(auto_id): - return smart_unicode(auto_id) % self.html_name + if auto_id and '%s' in smart_text(auto_id): + return smart_text(auto_id) % self.html_name elif auto_id: return self.html_name return '' diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 240cf71f43..4ea8dc4ca9 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -65,9 +65,10 @@ class BaseFormSet(StrAndUnicode): def __len__(self): return len(self.forms) - def __nonzero__(self): + def __bool__(self): """All formsets have a management form which is not included in the length""" return True + __nonzero__ = __bool__ # Python 2 def _management_form(self): """Returns the ManagementForm instance for this FormSet.""" @@ -365,7 +366,7 @@ def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, attrs = {'form': form, 'extra': extra, 'can_order': can_order, 'can_delete': can_delete, 'max_num': max_num} - return type(form.__name__ + b'FormSet', (formset,), attrs) + return type(form.__name__ + str('FormSet'), (formset,), attrs) def all_valid(formsets): """Returns true if every formset in formsets is valid.""" diff --git a/django/forms/models.py b/django/forms/models.py index 4d56f1d38a..0b07d31d9a 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -13,11 +13,12 @@ from django.forms.formsets import BaseFormSet, formset_factory from django.forms.util import ErrorList from django.forms.widgets import (SelectMultiple, HiddenInput, MultipleHiddenInput, media_property) -from django.utils.encoding import smart_unicode, force_unicode +from django.utils.encoding import smart_text, force_text from django.utils.datastructures import SortedDict from django.utils import six from django.utils.text import get_text_list, capfirst from django.utils.translation import ugettext_lazy as _, ugettext +from django.utils import six __all__ = ( @@ -206,7 +207,7 @@ class ModelFormMetaclass(type): fields = fields_for_model(opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback) # make sure opts.fields doesn't specify an invalid field - none_model_fields = [k for k, v in fields.iteritems() if not v] + none_model_fields = [k for k, v in six.iteritems(fields) if not v] missing_fields = set(none_model_fields) - \ set(declared_fields.keys()) if missing_fields: @@ -389,10 +390,10 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None, parent = (object,) if hasattr(form, 'Meta'): parent = (form.Meta, object) - Meta = type(b'Meta', parent, attrs) + Meta = type(str('Meta'), parent, attrs) # Give this new form class a reasonable name. - class_name = model.__name__ + b'Form' + class_name = model.__name__ + str('Form') # Class attributes for the new form class. form_class_attrs = { @@ -506,7 +507,7 @@ class BaseModelFormSet(BaseFormSet): all_unique_checks = set() all_date_checks = set() for form in self.forms: - if not hasattr(form, 'cleaned_data'): + if not form.is_valid(): continue exclude = form._get_validation_exclusions() unique_checks, date_checks = form.instance._get_unique_checks(exclude=exclude) @@ -518,21 +519,21 @@ class BaseModelFormSet(BaseFormSet): for uclass, unique_check in all_unique_checks: seen_data = set() for form in self.forms: - # if the form doesn't have cleaned_data then we ignore it, - # it's already invalid - if not hasattr(form, "cleaned_data"): + if not form.is_valid(): continue # get data for each field of each of unique_check row_data = tuple([form.cleaned_data[field] for field in unique_check if field in form.cleaned_data]) if row_data and not None in row_data: - # if we've aready seen it then we have a uniqueness failure + # if we've already seen it then we have a uniqueness failure if row_data in seen_data: # poke error messages into the right places and mark # the form as invalid errors.append(self.get_unique_error_message(unique_check)) form._errors[NON_FIELD_ERRORS] = self.error_class([self.get_form_error()]) - del form.cleaned_data - break + # remove the data from the cleaned_data dict since it was invalid + for field in unique_check: + if field in form.cleaned_data: + del form.cleaned_data[field] # mark the data as seen seen_data.add(row_data) # iterate over each of the date checks now @@ -540,9 +541,7 @@ class BaseModelFormSet(BaseFormSet): seen_data = set() uclass, lookup, field, unique_for = date_check for form in self.forms: - # if the form doesn't have cleaned_data then we ignore it, - # it's already invalid - if not hasattr(self, 'cleaned_data'): + if not form.is_valid(): continue # see if we have data for both fields if (form.cleaned_data and form.cleaned_data[field] is not None @@ -556,14 +555,15 @@ class BaseModelFormSet(BaseFormSet): else: date_data = (getattr(form.cleaned_data[unique_for], lookup),) data = (form.cleaned_data[field],) + date_data - # if we've aready seen it then we have a uniqueness failure + # if we've already seen it then we have a uniqueness failure if data in seen_data: # poke error messages into the right places and mark # the form as invalid errors.append(self.get_date_error_message(date_check)) form._errors[NON_FIELD_ERRORS] = self.error_class([self.get_form_error()]) - del form.cleaned_data - break + # remove the data from the cleaned_data dict since it was invalid + del form.cleaned_data[field] + # mark the data as seen seen_data.add(data) if errors: raise ValidationError(errors) @@ -875,7 +875,7 @@ class InlineForeignKeyField(Field): orig = getattr(self.parent_instance, self.to_field) else: orig = self.parent_instance.pk - if force_unicode(value) != force_unicode(orig): + if force_text(value) != force_text(orig): raise ValidationError(self.error_messages['invalid_choice']) return self.parent_instance @@ -953,7 +953,7 @@ class ModelChoiceField(ChoiceField): generate the labels for the choices presented by this object. Subclasses can override this method to customize the display of the choices. """ - return smart_unicode(obj) + return smart_text(obj) def _get_choices(self): # If self._choices is set, then somebody must have manually set @@ -1025,9 +1025,9 @@ class ModelMultipleChoiceField(ModelChoiceField): except ValueError: raise ValidationError(self.error_messages['invalid_pk_value'] % pk) qs = self.queryset.filter(**{'%s__in' % key: value}) - pks = set([force_unicode(getattr(o, key)) for o in qs]) + pks = set([force_text(getattr(o, key)) for o in qs]) for val in value: - if force_unicode(val) not in pks: + if force_text(val) not in pks: raise ValidationError(self.error_messages['invalid_choice'] % val) # Since this overrides the inherited ModelChoiceField.clean # we run custom validators here @@ -1035,6 +1035,6 @@ class ModelMultipleChoiceField(ModelChoiceField): return qs def prepare_value(self, value): - if hasattr(value, '__iter__'): + if hasattr(value, '__iter__') and not isinstance(value, six.text_type): return [super(ModelMultipleChoiceField, self).prepare_value(v) for v in value] return super(ModelMultipleChoiceField, self).prepare_value(value) diff --git a/django/forms/util.py b/django/forms/util.py index 8cf03d38af..cd6b52df6f 100644 --- a/django/forms/util.py +++ b/django/forms/util.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.conf import settings from django.utils.html import format_html, format_html_join -from django.utils.encoding import StrAndUnicode, force_unicode +from django.utils.encoding import StrAndUnicode, force_text from django.utils.safestring import mark_safe from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -35,12 +35,12 @@ class ErrorDict(dict, StrAndUnicode): if not self: return '' return format_html('
      {0}
    ', format_html_join('', '
  • {0}{1}
  • ', - ((k, force_unicode(v)) + ((k, force_text(v)) for k, v in self.items()) )) def as_text(self): - return '\n'.join(['* %s\n%s' % (k, '\n'.join([' * %s' % force_unicode(i) for i in v])) for k, v in self.items()]) + return '\n'.join(['* %s\n%s' % (k, '\n'.join([' * %s' % force_text(i) for i in v])) for k, v in self.items()]) class ErrorList(list, StrAndUnicode): """ @@ -53,16 +53,16 @@ class ErrorList(list, StrAndUnicode): if not self: return '' return format_html('
      {0}
    ', format_html_join('', '
  • {0}
  • ', - ((force_unicode(e),) for e in self) + ((force_text(e),) for e in self) ) ) def as_text(self): if not self: return '' - return '\n'.join(['* %s' % force_unicode(e) for e in self]) + return '\n'.join(['* %s' % force_text(e) for e in self]) def __repr__(self): - return repr([force_unicode(e) for e in self]) + return repr([force_text(e) for e in self]) # Utilities for time zone support in DateTimeField et al. diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 6b1be37ec2..be9ac8eb8f 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -17,7 +17,7 @@ from django.forms.util import flatatt, to_current_timezone from django.utils.datastructures import MultiValueDict, MergeDict from django.utils.html import conditional_escape, format_html, format_html_join from django.utils.translation import ugettext, ugettext_lazy -from django.utils.encoding import StrAndUnicode, force_unicode +from django.utils.encoding import StrAndUnicode, force_text from django.utils.safestring import mark_safe from django.utils import six from django.utils import datetime_safe, formats @@ -63,8 +63,7 @@ class Media(StrAndUnicode): def render_css(self): # To keep rendering order consistent, we can't just iterate over items(). # We need to sort the keys, and iterate over the sorted list. - media = self._css.keys() - media.sort() + media = sorted(self._css.keys()) return chain(*[ [format_html('', self.absolute_path(path), medium) for path in self._css[medium]] @@ -110,9 +109,10 @@ class Media(StrAndUnicode): def media_property(cls): def _media(self): # Get the media property of the superclass, if it exists - if hasattr(super(cls, self), 'media'): - base = super(cls, self).media - else: + sup_cls = super(cls, self) + try: + base = sup_cls.media + except AttributeError: base = Media() # Get the media definition for this class @@ -223,7 +223,7 @@ class Widget(six.with_metaclass(MediaDefiningClass)): initial_value = '' else: initial_value = initial - if force_unicode(initial_value) != force_unicode(data_value): + if force_text(initial_value) != force_text(data_value): return True return False @@ -257,7 +257,7 @@ class Input(Widget): final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) if value != '': # Only add the 'value' attribute if a value is non-empty. - final_attrs['value'] = force_unicode(self._format_value(value)) + final_attrs['value'] = force_text(self._format_value(value)) return format_html('', flatatt(final_attrs)) class TextInput(Input): @@ -294,7 +294,7 @@ class MultipleHiddenInput(HiddenInput): id_ = final_attrs.get('id', None) inputs = [] for i, v in enumerate(value): - input_attrs = dict(value=force_unicode(v), **final_attrs) + input_attrs = dict(value=force_text(v), **final_attrs) if id_: # An ID attribute was given. Add a numeric index as a suffix # so that the inputs don't all have the same ID attribute. @@ -361,7 +361,7 @@ class ClearableFileInput(FileInput): template = self.template_with_initial substitutions['initial'] = format_html('{1}', value.url, - force_unicode(value)) + force_text(value)) if not self.is_required: checkbox_name = self.clear_checkbox_name(name) checkbox_id = self.clear_checkbox_id(checkbox_name) @@ -398,7 +398,7 @@ class Textarea(Widget): final_attrs = self.build_attrs(attrs, name=name) return format_html('{1}', flatatt(final_attrs), - force_unicode(value)) + force_text(value)) class DateInput(Input): input_type = 'text' @@ -515,7 +515,7 @@ class CheckboxInput(Widget): final_attrs['checked'] = 'checked' if not (value is True or value is False or value is None or value == ''): # Only add the 'value' attribute if a value is non-empty. - final_attrs['value'] = force_unicode(value) + final_attrs['value'] = force_text(value) return format_html('', flatatt(final_attrs)) def value_from_datadict(self, data, files, name): @@ -556,7 +556,7 @@ class Select(Widget): return mark_safe('\n'.join(output)) def render_option(self, selected_choices, option_value, option_label): - option_value = force_unicode(option_value) + option_value = force_text(option_value) if option_value in selected_choices: selected_html = mark_safe(' selected="selected"') if not self.allow_multiple_selected: @@ -567,15 +567,15 @@ class Select(Widget): return format_html('', option_value, selected_html, - force_unicode(option_label)) + force_text(option_label)) def render_options(self, choices, selected_choices): # Normalize to strings. - selected_choices = set(force_unicode(v) for v in selected_choices) + selected_choices = set(force_text(v) for v in selected_choices) output = [] for option_value, option_label in chain(self.choices, choices): if isinstance(option_label, (list, tuple)): - output.append(format_html('', force_unicode(option_value))) + output.append(format_html('', force_text(option_value))) for option in option_label: output.append(self.render_option(selected_choices, *option)) output.append('') @@ -643,8 +643,8 @@ class SelectMultiple(Select): data = [] if len(initial) != len(data): return True - initial_set = set([force_unicode(value) for value in initial]) - data_set = set([force_unicode(value) for value in data]) + initial_set = set([force_text(value) for value in initial]) + data_set = set([force_text(value) for value in data]) return data_set != initial_set class RadioInput(SubWidget): @@ -656,8 +656,8 @@ class RadioInput(SubWidget): def __init__(self, name, value, attrs, choice, index): self.name, self.value = name, value self.attrs = attrs - self.choice_value = force_unicode(choice[0]) - self.choice_label = force_unicode(choice[1]) + self.choice_value = force_text(choice[0]) + self.choice_label = force_text(choice[1]) self.index = index def __unicode__(self): @@ -671,7 +671,7 @@ class RadioInput(SubWidget): label_for = format_html(' for="{0}_{1}"', self.attrs['id'], self.index) else: label_for = '' - choice_label = force_unicode(self.choice_label) + choice_label = force_text(self.choice_label) return format_html('{1} {2}', label_for, self.tag(), choice_label) def is_checked(self): @@ -709,7 +709,7 @@ class RadioFieldRenderer(StrAndUnicode): """Outputs a - Vote again? + Vote again? Now, go to ``/polls/1/`` in your browser and vote in the poll. You should see a results page that gets updated each time you vote. If you submit the form @@ -238,11 +238,13 @@ Change it like so:: ListView.as_view( queryset=Poll.objects.order_by('-pub_date')[:5], context_object_name='latest_poll_list', - template_name='polls/index.html')), + template_name='polls/index.html'), + name='poll_index'), url(r'^(?P\d+)/$', DetailView.as_view( model=Poll, - template_name='polls/detail.html')), + template_name='polls/detail.html'), + name='poll_detail'), url(r'^(?P\d+)/results/$', DetailView.as_view( model=Poll, @@ -265,8 +267,8 @@ two views abstract the concepts of "display a list of objects" and ``"pk"``, so we've changed ``poll_id`` to ``pk`` for the generic views. -* We've added a name, ``poll_results``, to the results view so - that we have a way to refer to its URL later on (see the +* We've added the ``name`` argument to the views (e.g. ``name='poll_results'``) + so that we have a way to refer to their URL later on (see the documentation about :ref:`naming URL patterns ` for information). We're also using the :func:`~django.conf.urls.url` function from @@ -317,6 +319,13 @@ function anymore -- generic views can be (and are) used multiple times return HttpResponseRedirect(reverse('poll_results', args=(p.id,))) +The same rule apply for the :ttag:`url` template tag. For example in the +``results.html`` template: + +.. code-block:: html+django + + Vote again? + Run the server, and use your new polling app based on generic views. For full details on generic views, see the :doc:`generic views documentation diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt index d5df369fb3..a65a59bc8b 100644 --- a/docs/ref/class-based-views/generic-editing.txt +++ b/docs/ref/class-based-views/generic-editing.txt @@ -2,7 +2,28 @@ Generic editing views ===================== -The views described here provide a foundation for editing content. +The following views are described on this page and provide a foundation for +editing content: + +* :class:`django.views.generic.edit.FormView` +* :class:`django.views.generic.edit.CreateView` +* :class:`django.views.generic.edit.UpdateView` +* :class:`django.views.generic.edit.DeleteView` + +.. note:: + + Some of the examples on this page assume that a model titled 'Author' + has been defined. For these cases we assume the following has been defined + in `myapp/models.py`:: + + from django import models + from django.core.urlresolvers import reverse + + class Author(models.Model): + name = models.CharField(max_length=200) + + def get_absolute_url(self): + return reverse('author-detail', kwargs={'pk': self.pk}) .. class:: django.views.generic.edit.FormView @@ -11,6 +32,8 @@ The views described here provide a foundation for editing content. **Ancestors (MRO)** + This view inherits methods and attributes from the following views: + * :class:`django.views.generic.edit.FormView` * :class:`django.views.generic.base.TemplateResponseMixin` * :class:`django.views.generic.edit.BaseFormView` @@ -18,6 +41,35 @@ The views described here provide a foundation for editing content. * :class:`django.views.generic.edit.ProcessFormView` * :class:`django.views.generic.base.View` + **Example forms.py**:: + + from django import forms + + class ContactForm(forms.Form): + name = forms.CharField() + message = forms.CharField(widget=forms.Textarea) + + def send_email(self): + # send email using the self.cleaned_data dictionary + pass + + **Example views.py**:: + + from myapp.forms import ContactForm + from django.views.generic.edit import FormView + + class ContactView(FormView): + template_name = 'contact.html' + form_class = ContactForm + success_url = '/thanks/' + + def form_valid(self, form): + # This method is called when valid form data has been POSTed. + # It should return an HttpResponse. + form.send_email() + return super(ContactView, self).form_valid(form) + + .. class:: django.views.generic.edit.CreateView A view that displays a form for creating an object, redisplaying the form @@ -25,6 +77,8 @@ The views described here provide a foundation for editing content. **Ancestors (MRO)** + This view inherits methods and attributes from the following views: + * :class:`django.views.generic.edit.CreateView` * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` * :class:`django.views.generic.base.TemplateResponseMixin` @@ -35,6 +89,24 @@ The views described here provide a foundation for editing content. * :class:`django.views.generic.edit.ProcessFormView` * :class:`django.views.generic.base.View` + **Attributes** + + .. attribute:: template_name_suffix + + The CreateView page displayed to a GET request uses a + ``template_name_suffix`` of ``'_form.html'``. For + example, changing this attribute to ``'_create_form.html'`` for a view + creating objects for the the example `Author` model would cause the the + default `template_name` to be ``'myapp/author_create_form.html'``. + + **Example views.py**:: + + from django.views.generic.edit import CreateView + from myapp.models import Author + + class AuthorCreate(CreateView): + model = Author + .. class:: django.views.generic.edit.UpdateView A view that displays a form for editing an existing object, redisplaying @@ -44,6 +116,8 @@ The views described here provide a foundation for editing content. **Ancestors (MRO)** + This view inherits methods and attributes from the following views: + * :class:`django.views.generic.edit.UpdateView` * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` * :class:`django.views.generic.base.TemplateResponseMixin` @@ -54,6 +128,24 @@ The views described here provide a foundation for editing content. * :class:`django.views.generic.edit.ProcessFormView` * :class:`django.views.generic.base.View` + **Attributes** + + .. attribute:: template_name_suffix + + The UpdateView page displayed to a GET request uses a + ``template_name_suffix`` of ``'_form.html'``. For + example, changing this attribute to ``'_update_form.html'`` for a view + updating objects for the the example `Author` model would cause the the + default `template_name` to be ``'myapp/author_update_form.html'``. + + **Example views.py**:: + + from django.views.generic.edit import UpdateView + from myapp.models import Author + + class AuthorUpdate(UpdateView): + model = Author + .. class:: django.views.generic.edit.DeleteView A view that displays a confirmation page and deletes an existing object. @@ -63,6 +155,8 @@ The views described here provide a foundation for editing content. **Ancestors (MRO)** + This view inherits methods and attributes from the following views: + * :class:`django.views.generic.edit.DeleteView` * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` * :class:`django.views.generic.base.TemplateResponseMixin` @@ -72,7 +166,23 @@ The views described here provide a foundation for editing content. * :class:`django.views.generic.detail.SingleObjectMixin` * :class:`django.views.generic.base.View` - **Notes** + **Attributes** - * The delete confirmation page displayed to a GET request uses a - ``template_name_suffix`` of ``'_confirm_delete'``. + .. attribute:: template_name_suffix + + The DeleteView page displayed to a GET request uses a + ``template_name_suffix`` of ``'_confirm_delete.html'``. For + example, changing this attribute to ``'_check_delete.html'`` for a view + deleting objects for the the example `Author` model would cause the the + default `template_name` to be ``'myapp/author_check_delete.html'``. + + + **Example views.py**:: + + from django.views.generic.edit import DeleteView + from django.core.urlresolvers import reverse_lazy + from myapp.models import Author + + class AuthorDelete(DeleteView): + model = Author + success_url = reverse_lazy('author-list') diff --git a/docs/ref/class-based-views/index.txt b/docs/ref/class-based-views/index.txt index c10e66b396..f2271d2506 100644 --- a/docs/ref/class-based-views/index.txt +++ b/docs/ref/class-based-views/index.txt @@ -32,9 +32,9 @@ A class-based view is deployed into a URL pattern using the Arguments passed to a view are shared between every instance of a view. This means that you shoudn't use a list, dictionary, or any other - variable object as an argument to a view. If you did, the actions of - one user visiting your view could have an effect on subsequent users - visiting the same view. + mutable object as an argument to a view. If you do and the shared object + is modified, the actions of one user visiting your view could have an + effect on subsequent users visiting the same view. Any argument passed into :meth:`~View.as_view()` will be assigned onto the instance that is used to service a request. Using the previous example, diff --git a/docs/ref/class-based-views/mixins-editing.txt b/docs/ref/class-based-views/mixins-editing.txt index 7258893d63..89610889db 100644 --- a/docs/ref/class-based-views/mixins-editing.txt +++ b/docs/ref/class-based-views/mixins-editing.txt @@ -2,6 +2,18 @@ Editing mixins ============== +The following mixins are used to construct Django's editing views: + +* :class:`django.views.generic.edit.FormMixin` +* :class:`django.views.generic.edit.ModelFormMixin` +* :class:`django.views.generic.edit.ProcessFormView` +* :class:`django.views.generic.edit.DeletionMixin` + +.. note:: + + Examples of how these are combined into editing views can be found at + the documentation on ``Generic editing views``. + .. class:: django.views.generic.edit.FormMixin A mixin class that provides facilities for creating and displaying forms. diff --git a/docs/ref/contrib/admin/_images/article_actions.png b/docs/ref/contrib/admin/_images/article_actions.png index 78a78ae494..1d35e60e5d 100644 Binary files a/docs/ref/contrib/admin/_images/article_actions.png and b/docs/ref/contrib/admin/_images/article_actions.png differ diff --git a/docs/ref/contrib/admin/_images/article_actions_message.png b/docs/ref/contrib/admin/_images/article_actions_message.png index 6ea9439b8e..16a2d0e197 100644 Binary files a/docs/ref/contrib/admin/_images/article_actions_message.png and b/docs/ref/contrib/admin/_images/article_actions_message.png differ diff --git a/docs/ref/contrib/admin/_images/user_actions.png b/docs/ref/contrib/admin/_images/user_actions.png index fdbe2ad897..22d40e0181 100644 Binary files a/docs/ref/contrib/admin/_images/user_actions.png and b/docs/ref/contrib/admin/_images/user_actions.png differ diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index f28aa4687b..4d39981a4d 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1266,11 +1266,11 @@ provided some extra mapping data that would not otherwise be available:: # ... pass - def change_view(self, request, object_id, extra_context=None): + def change_view(self, request, object_id, form_url='', extra_context=None): extra_context = extra_context or {} extra_context['osm_data'] = self.get_osm_info() return super(MyModelAdmin, self).change_view(request, object_id, - extra_context=extra_context) + form_url, extra_context=extra_context) .. versionadded:: 1.4 diff --git a/docs/ref/contrib/comments/custom.txt b/docs/ref/contrib/comments/custom.txt index 5007ddff69..0ef37a9a0b 100644 --- a/docs/ref/contrib/comments/custom.txt +++ b/docs/ref/contrib/comments/custom.txt @@ -51,9 +51,9 @@ To make this kind of customization, we'll need to do three things: custom :setting:`COMMENTS_APP`. So, carrying on the example above, we're dealing with a typical app structure in -the ``my_custom_app`` directory:: +the ``my_comment_app`` directory:: - my_custom_app/ + my_comment_app/ __init__.py models.py forms.py @@ -98,11 +98,11 @@ Django provides a couple of "helper" classes to make writing certain types of custom comment forms easier; see :mod:`django.contrib.comments.forms` for more. -Finally, we'll define a couple of methods in ``my_custom_app/__init__.py`` to +Finally, we'll define a couple of methods in ``my_comment_app/__init__.py`` to point Django at these classes we've created:: - from my_comments_app.models import CommentWithTitle - from my_comments_app.forms import CommentFormWithTitle + from my_comment_app.models import CommentWithTitle + from my_comment_app.forms import CommentFormWithTitle def get_model(): return CommentWithTitle diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index eda9617381..f4e706d275 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -75,6 +75,17 @@ return a :class:`GEOSGeometry` object from an input string or a file:: >>> pnt = fromfile('/path/to/pnt.wkt') >>> pnt = fromfile(open('/path/to/pnt.wkt')) +.. _geos-exceptions-in-logfile: + +.. admonition:: My logs are filled with GEOS-related errors + + You find many ``TypeError`` or ``AttributeError`` exceptions filling your + Web server's log files. This generally means that you are creating GEOS + objects at the top level of some of your Python modules. Then, due to a race + condition in the garbage collector, your module is garbage collected before + the GEOS object. To prevent this, create :class:`GEOSGeometry` objects + inside the local scope of your functions/methods. + Geometries are Pythonic ----------------------- :class:`GEOSGeometry` objects are 'Pythonic', in other words components may diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 5ee6d5153d..72bd72a6f8 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -191,6 +191,8 @@ GEOS C library. For example: The setting must be the *full* path to the **C** shared library; in other words you want to use ``libgeos_c.so``, not ``libgeos.so``. +See also :ref:`My logs are filled with GEOS-related errors `. + .. _proj4: PROJ.4 diff --git a/docs/ref/contrib/humanize.txt b/docs/ref/contrib/humanize.txt index cdc3009a51..57978288b1 100644 --- a/docs/ref/contrib/humanize.txt +++ b/docs/ref/contrib/humanize.txt @@ -103,9 +103,9 @@ naturaltime .. versionadded:: 1.4 For datetime values, returns a string representing how many seconds, -minutes or hours ago it was -- falling back to a longer date format if the -value is more than a day old. In case the datetime value is in the future -the return value will automatically use an appropriate phrase. +minutes or hours ago it was -- falling back to the :tfilter:`timesince` +format if the value is more than a day old. In case the datetime value is in +the future the return value will automatically use an appropriate phrase. Examples (when 'now' is 17 Feb 2007 16:30:00): @@ -115,13 +115,14 @@ Examples (when 'now' is 17 Feb 2007 16:30:00): * ``17 Feb 2007 16:25:35`` becomes ``4 minutes ago``. * ``17 Feb 2007 15:30:29`` becomes ``an hour ago``. * ``17 Feb 2007 13:31:29`` becomes ``2 hours ago``. -* ``16 Feb 2007 13:31:29`` becomes ``1 day ago``. +* ``16 Feb 2007 13:31:29`` becomes ``1 day, 3 hours ago``. * ``17 Feb 2007 16:30:30`` becomes ``29 seconds from now``. * ``17 Feb 2007 16:31:00`` becomes ``a minute from now``. * ``17 Feb 2007 16:34:35`` becomes ``4 minutes from now``. * ``17 Feb 2007 16:30:29`` becomes ``an hour from now``. * ``17 Feb 2007 18:31:29`` becomes ``2 hours from now``. * ``18 Feb 2007 16:31:29`` becomes ``1 day from now``. +* ``26 Feb 2007 18:31:29`` becomes ``1 week, 2 days from now``. .. templatefilter:: ordinal diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index 6929a3b0d0..4cf90ee381 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -5,12 +5,14 @@ The messages framework .. module:: django.contrib.messages :synopsis: Provides cookie- and session-based temporary message storage. -Django provides full support for cookie- and session-based messaging, for -both anonymous and authenticated clients. The messages framework allows you -to temporarily store messages in one request and retrieve them for display -in a subsequent request (usually the next one). Every message is tagged -with a specific ``level`` that determines its priority (e.g., ``info``, -``warning``, or ``error``). +Quite commonly in web applications, you may need to display a one-time +notification message (also know as "flash message") to the user after +processing a form or some other types of user input. For this, Django provides +full support for cookie- and session-based messaging, for both anonymous and +authenticated users. The messages framework allows you to temporarily store +messages in one request and retrieve them for display in a subsequent request +(usually the next one). Every message is tagged with a specific ``level`` that +determines its priority (e.g., ``info``, ``warning``, or ``error``). Enabling messages ================= diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 74e6b48f07..92b5665bea 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -238,7 +238,7 @@ to you, the developer, to handle the fact that you will receive bytestrings if you configure your table(s) to use ``utf8_bin`` collation. Django itself should mostly work smoothly with such columns (except for the ``contrib.sessions`` ``Session`` and ``contrib.admin`` ``LogEntry`` tables described below), but -your code must be prepared to call ``django.utils.encoding.smart_unicode()`` at +your code must be prepared to call ``django.utils.encoding.smart_text()`` at times if it really wants to work with consistent data -- Django will not do this for you (the database backend layer and the model population layer are separated internally so the database layer doesn't know it needs to make this diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index c4295c68d5..5ff7ecba2c 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -747,7 +747,7 @@ use the ``--plain`` option, like so:: If you would like to specify either IPython or bpython as your interpreter if you have both installed you can specify an alternative interpreter interface -with the ``-i`` or ``--interface`` options like so:: +with the ``-i`` or ``--interface`` options like so: IPython:: diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index 50488b026a..777d73e015 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -199,8 +199,8 @@ Note that any text-based field -- such as ``CharField`` or ``EmailField`` -- always cleans the input into a Unicode string. We'll cover the encoding implications later in this document. -If your data does *not* validate, your ``Form`` instance will not have a -``cleaned_data`` attribute:: +If your data does *not* validate, the ``cleaned_data`` dictionary contains +only the valid fields:: >>> data = {'subject': '', ... 'message': 'Hi there', @@ -210,9 +210,12 @@ If your data does *not* validate, your ``Form`` instance will not have a >>> f.is_valid() False >>> f.cleaned_data - Traceback (most recent call last): - ... - AttributeError: 'ContactForm' object has no attribute 'cleaned_data' + {'cc_myself': True, 'message': u'Hi there'} + +.. versionchanged:: 1.5 + +Until Django 1.5, the ``cleaned_data`` attribute wasn't defined at all when +the ``Form`` didn't validate. ``cleaned_data`` will always *only* contain a key for fields defined in the ``Form``, even if you pass extra data when you define the ``Form``. In this @@ -232,9 +235,9 @@ but ``cleaned_data`` contains only the form's fields:: >>> f.cleaned_data # Doesn't contain extra_field_1, etc. {'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'} -``cleaned_data`` will include a key and value for *all* fields defined in the -``Form``, even if the data didn't include a value for fields that are not -required. In this example, the data dictionary doesn't include a value for the +When the ``Form`` is valid, ``cleaned_data`` will include a key and value for +*all* its fields, even if the data didn't include a value for some optional +fields. In this example, the data dictionary doesn't include a value for the ``nick_name`` field, but ``cleaned_data`` includes it, with an empty value:: >>> class OptionalPersonForm(Form): @@ -583,7 +586,7 @@ lazy developers -- they're not the only way a form object can be displayed. Used to display HTML or access attributes for a single field of a :class:`Form` instance. - + The :meth:`__unicode__` and :meth:`__str__` methods of this object displays the HTML for this field. diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index 97883d7880..95424d0cd0 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -362,7 +362,9 @@ Secondly, once we have decided that the combined data in the two fields we are considering aren't valid, we must remember to remove them from the ``cleaned_data``. -In fact, Django will currently completely wipe out the ``cleaned_data`` -dictionary if there are any errors in the form. However, this behavior may -change in the future, so it's not a bad idea to clean up after yourself in the -first place. +.. versionchanged:: 1.5 + +Django used to remove the ``cleaned_data`` attribute entirely if there were +any errors in the form. Since version 1.5, ``cleaned_data`` is present even if +the form doesn't validate, but it contains only field values that did +validate. diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index c280202ebf..a6ea9a6c41 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -146,7 +146,7 @@ Locale middleware Enables language selection based on data from the request. It customizes content for each user. See the :doc:`internationalization documentation -`. +`. Message middleware ------------------ diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 5039ba4373..a43163c5e9 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -86,42 +86,43 @@ field. If this is given, Django's admin will use a select box instead of the standard text field and will limit choices to the choices given. -A choices list looks like this:: +A choices list is an iterable of 2-tuples; the first element in each +tuple is the actual value to be stored, and the second element is the +human-readable name. For example:: YEAR_IN_SCHOOL_CHOICES = ( ('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), - ('GR', 'Graduate'), ) -The first element in each tuple is the actual value to be stored. The second -element is the human-readable name for the option. +Generally, it's best to define choices inside a model class, and to +define a suitably-named constant for each value:: -The choices list can be defined either as part of your model class:: - - class Foo(models.Model): + class Student(models.Model): + FRESHMAN = 'FR' + SOPHOMORE = 'SO' + JUNIOR = 'JR' + SENIOR = 'SR' YEAR_IN_SCHOOL_CHOICES = ( - ('FR', 'Freshman'), - ('SO', 'Sophomore'), - ('JR', 'Junior'), - ('SR', 'Senior'), - ('GR', 'Graduate'), + (FRESHMAN, 'Freshman'), + (SOPHOMORE, 'Sophomore'), + (JUNIOR, 'Junior'), + (SENIOR, 'Senior'), ) - year_in_school = models.CharField(max_length=2, choices=YEAR_IN_SCHOOL_CHOICES) + year_in_school = models.CharField(max_length=2, + choices=YEAR_IN_SCHOOL_CHOICES, + default=FRESHMAN) -or outside your model class altogether:: + def is_upperclass(self): + return self.year_in_school in (self.JUNIOR, self.SENIOR) - YEAR_IN_SCHOOL_CHOICES = ( - ('FR', 'Freshman'), - ('SO', 'Sophomore'), - ('JR', 'Junior'), - ('SR', 'Senior'), - ('GR', 'Graduate'), - ) - class Foo(models.Model): - year_in_school = models.CharField(max_length=2, choices=YEAR_IN_SCHOOL_CHOICES) +Though you can define a choices list outside of a model class and then +refer to it, defining the choices and names for each choice inside the +model class keeps all of that information with the class that uses it, +and makes the choices easy to reference (e.g, ``Student.SOPHOMORE`` +will work anywhere that the ``Student`` model has been imported). You can also collect your available choices into named groups that can be used for organizational purposes:: @@ -1002,9 +1003,10 @@ define the details of how the relation works. `; and when you do so :ref:`some special syntax ` is available. - If you'd prefer Django didn't create a backwards relation, set ``related_name`` - to ``'+'``. For example, this will ensure that the ``User`` model won't get a - backwards relation to this model:: + If you'd prefer Django not to create a backwards relation, set + ``related_name`` to ``'+'`` or end it with ``'+'``. For example, this will + ensure that the ``User`` model won't have a backwards relation to this + model:: user = models.ForeignKey(User, related_name='+') @@ -1095,6 +1097,13 @@ that control how the relationship functions. Same as :attr:`ForeignKey.related_name`. + If you have more than one ``ManyToManyField`` pointing to the same model + and want to suppress the backwards relations, set each ``related_name`` + to a unique value ending with ``'+'``:: + + users = models.ManyToManyField(User, related_name='u+') + referents = models.ManyToManyField(User, related_name='ref+') + .. attribute:: ManyToManyField.limit_choices_to Same as :attr:`ForeignKey.limit_choices_to`. diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 509ea9d30e..14541ad0d1 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -453,9 +453,9 @@ using ``__str__()`` like this:: last_name = models.CharField(max_length=50) def __str__(self): - # Note use of django.utils.encoding.smart_str() here because + # Note use of django.utils.encoding.smart_bytes() here because # first_name and last_name will be unicode strings. - return smart_str('%s %s' % (self.first_name, self.last_name)) + return smart_bytes('%s %s' % (self.first_name, self.last_name)) ``get_absolute_url`` -------------------- diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 72d60453c3..531ff33da2 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -183,7 +183,7 @@ compose a prefix, version and key into a final cache key. The default implementation is equivalent to the function:: def make_key(key, key_prefix, version): - return ':'.join([key_prefix, str(version), smart_str(key)]) + return ':'.join([key_prefix, str(version), smart_bytes(key)]) You may use any key function you want, as long as it has the same argument signature. diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 500a47c6f1..072eebf69f 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1184,7 +1184,7 @@ Removes all values of arg from the given string. For example:: - {{ value|cut:" "}} + {{ value|cut:" " }} If ``value`` is ``"String with spaces"``, the output will be ``"Stringwithspaces"``. diff --git a/docs/ref/unicode.txt b/docs/ref/unicode.txt index b9253e70b3..ffab647379 100644 --- a/docs/ref/unicode.txt +++ b/docs/ref/unicode.txt @@ -129,7 +129,7 @@ Conversion functions The ``django.utils.encoding`` module contains a few functions that are handy for converting back and forth between Unicode and bytestrings. -* ``smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict')`` +* ``smart_text(s, encoding='utf-8', strings_only=False, errors='strict')`` converts its input to a Unicode string. The ``encoding`` parameter specifies the input encoding. (For example, Django uses this internally when processing form input data, which might not be UTF-8 encoded.) The @@ -139,27 +139,27 @@ for converting back and forth between Unicode and bytestrings. that are accepted by Python's ``unicode()`` function for its error handling. - If you pass ``smart_unicode()`` an object that has a ``__unicode__`` + If you pass ``smart_text()`` an object that has a ``__unicode__`` method, it will use that method to do the conversion. -* ``force_unicode(s, encoding='utf-8', strings_only=False, - errors='strict')`` is identical to ``smart_unicode()`` in almost all +* ``force_text(s, encoding='utf-8', strings_only=False, + errors='strict')`` is identical to ``smart_text()`` in almost all cases. The difference is when the first argument is a :ref:`lazy - translation ` instance. While ``smart_unicode()`` - preserves lazy translations, ``force_unicode()`` forces those objects to a + translation ` instance. While ``smart_text()`` + preserves lazy translations, ``force_text()`` forces those objects to a Unicode string (causing the translation to occur). Normally, you'll want - to use ``smart_unicode()``. However, ``force_unicode()`` is useful in + to use ``smart_text()``. However, ``force_text()`` is useful in template tags and filters that absolutely *must* have a string to work with, not just something that can be converted to a string. -* ``smart_str(s, encoding='utf-8', strings_only=False, errors='strict')`` - is essentially the opposite of ``smart_unicode()``. It forces the first +* ``smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict')`` + is essentially the opposite of ``smart_text()``. It forces the first argument to a bytestring. The ``strings_only`` parameter has the same - behavior as for ``smart_unicode()`` and ``force_unicode()``. This is + behavior as for ``smart_text()`` and ``force_text()``. This is slightly different semantics from Python's builtin ``str()`` function, but the difference is needed in a few places within Django's internals. -Normally, you'll only need to use ``smart_unicode()``. Call it as early as +Normally, you'll only need to use ``smart_text()``. Call it as early as possible on any input data that might be either Unicode or a bytestring, and from then on, you can treat the result as always being Unicode. @@ -324,7 +324,7 @@ A couple of tips to remember when writing your own template tags and filters: * Always return Unicode strings from a template tag's ``render()`` method and from template filters. -* Use ``force_unicode()`` in preference to ``smart_unicode()`` in these +* Use ``force_text()`` in preference to ``smart_text()`` in these places. Tag rendering and filter calls occur as the template is being rendered, so there is no advantage to postponing the conversion of lazy translation objects into strings. It's easier to work solely with Unicode diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index c2f2025bc3..b6cb1035d3 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -178,33 +178,53 @@ The functions defined in this module share the following properties: .. class:: StrAndUnicode - A class whose ``__str__`` returns its ``__unicode__`` as a UTF-8 - bytestring. Useful as a mix-in. + A class that derives ``__str__`` from ``__unicode__``. -.. function:: smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict') + On Python 2, ``__str__`` returns the output of ``__unicode__`` encoded as + a UTF-8 bytestring. On Python 3, ``__str__`` returns the output of + ``__unicode__``. - Returns a ``unicode`` object representing ``s``. Treats bytestrings using - the 'encoding' codec. + Useful as a mix-in. If you support Python 2 and 3 with a single code base, + you can inherit this mix-in and just define ``__unicode__``. + +.. function:: smart_text(s, encoding='utf-8', strings_only=False, errors='strict') + + .. versionadded:: 1.5 + + Returns a text object representing ``s`` -- ``unicode`` on Python 2 and + ``str`` on Python 3. Treats bytestrings using the ``encoding`` codec. If ``strings_only`` is ``True``, don't convert (some) non-string-like objects. +.. function:: smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict') + + Historical name of :func:`smart_text`. Only available under Python 2. + .. function:: is_protected_type(obj) Determine if the object instance is of a protected type. Objects of protected types are preserved as-is when passed to - ``force_unicode(strings_only=True)``. + ``force_text(strings_only=True)``. -.. function:: force_unicode(s, encoding='utf-8', strings_only=False, errors='strict') +.. function:: force_text(s, encoding='utf-8', strings_only=False, errors='strict') - Similar to ``smart_unicode``, except that lazy instances are resolved to + .. versionadded:: 1.5 + + Similar to ``smart_text``, except that lazy instances are resolved to strings, rather than kept as lazy objects. If ``strings_only`` is ``True``, don't convert (some) non-string-like objects. -.. function:: smart_str(s, encoding='utf-8', strings_only=False, errors='strict') +.. function:: force_unicode(s, encoding='utf-8', strings_only=False, errors='strict') + + Historical name of :func:`force_text`. Only available under Python 2. + +.. function:: smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict') + + .. versionadded:: 1.5 Returns a bytestring version of ``s``, encoded as specified in ``encoding``. @@ -212,6 +232,14 @@ The functions defined in this module share the following properties: If ``strings_only`` is ``True``, don't convert (some) non-string-like objects. +.. function:: smart_str(s, encoding='utf-8', strings_only=False, errors='strict') + + Alias of :func:`smart_bytes` on Python 2 and :func:`smart_text` on Python + 3. This function always returns a :class:`str`. + + For instance, this is suitable for writing to :attr:`sys.stdout` on + Python 2 and 3. + .. function:: iri_to_uri(iri) Convert an Internationalized Resource Identifier (IRI) portion to a URI @@ -406,7 +434,7 @@ escaping HTML. Returns the given text with ampersands, quotes and angle brackets encoded for use in HTML. The input is first passed through - :func:`~django.utils.encoding.force_unicode` and the output has + :func:`~django.utils.encoding.force_text` and the output has :func:`~django.utils.safestring.mark_safe` applied. .. function:: conditional_escape(text) @@ -448,7 +476,7 @@ escaping HTML. interpolation, some of the formatting options provided by `str.format`_ (e.g. number formatting) will not work, since all arguments are passed through :func:`conditional_escape` which (ultimately) calls - :func:`~django.utils.encoding.force_unicode` on the values. + :func:`~django.utils.encoding.force_text` on the values. .. _str.format: http://docs.python.org/library/stdtypes.html#str.format @@ -504,11 +532,13 @@ escaping HTML. .. function:: base36_to_int(s) - Converts a base 36 string to an integer. + Converts a base 36 string to an integer. On Python 2 the output is + guaranteed to be an :class:`int` and not a :class:`long`. .. function:: int_to_base36(i) - Converts a positive integer less than sys.maxint to a base 36 string. + Converts a positive integer to a base 36 string. On Python 2 ``i`` must be + smaller than :attr:`sys.maxint`. ``django.utils.safestring`` =========================== diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index aae8b25e07..f789ebde40 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -107,7 +107,7 @@ Django 1.5 also includes several smaller improvements worth noting: connect to more than one signal by supplying a list of signals. * :meth:`QuerySet.bulk_create() - ` has now a batch_size + ` now has a batch_size argument. By default the batch_size is unlimited except for SQLite where single batch is limited so that 999 parameters per query isn't exceeded. @@ -161,7 +161,7 @@ If you have written a :ref:`custom password hasher `, your ``encode()``, ``verify()`` or ``safe_summary()`` methods should accept Unicode parameters (``password``, ``salt`` or ``encoded``). If any of the hashing methods need byte strings, you can use the -:func:`~django.utils.encoding.smart_str` utility to encode the strings. +:func:`~django.utils.encoding.smart_bytes` utility to encode the strings. Validation of previous_page_number and next_page_number ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -239,11 +239,24 @@ database state behind or unit tests that rely on some form of state being preserved after the execution of other tests. Such tests are already very fragile, and must now be changed to be able to run independently. +`cleaned_data` dictionary kept for invalid forms +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :attr:`~django.forms.Form.cleaned_data` dictionary is now always present +after form validation. When the form doesn't validate, it contains only the +fields that passed validation. You should test the success of the validation +with the :meth:`~django.forms.Form.is_valid()` method and not with the +presence or absence of the :attr:`~django.forms.Form.cleaned_data` attribute +on the form. + Miscellaneous ~~~~~~~~~~~~~ * GeoDjango dropped support for GDAL < 1.5 +* :func:`~django.utils.http.int_to_base36` properly raises a :exc:`TypeError` + instead of :exc:`ValueError` for non-integer inputs. + Features deprecated in 1.5 ========================== diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index c0e56dbba0..307691bd4a 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -98,12 +98,13 @@ Fields This doesn't necessarily control whether or not the user can log in. Authentication backends aren't required to check for the ``is_active`` - flag, so if you want to reject a login based on ``is_active`` being - ``False``, it's up to you to check that in your own login view. - However, the :class:`~django.contrib.auth.forms.AuthenticationForm` - used by the :func:`~django.contrib.auth.views.login` view *does* - perform this check, as do the permission-checking methods such as - :meth:`~models.User.has_perm` and the authentication in the Django + flag, and the default backends do not. If you want to reject a login + based on ``is_active`` being ``False``, it's up to you to check that in + your own login view or a custom authentication backend. However, the + :class:`~django.contrib.auth.forms.AuthenticationForm` used by the + :func:`~django.contrib.auth.views.login` view (which is the default) + *does* perform this check, as do the permission-checking methods such + as :meth:`~models.User.has_perm` and the authentication in the Django admin. All of those functions/methods will return ``False`` for inactive users. @@ -1447,10 +1448,10 @@ The permission_required decorator .. currentmodule:: django.contrib.auth -Limiting access to generic views --------------------------------- +Applying permissions to generic views +------------------------------------- -To limit access to a :doc:`class-based generic view +To apply a permission to a :doc:`class-based generic view `, decorate the :meth:`View.dispatch ` method on the class. See :ref:`decorating-class-based-views` for details. @@ -1476,12 +1477,13 @@ The Django admin site uses permissions as follows: * Access to delete an object is limited to users with the "delete" permission for that type of object. -Permissions are set globally per type of object, not per specific object -instance. For example, it's possible to say "Mary may change news stories," but -it's not currently possible to say "Mary may change news stories, but only the -ones she created herself" or "Mary may only change news stories that have a -certain status, publication date or ID." The latter functionality is something -Django developers are currently discussing. +Permissions can be set not only per type of object, but also per specific +object instance. By using the +:meth:`~django.contrib.admin.ModelAdmin.has_add_permission`, +:meth:`~django.contrib.admin.ModelAdmin.has_change_permission` and +:meth:`~django.contrib.admin.ModelAdmin.has_delete_permission` methods provided +by the :class:`~django.contrib.admin.ModelAdmin` class, it is possible to +customize permissions for different object instances of the same type. Default permissions ------------------- @@ -1747,7 +1749,11 @@ By default, :setting:`AUTHENTICATION_BACKENDS` is set to:: ('django.contrib.auth.backends.ModelBackend',) -That's the basic authentication scheme that checks the Django users database. +That's the basic authentication backend that checks the Django users database +and queries the builtin permissions. It does not provide protection against +brute force attacks via any rate limiting mechanism. You may either implement +your own rate limiting mechanism in a custom auth backend, or use the +mechanisms provided by most Web servers. The order of :setting:`AUTHENTICATION_BACKENDS` matters, so if the same username and password is valid in multiple backends, Django will stop @@ -1767,8 +1773,9 @@ processing at the first positive match. Writing an authentication backend --------------------------------- -An authentication backend is a class that implements two methods: -``get_user(user_id)`` and ``authenticate(**credentials)``. +An authentication backend is a class that implements two required methods: +``get_user(user_id)`` and ``authenticate(**credentials)``, as well as a set of +optional permission related :ref:`authorization methods `. The ``get_user`` method takes a ``user_id`` -- which could be a username, database ID or whatever -- and returns a ``User`` object. @@ -1837,6 +1844,8 @@ object the first time a user authenticates:: except User.DoesNotExist: return None +.. _authorization_methods: + Handling authorization in custom backends ----------------------------------------- @@ -1867,13 +1876,16 @@ fairly simply:: return False This gives full permissions to the user granted access in the above example. -Notice that the backend auth functions all take the user object as an argument, -and they also accept the same arguments given to the associated -:class:`django.contrib.auth.models.User` functions. +Notice that in addition to the same arguments given to the associated +:class:`django.contrib.auth.models.User` functions, the backend auth functions +all take the user object, which may be an anonymous user, as an argument. -A full authorization implementation can be found in -`django/contrib/auth/backends.py`_, which is the default backend and queries -the ``auth_permission`` table most of the time. +A full authorization implementation can be found in the ``ModelBackend`` class +in `django/contrib/auth/backends.py`_, which is the default backend and queries +the ``auth_permission`` table most of the time. If you wish to provide +custom behavior for only part of the backend API, you can take advantage of +Python inheritence and subclass ``ModelBackend`` instead of implementing the +complete API in a custom backend. .. _django/contrib/auth/backends.py: https://github.com/django/django/blob/master/django/contrib/auth/backends.py @@ -1889,25 +1901,27 @@ authorize anonymous users to browse most of the site, and many allow anonymous posting of comments etc. Django's permission framework does not have a place to store permissions for -anonymous users. However, it has a foundation that allows custom authentication -backends to specify authorization for anonymous users. This is especially useful -for the authors of re-usable apps, who can delegate all questions of authorization -to the auth backend, rather than needing settings, for example, to control -anonymous access. +anonymous users. However, the user object passed to an authentication backend +may be an :class:`django.contrib.auth.models.AnonymousUser` object, allowing +the backend to specify custom authorization behavior for anonymous users. This +is especially useful for the authors of re-usable apps, who can delegate all +questions of authorization to the auth backend, rather than needing settings, +for example, to control anonymous access. +.. _inactive_auth: Authorization for inactive users ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.3 +.. versionchanged:: 1.3 An inactive user is a one that is authenticated but has its attribute ``is_active`` set to ``False``. However this does not mean they are not authorized to do anything. For example they are allowed to activate their account. -The support for anonymous users in the permission system allows for -anonymous users to have permissions to do something while inactive +The support for anonymous users in the permission system allows for a scenario +where anonymous users have permissions to do something while inactive authenticated users do not. Do not forget to test for the ``is_active`` attribute of the user in your own @@ -1915,9 +1929,11 @@ backend permission methods. Handling object permissions ---------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~ Django's permission framework has a foundation for object permissions, though there is no implementation for it in the core. That means that checking for object permissions will always return ``False`` or an empty list (depending on -the check performed). +the check performed). An authentication backend will receive the keyword +parameters ``obj`` and ``user_obj`` for each object related authorization +method and can return the object level permission as appropriate. diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 03afa86647..219b6c7795 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -595,7 +595,8 @@ the ``cache`` template tag. To give your template access to this tag, put The ``{% cache %}`` template tag caches the contents of the block for a given amount of time. It takes at least two arguments: the cache timeout, in seconds, -and the name to give the cache fragment. For example: +and the name to give the cache fragment. The name will be taken as is, do not +use a variable. For example: .. code-block:: html+django @@ -863,7 +864,7 @@ key version to provide a final cache key. By default, the three parts are joined using colons to produce a final string:: def make_key(key, key_prefix, version): - return ':'.join([key_prefix, str(version), smart_str(key)]) + return ':'.join([key_prefix, str(version), smart_bytes(key)]) If you want to combine the parts in different ways, or apply other processing to the final key (e.g., taking a hash digest of the key diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt index 6c2848944c..1ac70e6938 100644 --- a/docs/topics/class-based-views/index.txt +++ b/docs/topics/class-based-views/index.txt @@ -87,7 +87,7 @@ Where class based views shine is when you want to do the same thing many times. Suppose you're writing an API, and every view should return JSON instead of rendered HTML. -We can use create a mixin class to use in all of our views, handling the +We can create a mixin class to use in all of our views, handling the conversion to JSON once. For example, a simple JSON mixin might look something like this:: diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 573e1fd0aa..772792d39d 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -230,7 +230,7 @@ It is optimal because: #. Use of :ttag:`with` means that we store ``user.emails.all`` in a variable for later use, allowing its cache to be re-used. -#. The line ``{% if emails %}`` causes ``QuerySet.__nonzero__()`` to be called, +#. The line ``{% if emails %}`` causes ``QuerySet.__bool__()`` to be called, which causes the ``user.emails.all()`` query to be run on the database, and at the least the first line to be turned into an ORM object. If there aren't any results, it will return False, otherwise True. diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 4cfde400a7..0ca37745c7 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -437,6 +437,10 @@ parameter when declaring the form field:: class Meta: model = Article + You must ensure that the type of the form field can be used to set the + contents of the corresponding model field. When they are not compatible, + you will get a ``ValueError`` as no implicit conversion takes place. + See the :doc:`form field documentation ` for more information on fields and their arguments. diff --git a/docs/topics/i18n/formatting.txt b/docs/topics/i18n/formatting.txt index d18781c0a9..b09164769e 100644 --- a/docs/topics/i18n/formatting.txt +++ b/docs/topics/i18n/formatting.txt @@ -21,7 +21,10 @@ necessary to set :setting:`USE_L10N = True ` in your settings file. The default :file:`settings.py` file created by :djadmin:`django-admin.py startproject ` includes :setting:`USE_L10N = True ` - for convenience. + for convenience. Note, however, that to enable number formatting with + thousand separators it is necessary to set :setting:`USE_THOUSAND_SEPARATOR + = True ` in your settings file. Alternatively, you + could use :tfilter:`intcomma` to format numbers in your template. .. note:: diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index c912bf9575..bdbb04823d 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1247,6 +1247,12 @@ Activate this view by adding the following line to your URLconf:: (Note that this example makes the view available at ``/i18n/setlang/``.) +.. warning:: + + Make sure that you don't include the above URL within + :func:`~django.conf.urls.i18n.i18n_patterns` - it needs to be + language-independent itself to work correctly. + The view expects to be called via the ``POST`` method, with a ``language`` parameter set in request. If session support is enabled, the view saves the language choice in the user's session. Otherwise, it saves the @@ -1266,7 +1272,7 @@ Here's example HTML template code: .. code-block:: html+django - + {% csrf_token %}
    • This field is required.
    • This field is required.
    """) @@ -145,11 +141,7 @@ class FormsTestCase(TestCase): * This field is required. * birthday * This field is required.""") - try: - p.cleaned_data - self.fail('Attempts to access cleaned_data when validation fails should fail.') - except AttributeError: - pass + self.assertEqual(p.cleaned_data, {'last_name': 'Lennon'}) self.assertEqual(p['first_name'].errors, ['This field is required.']) self.assertHTMLEqual(p['first_name'].errors.as_ul(), '
    • This field is required.
    ') self.assertEqual(p['first_name'].errors.as_text(), '* This field is required.') @@ -1678,11 +1670,7 @@ class FormsTestCase(TestCase): form = SongForm(data, empty_permitted=False) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, {'name': ['This field is required.'], 'artist': ['This field is required.']}) - try: - form.cleaned_data - self.fail('Attempts to access cleaned_data when validation fails should fail.') - except AttributeError: - pass + self.assertEqual(form.cleaned_data, {}) # Now let's show what happens when empty_permitted=True and the form is empty. form = SongForm(data, empty_permitted=True) @@ -1696,11 +1684,7 @@ class FormsTestCase(TestCase): form = SongForm(data, empty_permitted=False) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, {'name': ['This field is required.']}) - try: - form.cleaned_data - self.fail('Attempts to access cleaned_data when validation fails should fail.') - except AttributeError: - pass + self.assertEqual(form.cleaned_data, {'artist': 'The Doors'}) # If a field is not given in the data then None is returned for its data. Lets # make sure that when checking for empty_permitted that None is treated diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index 9b599db6d0..0f61c2d840 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -4,8 +4,11 @@ from __future__ import unicode_literals import copy import pickle -from django.http import (QueryDict, HttpResponse, SimpleCookie, BadHeaderError, - parse_cookie) +from django.core.exceptions import SuspiciousOperation +from django.http import (QueryDict, HttpResponse, HttpResponseRedirect, + HttpResponsePermanentRedirect, + SimpleCookie, BadHeaderError, + parse_cookie) from django.utils import unittest @@ -309,6 +312,18 @@ class HttpResponseTests(unittest.TestCase): r = HttpResponse(['abc']) self.assertRaises(Exception, r.write, 'def') + def test_unsafe_redirect(self): + bad_urls = [ + 'data:text/html,', + 'mailto:test@example.com', + 'file:///etc/passwd', + ] + for url in bad_urls: + self.assertRaises(SuspiciousOperation, + HttpResponseRedirect, url) + self.assertRaises(SuspiciousOperation, + HttpResponsePermanentRedirect, url) + class CookieTests(unittest.TestCase): def test_encode(self): diff --git a/tests/regressiontests/i18n/commands/compilation.py b/tests/regressiontests/i18n/commands/compilation.py index d88e1feef6..b6119cf43d 100644 --- a/tests/regressiontests/i18n/commands/compilation.py +++ b/tests/regressiontests/i18n/commands/compilation.py @@ -1,10 +1,10 @@ import os -from io import BytesIO from django.core.management import call_command, CommandError from django.test import TestCase from django.test.utils import override_settings from django.utils import translation +from django.utils.six import StringIO test_dir = os.path.abspath(os.path.dirname(__file__)) @@ -26,7 +26,7 @@ class PoFileTests(MessageCompilationTests): os.chdir(test_dir) with self.assertRaisesRegexp(CommandError, "file has a BOM \(Byte Order Mark\)"): - call_command('compilemessages', locale=self.LOCALE, stderr=BytesIO()) + call_command('compilemessages', locale=self.LOCALE, stderr=StringIO()) self.assertFalse(os.path.exists(self.MO_FILE)) @@ -42,7 +42,7 @@ class PoFileContentsTests(MessageCompilationTests): def test_percent_symbol_in_po_file(self): os.chdir(test_dir) - call_command('compilemessages', locale=self.LOCALE, stderr=BytesIO()) + call_command('compilemessages', locale=self.LOCALE, stderr=StringIO()) self.assertTrue(os.path.exists(self.MO_FILE)) @@ -57,7 +57,7 @@ class PercentRenderingTests(MessageCompilationTests): def test_percent_symbol_escaping(self): from django.template import Template, Context os.chdir(test_dir) - call_command('compilemessages', locale=self.LOCALE, stderr=BytesIO()) + call_command('compilemessages', locale=self.LOCALE, stderr=StringIO()) with translation.override(self.LOCALE): t = Template('{% load i18n %}{% trans "Looks like a str fmt spec %% o but shouldn\'t be interpreted as such" %}') rendered = t.render(Context({})) diff --git a/tests/regressiontests/i18n/commands/extraction.py b/tests/regressiontests/i18n/commands/extraction.py index cd6d50893a..29d9e277ff 100644 --- a/tests/regressiontests/i18n/commands/extraction.py +++ b/tests/regressiontests/i18n/commands/extraction.py @@ -3,10 +3,10 @@ import os import re import shutil -from StringIO import StringIO from django.core import management from django.test import TestCase +from django.utils.six import StringIO LOCALE='de' diff --git a/tests/regressiontests/inspectdb/tests.py b/tests/regressiontests/inspectdb/tests.py index 29435b8375..aae7bc5cc7 100644 --- a/tests/regressiontests/inspectdb/tests.py +++ b/tests/regressiontests/inspectdb/tests.py @@ -1,7 +1,6 @@ -from StringIO import StringIO - from django.core.management import call_command from django.test import TestCase, skipUnlessDBFeature +from django.utils.six import StringIO class InspectDBTestCase(TestCase): diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py index 2215f56523..c948662bc3 100644 --- a/tests/regressiontests/mail/tests.py +++ b/tests/regressiontests/mail/tests.py @@ -7,7 +7,6 @@ import os import shutil import smtpd import sys -from StringIO import StringIO import tempfile import threading @@ -18,6 +17,7 @@ from django.core.mail.backends import console, dummy, locmem, filebased, smtp from django.core.mail.message import BadHeaderError from django.test import TestCase from django.test.utils import override_settings +from django.utils.six import PY3, StringIO from django.utils.translation import ugettext_lazy @@ -29,7 +29,7 @@ class MailTests(TestCase): def test_ascii(self): email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com']) message = email.message() - self.assertEqual(message['Subject'].encode(), 'Subject') + self.assertEqual(message['Subject'], 'Subject') self.assertEqual(message.get_payload(), 'Content') self.assertEqual(message['From'], 'from@example.com') self.assertEqual(message['To'], 'to@example.com') @@ -37,7 +37,7 @@ class MailTests(TestCase): def test_multiple_recipients(self): email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com', 'other@example.com']) message = email.message() - self.assertEqual(message['Subject'].encode(), 'Subject') + self.assertEqual(message['Subject'], 'Subject') self.assertEqual(message.get_payload(), 'Content') self.assertEqual(message['From'], 'from@example.com') self.assertEqual(message['To'], 'to@example.com, other@example.com') @@ -77,9 +77,10 @@ class MailTests(TestCase): """ Test for space continuation character in long (ascii) subject headers (#7747) """ - email = EmailMessage('Long subject lines that get wrapped should use a space continuation character to get expected behavior in Outlook and Thunderbird', 'Content', 'from@example.com', ['to@example.com']) + email = EmailMessage('Long subject lines that get wrapped should contain a space continuation character to get expected behavior in Outlook and Thunderbird', 'Content', 'from@example.com', ['to@example.com']) message = email.message() - self.assertEqual(message['Subject'], 'Long subject lines that get wrapped should use a space continuation\n character to get expected behavior in Outlook and Thunderbird') + # Note that in Python 3, maximum line length has increased from 76 to 78 + self.assertEqual(message['Subject'].encode(), b'Long subject lines that get wrapped should contain a space continuation\n character to get expected behavior in Outlook and Thunderbird') def test_message_header_overrides(self): """ @@ -88,7 +89,7 @@ class MailTests(TestCase): """ headers = {"date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} email = EmailMessage('subject', 'content', 'from@example.com', ['to@example.com'], headers=headers) - self.assertEqual(email.message().as_string(), b'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: subject\nFrom: from@example.com\nTo: to@example.com\ndate: Fri, 09 Nov 2001 01:08:47 -0000\nMessage-ID: foo\n\ncontent') + self.assertEqual(email.message().as_string(), 'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: subject\nFrom: from@example.com\nTo: to@example.com\ndate: Fri, 09 Nov 2001 01:08:47 -0000\nMessage-ID: foo\n\ncontent') def test_from_header(self): """ @@ -160,7 +161,7 @@ class MailTests(TestCase): msg.attach_alternative(html_content, "text/html") msg.encoding = 'iso-8859-1' self.assertEqual(msg.message()['To'], '=?iso-8859-1?q?S=FCrname=2C_Firstname?= ') - self.assertEqual(msg.message()['Subject'].encode(), '=?iso-8859-1?q?Message_from_Firstname_S=FCrname?=') + self.assertEqual(msg.message()['Subject'], '=?iso-8859-1?q?Message_from_Firstname_S=FCrname?=') def test_encoding(self): """ @@ -170,7 +171,7 @@ class MailTests(TestCase): email = EmailMessage('Subject', 'Firstname Sürname is a great guy.', 'from@example.com', ['other@example.com']) email.encoding = 'iso-8859-1' message = email.message() - self.assertTrue(message.as_string().startswith(b'Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: other@example.com')) + self.assertTrue(message.as_string().startswith('Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: other@example.com')) self.assertEqual(message.get_payload(), 'Firstname S=FCrname is a great guy.') # Make sure MIME attachments also works correctly with other encodings than utf-8 @@ -179,8 +180,8 @@ class MailTests(TestCase): msg = EmailMultiAlternatives('Subject', text_content, 'from@example.com', ['to@example.com']) msg.encoding = 'iso-8859-1' msg.attach_alternative(html_content, "text/html") - self.assertEqual(msg.message().get_payload(0).as_string(), b'Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\nFirstname S=FCrname is a great guy.') - self.assertEqual(msg.message().get_payload(1).as_string(), b'Content-Type: text/html; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\n

    Firstname S=FCrname is a great guy.

    ') + self.assertEqual(msg.message().get_payload(0).as_string(), 'Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\nFirstname S=FCrname is a great guy.') + self.assertEqual(msg.message().get_payload(1).as_string(), 'Content-Type: text/html; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\n

    Firstname S=FCrname is a great guy.

    ') def test_attachments(self): """Regression test for #9367""" @@ -291,31 +292,31 @@ class MailTests(TestCase): # Regression for #13433 - Make sure that EmailMessage doesn't mangle # 'From ' in message body. email = EmailMessage('Subject', 'From the future', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) - self.assertFalse(b'>From the future' in email.message().as_string()) + self.assertFalse('>From the future' in email.message().as_string()) def test_dont_base64_encode(self): # Ticket #3472 # Shouldn't use Base64 encoding at all msg = EmailMessage('Subject', 'UTF-8 encoded body', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) - self.assertFalse(b'Content-Transfer-Encoding: base64' in msg.message().as_string()) + self.assertFalse('Content-Transfer-Encoding: base64' in msg.message().as_string()) # Ticket #11212 # Shouldn't use quoted printable, should detect it can represent content with 7 bit data msg = EmailMessage('Subject', 'Body with only ASCII characters.', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) s = msg.message().as_string() - self.assertFalse(b'Content-Transfer-Encoding: quoted-printable' in s) - self.assertTrue(b'Content-Transfer-Encoding: 7bit' in s) + self.assertFalse('Content-Transfer-Encoding: quoted-printable' in s) + self.assertTrue('Content-Transfer-Encoding: 7bit' in s) # Shouldn't use quoted printable, should detect it can represent content with 8 bit data msg = EmailMessage('Subject', 'Body with latin characters: àáä.', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) s = msg.message().as_string() - self.assertFalse(b'Content-Transfer-Encoding: quoted-printable' in s) - self.assertTrue(b'Content-Transfer-Encoding: 8bit' in s) + self.assertFalse(str('Content-Transfer-Encoding: quoted-printable') in s) + self.assertTrue(str('Content-Transfer-Encoding: 8bit') in s) msg = EmailMessage('Subject', 'Body with non latin characters: А Б В Г Д Е Ж Ѕ З И І К Л М Н О П.', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) s = msg.message().as_string() - self.assertFalse(b'Content-Transfer-Encoding: quoted-printable' in s) - self.assertTrue(b'Content-Transfer-Encoding: 8bit' in s) + self.assertFalse(str('Content-Transfer-Encoding: quoted-printable') in s) + self.assertTrue(str('Content-Transfer-Encoding: 8bit') in s) class BaseEmailBackendTests(object): @@ -440,7 +441,7 @@ class BaseEmailBackendTests(object): email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com'], cc=['cc@example.com']) mail.get_connection().send_messages([email]) message = self.get_the_message() - self.assertStartsWith(message.as_string(), b'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nCc: cc@example.com\nDate: ') + self.assertStartsWith(message.as_string(), 'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nCc: cc@example.com\nDate: ') def test_idn_send(self): """ @@ -519,9 +520,9 @@ class FileBackendTests(BaseEmailBackendTests, TestCase): def get_mailbox_content(self): messages = [] for filename in os.listdir(self.tmp_dir): - with open(os.path.join(self.tmp_dir, filename), 'rb') as fp: - session = fp.read().split(b'\n' + (b'-' * 79) + b'\n') - messages.extend(email.message_from_string(m) for m in session if m) + with open(os.path.join(self.tmp_dir, filename), 'r') as fp: + session = fp.read().split('\n' + ('-' * 79) + '\n') + messages.extend(email.message_from_string(str(m)) for m in session if m) return messages def test_file_sessions(self): @@ -571,8 +572,8 @@ class ConsoleBackendTests(BaseEmailBackendTests, TestCase): self.stream = sys.stdout = StringIO() def get_mailbox_content(self): - messages = self.stream.getvalue().split(b'\n' + (b'-' * 79) + b'\n') - return [email.message_from_string(m) for m in messages if m] + messages = self.stream.getvalue().split('\n' + ('-' * 79) + '\n') + return [email.message_from_string(str(m)) for m in messages if m] def test_console_stream_kwarg(self): """ @@ -600,7 +601,10 @@ class FakeSMTPServer(smtpd.SMTPServer, threading.Thread): def process_message(self, peer, mailfrom, rcpttos, data): m = email.message_from_string(data) - maddr = email.Utils.parseaddr(m.get('from'))[1] + if PY3: + maddr = email.utils.parseaddr(m.get('from'))[1] + else: + maddr = email.Utils.parseaddr(m.get('from'))[1] if mailfrom != maddr: return "553 '%s' != '%s'" % (mailfrom, maddr) with self.sink_lock: diff --git a/tests/regressiontests/middleware/tests.py b/tests/regressiontests/middleware/tests.py index ead34f46db..08a385e6cf 100644 --- a/tests/regressiontests/middleware/tests.py +++ b/tests/regressiontests/middleware/tests.py @@ -3,7 +3,6 @@ import gzip import re import random -import StringIO from django.conf import settings from django.core import mail @@ -16,6 +15,7 @@ from django.middleware.gzip import GZipMiddleware from django.test import TestCase, RequestFactory from django.test.utils import override_settings from django.utils.six.moves import xrange +from django.utils.six import StringIO class CommonMiddlewareTest(TestCase): def setUp(self): @@ -526,7 +526,7 @@ class GZipMiddlewareTest(TestCase): @staticmethod def decompress(gzipped_string): - return gzip.GzipFile(mode='rb', fileobj=StringIO.StringIO(gzipped_string)).read() + return gzip.GzipFile(mode='rb', fileobj=StringIO(gzipped_string)).read() def test_compress_response(self): """ diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index e86159463d..7d6071accc 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/regressiontests/model_fields/tests.py @@ -274,6 +274,10 @@ class ValidationTest(test.TestCase): self.assertRaises(ValidationError, f.clean, None, None) self.assertRaises(ValidationError, f.clean, '', None) + def test_integerfield_validates_zero_against_choices(self): + f = models.IntegerField(choices=((1, 1),)) + self.assertRaises(ValidationError, f.clean, '0', None) + def test_charfield_raises_error_on_empty_input(self): f = models.CharField(null=False) self.assertRaises(ValidationError, f.clean, None, None) diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py index 595c5edb3b..74a5f2f550 100644 --- a/tests/regressiontests/multiple_database/tests.py +++ b/tests/regressiontests/multiple_database/tests.py @@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals import datetime import pickle -from StringIO import StringIO +from operator import attrgetter from django.conf import settings from django.contrib.auth.models import User @@ -11,6 +11,7 @@ from django.core import management from django.db import connections, router, DEFAULT_DB_ALIAS from django.db.models import signals from django.test import TestCase +from django.utils.six import StringIO from .models import Book, Person, Pet, Review, UserProfile @@ -873,10 +874,10 @@ class QueryTestCase(TestCase): dive = Book.objects.using('other').create(title="Dive into Python", published=datetime.date(2009, 5, 4)) val = Book.objects.db_manager("other").raw('SELECT id FROM multiple_database_book') - self.assertEqual(map(lambda o: o.pk, val), [dive.pk]) + self.assertQuerysetEqual(val, [dive.pk], attrgetter("pk")) val = Book.objects.raw('SELECT id FROM multiple_database_book').using('other') - self.assertEqual(map(lambda o: o.pk, val), [dive.pk]) + self.assertQuerysetEqual(val, [dive.pk], attrgetter("pk")) def test_select_related(self): "Database assignment is retained if an object is retrieved with select_related()" diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py index 146dca2b7b..f192459246 100644 --- a/tests/regressiontests/requests/tests.py +++ b/tests/regressiontests/requests/tests.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import time import warnings from datetime import datetime, timedelta -from StringIO import StringIO from django.conf import settings from django.core.handlers.wsgi import WSGIRequest, LimitedStream @@ -11,6 +10,7 @@ from django.http import HttpRequest, HttpResponse, parse_cookie, build_request_r from django.test.utils import str_prefix from django.utils import unittest from django.utils.http import cookie_date +from django.utils.six import StringIO from django.utils.timezone import utc diff --git a/tests/regressiontests/signing/tests.py b/tests/regressiontests/signing/tests.py index f3fe5f3ec7..2368405060 100644 --- a/tests/regressiontests/signing/tests.py +++ b/tests/regressiontests/signing/tests.py @@ -4,7 +4,7 @@ import time from django.core import signing from django.test import TestCase -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text class TestSigner(TestCase): @@ -48,7 +48,7 @@ class TestSigner(TestCase): ) for example in examples: self.assertNotEqual( - force_unicode(example), force_unicode(signer.sign(example))) + force_text(example), force_text(signer.sign(example))) self.assertEqual(example, signer.unsign(signer.sign(example))) def unsign_detects_tampering(self): diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 2c038e1713..19951f100b 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -17,7 +17,7 @@ from django.core.files.storage import default_storage from django.core.management import call_command from django.test import TestCase from django.test.utils import override_settings -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.functional import empty from django.utils._os import rmtree_errorhandler from django.utils import six @@ -80,7 +80,7 @@ class BaseStaticFilesTestCase(object): os.unlink(self._backup_filepath) def assertFileContains(self, filepath, text): - self.assertIn(text, self._get_file(smart_unicode(filepath)), + self.assertIn(text, self._get_file(smart_text(filepath)), "'%s' not in '%s'" % (text, filepath)) def assertFileNotFound(self, filepath): @@ -199,7 +199,7 @@ class TestFindStatic(CollectionTestCase, TestDefaults): out.seek(0) lines = [l.strip() for l in out.readlines()] contents = codecs.open( - smart_unicode(lines[1].strip()), "r", "utf-8").read() + smart_text(lines[1].strip()), "r", "utf-8").read() return contents def test_all_files(self): diff --git a/tests/regressiontests/templates/loaders.py b/tests/regressiontests/templates/loaders.py index 5c11916308..6b635c8f23 100644 --- a/tests/regressiontests/templates/loaders.py +++ b/tests/regressiontests/templates/loaders.py @@ -12,13 +12,13 @@ if __name__ == '__main__': import sys import pkg_resources import imp -import StringIO import os.path from django.template import TemplateDoesNotExist, Context from django.template.loaders.eggs import Loader as EggLoader from django.template import loader from django.utils import unittest +from django.utils.six import StringIO # Mock classes and objects for pkg_resources functions. @@ -61,8 +61,8 @@ class EggLoaderTest(unittest.TestCase): self.empty_egg = create_egg("egg_empty", {}) self.egg_1 = create_egg("egg_1", { - os.path.normcase('templates/y.html') : StringIO.StringIO("y"), - os.path.normcase('templates/x.txt') : StringIO.StringIO("x"), + os.path.normcase('templates/y.html') : StringIO("y"), + os.path.normcase('templates/x.txt') : StringIO("x"), }) self._old_installed_apps = settings.INSTALLED_APPS settings.INSTALLED_APPS = [] diff --git a/tests/regressiontests/templates/templatetags/custom.py b/tests/regressiontests/templates/templatetags/custom.py index 95fcd551de..3f51353880 100644 --- a/tests/regressiontests/templates/templatetags/custom.py +++ b/tests/regressiontests/templates/templatetags/custom.py @@ -70,7 +70,7 @@ simple_only_unlimited_args.anything = "Expected simple_only_unlimited_args __dic def simple_unlimited_args_kwargs(one, two='hi', *args, **kwargs): """Expected simple_unlimited_args_kwargs __doc__""" # Sort the dictionary by key to guarantee the order for testing. - sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0)) + sorted_kwarg = sorted(six.iteritems(kwargs), key=operator.itemgetter(0)) return "simple_unlimited_args_kwargs - Expected result: %s / %s" % ( ', '.join([six.text_type(arg) for arg in [one, two] + list(args)]), ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg]) @@ -221,7 +221,7 @@ inclusion_tag_use_l10n.anything = "Expected inclusion_tag_use_l10n __dict__" def inclusion_unlimited_args_kwargs(one, two='hi', *args, **kwargs): """Expected inclusion_unlimited_args_kwargs __doc__""" # Sort the dictionary by key to guarantee the order for testing. - sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0)) + sorted_kwarg = sorted(six.iteritems(kwargs), key=operator.itemgetter(0)) return {"result": "inclusion_unlimited_args_kwargs - Expected result: %s / %s" % ( ', '.join([six.text_type(arg) for arg in [one, two] + list(args)]), ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg]) @@ -292,7 +292,7 @@ assignment_only_unlimited_args.anything = "Expected assignment_only_unlimited_ar def assignment_unlimited_args_kwargs(one, two='hi', *args, **kwargs): """Expected assignment_unlimited_args_kwargs __doc__""" # Sort the dictionary by key to guarantee the order for testing. - sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0)) + sorted_kwarg = sorted(six.iteritems(kwargs), key=operator.itemgetter(0)) return "assignment_unlimited_args_kwargs - Expected result: %s / %s" % ( ', '.join([six.text_type(arg) for arg in [one, two] + list(args)]), ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg]) diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 402cbb19d2..edbb21b6bd 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -30,6 +30,7 @@ from django.utils import unittest from django.utils.formats import date_format from django.utils.translation import activate, deactivate, ugettext as _ from django.utils.safestring import mark_safe +from django.utils import six from django.utils.tzinfo import LocalTimezone from .callables import CallableVariablesTests @@ -402,7 +403,7 @@ class Templates(unittest.TestCase): template_tests.update(filter_tests) cache_loader = setup_test_template_loader( - dict([(name, t[0]) for name, t in template_tests.iteritems()]), + dict([(name, t[0]) for name, t in six.iteritems(template_tests)]), use_cached_loader=True, ) diff --git a/tests/regressiontests/test_utils/tests.py b/tests/regressiontests/test_utils/tests.py index 46479ebe8b..f43a855a59 100644 --- a/tests/regressiontests/test_utils/tests.py +++ b/tests/regressiontests/test_utils/tests.py @@ -493,7 +493,7 @@ __test__ = {"API_TEST": r""" # Standard doctests do fairly >>> import json >>> from django.utils.xmlutils import SimplerXMLGenerator ->>> from StringIO import StringIO +>>> from django.utils.six import StringIO >>> def produce_long(): ... return 42L diff --git a/tests/regressiontests/utils/baseconv.py b/tests/regressiontests/utils/baseconv.py index 75660d8119..cc413b4e8e 100644 --- a/tests/regressiontests/utils/baseconv.py +++ b/tests/regressiontests/utils/baseconv.py @@ -1,10 +1,11 @@ from unittest import TestCase from django.utils.baseconv import base2, base16, base36, base56, base62, base64, BaseConverter +from django.utils.six.moves import xrange class TestBaseConv(TestCase): def test_baseconv(self): - nums = [-10 ** 10, 10 ** 10] + range(-100, 100) + nums = [-10 ** 10, 10 ** 10] + list(xrange(-100, 100)) for converter in [base2, base16, base36, base56, base62, base64]: for i in nums: self.assertEqual(i, converter.decode(converter.encode(i))) diff --git a/tests/regressiontests/utils/crypto.py b/tests/regressiontests/utils/crypto.py index 2bdc5ba530..52a286cb27 100644 --- a/tests/regressiontests/utils/crypto.py +++ b/tests/regressiontests/utils/crypto.py @@ -1,4 +1,6 @@ +from __future__ import unicode_literals +import binascii import math import timeit import hashlib @@ -108,15 +110,15 @@ class TestUtilsCryptoPBKDF2(unittest.TestCase): "c4007d5298f9033c0241d5ab69305e7b64eceeb8d" "834cfec"), }, - # Check leading zeros are not stripped (#17481) + # Check leading zeros are not stripped (#17481) { - "args": { - "password": chr(186), - "salt": "salt", - "iterations": 1, - "dklen": 20, - "digest": hashlib.sha1, - }, + "args": { + "password": b'\xba', + "salt": "salt", + "iterations": 1, + "dklen": 20, + "digest": hashlib.sha1, + }, "result": '0053d3b91a7f1e54effebd6d68771e8a6e0b2c5b', }, ] @@ -124,12 +126,14 @@ class TestUtilsCryptoPBKDF2(unittest.TestCase): def test_public_vectors(self): for vector in self.rfc_vectors: result = pbkdf2(**vector['args']) - self.assertEqual(result.encode('hex'), vector['result']) + self.assertEqual(binascii.hexlify(result).decode('ascii'), + vector['result']) def test_regression_vectors(self): for vector in self.regression_vectors: result = pbkdf2(**vector['args']) - self.assertEqual(result.encode('hex'), vector['result']) + self.assertEqual(binascii.hexlify(result).decode('ascii'), + vector['result']) def test_performance_scalability(self): """ @@ -140,11 +144,11 @@ class TestUtilsCryptoPBKDF2(unittest.TestCase): # to run the test suite and false positives caused by imprecise # measurement. n1, n2 = 200000, 800000 - elapsed = lambda f: timeit.Timer(f, + elapsed = lambda f: timeit.Timer(f, 'from django.utils.crypto import pbkdf2').timeit(number=1) t1 = elapsed('pbkdf2("password", "salt", iterations=%d)' % n1) t2 = elapsed('pbkdf2("password", "salt", iterations=%d)' % n2) measured_scale_exponent = math.log(t2 / t1, n2 / n1) - # This should be less than 1. We allow up to 1.2 so that tests don't + # This should be less than 1. We allow up to 1.2 so that tests don't # fail nondeterministically too often. self.assertLess(measured_scale_exponent, 1.2) diff --git a/tests/regressiontests/utils/decorators.py b/tests/regressiontests/utils/decorators.py index 96f4dd4e7a..4d503df026 100644 --- a/tests/regressiontests/utils/decorators.py +++ b/tests/regressiontests/utils/decorators.py @@ -105,4 +105,4 @@ class DecoratorFromMiddlewareTests(TestCase): response.render() self.assertTrue(getattr(request, 'process_response_reached', False)) # Check that process_response saw the rendered content - self.assertEqual(request.process_response_content, "Hello world") + self.assertEqual(request.process_response_content, b"Hello world") diff --git a/tests/regressiontests/utils/http.py b/tests/regressiontests/utils/http.py index 67dcd7af89..f22e05496d 100644 --- a/tests/regressiontests/utils/http.py +++ b/tests/regressiontests/utils/http.py @@ -1,10 +1,11 @@ import sys -from django.utils import http -from django.utils import unittest -from django.utils.datastructures import MultiValueDict from django.http import HttpResponse, utils from django.test import RequestFactory +from django.utils.datastructures import MultiValueDict +from django.utils import http +from django.utils import six +from django.utils import unittest class TestUtilsHttp(unittest.TestCase): @@ -110,22 +111,23 @@ class TestUtilsHttp(unittest.TestCase): def test_base36(self): # reciprocity works - for n in [0, 1, 1000, 1000000, sys.maxint]: + for n in [0, 1, 1000, 1000000]: self.assertEqual(n, http.base36_to_int(http.int_to_base36(n))) + if not six.PY3: + self.assertEqual(sys.maxint, http.base36_to_int(http.int_to_base36(sys.maxint))) # bad input - for n in [-1, sys.maxint+1, '1', 'foo', {1:2}, (1,2,3)]: - self.assertRaises(ValueError, http.int_to_base36, n) - + self.assertRaises(ValueError, http.int_to_base36, -1) + if not six.PY3: + self.assertRaises(ValueError, http.int_to_base36, sys.maxint + 1) + for n in ['1', 'foo', {1: 2}, (1, 2, 3), 3.141]: + self.assertRaises(TypeError, http.int_to_base36, n) + for n in ['#', ' ']: self.assertRaises(ValueError, http.base36_to_int, n) - - for n in [123, {1:2}, (1,2,3)]: + for n in [123, {1: 2}, (1, 2, 3), 3.141]: self.assertRaises(TypeError, http.base36_to_int, n) - # non-integer input - self.assertRaises(TypeError, http.int_to_base36, 3.141) - # more explicit output testing for n, b36 in [(0, '0'), (1, '1'), (42, '16'), (818469960, 'django')]: self.assertEqual(http.int_to_base36(n), b36) diff --git a/tests/regressiontests/utils/simplelazyobject.py b/tests/regressiontests/utils/simplelazyobject.py index 960a5e3201..3f81e8f608 100644 --- a/tests/regressiontests/utils/simplelazyobject.py +++ b/tests/regressiontests/utils/simplelazyobject.py @@ -19,17 +19,27 @@ class _ComplexObject(object): def __hash__(self): return hash(self.name) - def __str__(self): - return "I am _ComplexObject(%r)" % self.name + if six.PY3: + def __bytes__(self): + return ("I am _ComplexObject(%r)" % self.name).encode("utf-8") - def __unicode__(self): - return six.text_type(self.name) + def __str__(self): + return self.name + + else: + def __str__(self): + return b"I am _ComplexObject(%r)" % str(self.name) + + def __unicode__(self): + return self.name def __repr__(self): return "_ComplexObject(%r)" % self.name + complex_object = lambda: _ComplexObject("joe") + class TestUtilsSimpleLazyObject(TestCase): """ Tests for SimpleLazyObject @@ -54,11 +64,11 @@ class TestUtilsSimpleLazyObject(TestCase): # proxy __repr__ self.assertTrue("SimpleLazyObject" in repr(SimpleLazyObject(complex_object))) - def test_str(self): - self.assertEqual(str_prefix("I am _ComplexObject(%(_)s'joe')"), - str(SimpleLazyObject(complex_object))) + def test_bytes(self): + self.assertEqual(b"I am _ComplexObject('joe')", + bytes(SimpleLazyObject(complex_object))) - def test_unicode(self): + def test_text(self): self.assertEqual("joe", six.text_type(SimpleLazyObject(complex_object))) def test_class(self): diff --git a/tests/regressiontests/views/tests/i18n.py b/tests/regressiontests/views/tests/i18n.py index 9993ae959c..cb580267d2 100644 --- a/tests/regressiontests/views/tests/i18n.py +++ b/tests/regressiontests/views/tests/i18n.py @@ -5,6 +5,7 @@ import gettext from os import path from django.conf import settings +from django.core.urlresolvers import reverse from django.test import TestCase from django.utils.translation import override, activate, get_language from django.utils.text import javascript_quote @@ -23,6 +24,9 @@ class I18NTests(TestCase): self.assertRedirects(response, 'http://testserver/views/') self.assertEqual(self.client.session['django_language'], lang_code) + def test_setlang_reversal(self): + self.assertEqual(reverse('set_language'), '/views/i18n/setlang/') + def test_jsi18n(self): """The javascript_catalog can be deployed with language settings""" saved_lang = get_language()