summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2012-09-05 09:39:03 -0400
committerAndrew Godwin <andrew@aeracode.org>2012-09-05 09:39:03 -0400
commitb546e7eb633022ee1962570387f22fb2bcea46ed (patch)
treef87f4a2d68fb66afae39148fa35489930710b623 /django
parentcd583d6dbd222ae61331a6965b0e1fc86c974c50 (diff)
parentcff911f4ba3b3e6393c58da5131ce8b188a68f0c (diff)
Merge branch 'master' into schema-alteration
Diffstat (limited to 'django')
-rwxr-xr-xdjango/bin/django-2to3.py10
-rw-r--r--django/conf/__init__.py14
-rw-r--r--django/conf/project_template/project_name/settings.py9
-rw-r--r--django/contrib/admin/models.py4
-rw-r--r--django/contrib/admin/templates/admin/change_form.html2
-rw-r--r--django/contrib/admin/templatetags/log.py2
-rw-r--r--django/contrib/admin/util.py4
-rw-r--r--django/contrib/admin/views/main.py6
-rw-r--r--django/contrib/admindocs/utils.py4
-rw-r--r--django/contrib/auth/decorators.py5
-rw-r--r--django/contrib/auth/forms.py10
-rw-r--r--django/contrib/auth/hashers.py10
-rw-r--r--django/contrib/auth/management/__init__.py18
-rw-r--r--django/contrib/auth/models.py16
-rw-r--r--django/contrib/auth/tests/basic.py44
-rw-r--r--django/contrib/auth/tests/management.py8
-rw-r--r--django/contrib/auth/tests/tokens.py3
-rw-r--r--django/contrib/auth/views.py9
-rw-r--r--django/contrib/comments/models.py7
-rw-r--r--django/contrib/contenttypes/models.py4
-rw-r--r--django/contrib/contenttypes/tests.py4
-rw-r--r--django/contrib/databrowse/datastructures.py19
-rw-r--r--django/contrib/databrowse/tests.py10
-rw-r--r--django/contrib/flatpages/models.py4
-rw-r--r--django/contrib/formtools/tests/__init__.py61
-rw-r--r--django/contrib/formtools/tests/forms.py1
-rw-r--r--django/contrib/formtools/tests/wizard/namedwizardtests/tests.py17
-rw-r--r--django/contrib/formtools/tests/wizard/wizardtests/tests.py15
-rw-r--r--django/contrib/formtools/utils.py2
-rw-r--r--django/contrib/formtools/wizard/storage/base.py4
-rw-r--r--django/contrib/gis/db/backends/base.py4
-rw-r--r--django/contrib/gis/db/backends/oracle/models.py4
-rw-r--r--django/contrib/gis/db/backends/postgis/models.py4
-rw-r--r--django/contrib/gis/db/backends/spatialite/creation.py1
-rw-r--r--django/contrib/gis/db/backends/spatialite/models.py4
-rw-r--r--django/contrib/gis/db/backends/spatialite/operations.py11
-rw-r--r--django/contrib/gis/db/models/query.py3
-rw-r--r--django/contrib/gis/gdal/tests/__init__.py3
-rw-r--r--django/contrib/gis/maps/google/overlays.py7
-rw-r--r--django/contrib/gis/sitemaps/views.py3
-rw-r--r--django/contrib/gis/tests/distapp/models.py22
-rw-r--r--django/contrib/gis/tests/geo3d/models.py26
-rw-r--r--django/contrib/gis/tests/geoadmin/models.py4
-rw-r--r--django/contrib/gis/tests/geoapp/models.py16
-rw-r--r--django/contrib/gis/tests/geoapp/tests.py9
-rw-r--r--django/contrib/gis/tests/geogapp/models.py10
-rw-r--r--django/contrib/gis/tests/relatedapp/models.py14
-rw-r--r--django/contrib/gis/utils/ogrinspect.py2
-rw-r--r--django/contrib/markup/templatetags/markup.py6
-rw-r--r--django/contrib/messages/storage/base.py7
-rw-r--r--django/contrib/redirects/models.py6
-rw-r--r--django/contrib/sessions/backends/base.py11
-rw-r--r--django/contrib/sessions/backends/db.py3
-rw-r--r--django/contrib/sessions/backends/file.py2
-rw-r--r--django/contrib/sitemaps/tests/generic.py7
-rw-r--r--django/contrib/sitemaps/tests/http.py36
-rw-r--r--django/contrib/sitemaps/tests/https.py22
-rw-r--r--django/contrib/sites/models.py7
-rw-r--r--django/contrib/staticfiles/management/commands/collectstatic.py2
-rw-r--r--django/contrib/staticfiles/storage.py20
-rw-r--r--django/contrib/syndication/views.py2
-rw-r--r--django/core/cache/backends/base.py6
-rw-r--r--django/core/cache/backends/db.py7
-rw-r--r--django/core/cache/backends/filebased.py3
-rw-r--r--django/core/cache/backends/memcached.py5
-rw-r--r--django/core/context_processors.py11
-rw-r--r--django/core/files/base.py18
-rw-r--r--django/core/files/move.py2
-rw-r--r--django/core/files/storage.py11
-rw-r--r--django/core/files/uploadedfile.py4
-rw-r--r--django/core/files/uploadhandler.py4
-rw-r--r--django/core/handlers/base.py25
-rw-r--r--django/core/handlers/wsgi.py4
-rw-r--r--django/core/mail/message.py7
-rw-r--r--django/core/management/__init__.py7
-rw-r--r--django/core/management/base.py4
-rw-r--r--django/core/management/commands/inspectdb.py116
-rw-r--r--django/core/management/commands/loaddata.py12
-rw-r--r--django/core/management/commands/makemessages.py60
-rw-r--r--django/core/management/commands/shell.py18
-rw-r--r--django/core/management/commands/syncdb.py2
-rw-r--r--django/core/management/sql.py5
-rw-r--r--django/core/management/templates.py6
-rw-r--r--django/core/management/validation.py4
-rw-r--r--django/core/serializers/base.py6
-rw-r--r--django/core/serializers/json.py8
-rw-r--r--django/core/serializers/pyyaml.py1
-rw-r--r--django/core/servers/basehttp.py10
-rw-r--r--django/core/signing.py47
-rw-r--r--django/core/urlresolvers.py40
-rw-r--r--django/core/validators.py15
-rw-r--r--django/db/backends/__init__.py28
-rw-r--r--django/db/backends/mysql/base.py8
-rw-r--r--django/db/backends/mysql/compiler.py10
-rw-r--r--django/db/backends/mysql/introspection.py22
-rw-r--r--django/db/backends/oracle/base.py23
-rw-r--r--django/db/backends/oracle/compiler.py9
-rw-r--r--django/db/backends/postgresql_psycopg2/base.py6
-rw-r--r--django/db/backends/postgresql_psycopg2/introspection.py4
-rw-r--r--django/db/backends/sqlite3/base.py26
-rw-r--r--django/db/backends/sqlite3/introspection.py18
-rw-r--r--django/db/backends/util.py3
-rw-r--r--django/db/models/base.py30
-rw-r--r--django/db/models/expressions.py6
-rw-r--r--django/db/models/fields/__init__.py5
-rw-r--r--django/db/models/fields/files.py4
-rw-r--r--django/db/models/fields/subclassing.py5
-rw-r--r--django/db/models/options.py10
-rw-r--r--django/db/models/query.py8
-rw-r--r--django/db/models/sql/compiler.py14
-rw-r--r--django/db/models/sql/constants.py2
-rw-r--r--django/db/models/sql/query.py100
-rw-r--r--django/dispatch/saferef.py6
-rw-r--r--django/forms/forms.py12
-rw-r--r--django/forms/formsets.py29
-rw-r--r--django/forms/models.py16
-rw-r--r--django/forms/util.py12
-rw-r--r--django/forms/widgets.py24
-rw-r--r--django/http/__init__.py197
-rw-r--r--django/http/multipartparser.py12
-rw-r--r--django/middleware/csrf.py7
-rw-r--r--django/template/base.py11
-rw-r--r--django/template/defaultfilters.py19
-rw-r--r--django/template/defaulttags.py4
-rw-r--r--django/template/loaders/eggs.py13
-rw-r--r--django/template/response.py14
-rw-r--r--django/templatetags/cache.py6
-rw-r--r--django/test/client.py50
-rw-r--r--django/test/html.py7
-rw-r--r--django/test/signals.py9
-rw-r--r--django/test/testcases.py53
-rw-r--r--django/test/utils.py2
-rw-r--r--django/utils/2to3_fixes/__init__.py0
-rw-r--r--django/utils/2to3_fixes/fix_unicode.py36
-rw-r--r--django/utils/archive.py18
-rw-r--r--django/utils/autoreload.py5
-rw-r--r--django/utils/cache.py11
-rw-r--r--django/utils/crypto.py17
-rw-r--r--django/utils/dateparse.py18
-rw-r--r--django/utils/encoding.py49
-rw-r--r--django/utils/formats.py10
-rw-r--r--django/utils/functional.py1
-rw-r--r--django/utils/html.py19
-rw-r--r--django/utils/html_parser.py188
-rw-r--r--django/utils/http.py18
-rw-r--r--django/utils/ipv6.py2
-rw-r--r--django/utils/safestring.py74
-rw-r--r--django/utils/six.py52
-rw-r--r--django/utils/text.py21
-rw-r--r--django/utils/translation/__init__.py6
-rw-r--r--django/utils/translation/trans_real.py19
-rw-r--r--django/utils/tzinfo.py6
-rw-r--r--django/views/debug.py4
-rw-r--r--django/views/generic/base.py8
-rw-r--r--django/views/generic/dates.py17
-rw-r--r--django/views/static.py2
156 files changed, 1561 insertions, 956 deletions
diff --git a/django/bin/django-2to3.py b/django/bin/django-2to3.py
new file mode 100755
index 0000000000..35566abb94
--- /dev/null
+++ b/django/bin/django-2to3.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+
+# This works exactly like 2to3, except that it uses Django's fixers rather
+# than 2to3's built-in fixers.
+
+import sys
+from lib2to3.main import main
+
+sys.exit(main("django.utils.2to3_fixes"))
+
diff --git a/django/conf/__init__.py b/django/conf/__init__.py
index e1c3fd1808..f4d17ca9f3 100644
--- a/django/conf/__init__.py
+++ b/django/conf/__init__.py
@@ -152,17 +152,25 @@ class UserSettingsHolder(BaseSettings):
Requests for configuration variables not in this class are satisfied
from the module specified in default_settings (if possible).
"""
+ self.__dict__['_deleted'] = set()
self.default_settings = default_settings
def __getattr__(self, name):
+ if name in self._deleted:
+ raise AttributeError
return getattr(self.default_settings, name)
+ def __setattr__(self, name, value):
+ self._deleted.discard(name)
+ return super(UserSettingsHolder, self).__setattr__(name, value)
+
+ def __delattr__(self, name):
+ self._deleted.add(name)
+ return super(UserSettingsHolder, self).__delattr__(name)
+
def __dir__(self):
return list(self.__dict__) + dir(self.default_settings)
- # For Python < 2.6:
- __members__ = property(lambda self: self.__dir__())
-
settings = LazySettings()
diff --git a/django/conf/project_template/project_name/settings.py b/django/conf/project_template/project_name/settings.py
index 99590d6fd5..6bdaa34988 100644
--- a/django/conf/project_template/project_name/settings.py
+++ b/django/conf/project_template/project_name/settings.py
@@ -13,10 +13,11 @@ DATABASES = {
'default': {
'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': '', # Or path to database file if using sqlite3.
- 'USER': '', # Not used with sqlite3.
- 'PASSWORD': '', # Not used with sqlite3.
- 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
- 'PORT': '', # Set to empty string for default. Not used with sqlite3.
+ # The following settings are not used with sqlite3:
+ 'USER': '',
+ 'PASSWORD': '',
+ 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
+ 'PORT': '', # Set to empty string for default.
}
}
diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py
index e31c6d84ed..2b12edd4e2 100644
--- a/django/contrib/admin/models.py
+++ b/django/contrib/admin/models.py
@@ -6,6 +6,7 @@ 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_text
+from django.utils.encoding import python_2_unicode_compatible
ADDITION = 1
CHANGE = 2
@@ -16,6 +17,7 @@ class LogEntryManager(models.Manager):
e = self.model(None, None, user_id, content_type_id, smart_text(object_id), object_repr[:200], action_flag, change_message)
e.save()
+@python_2_unicode_compatible
class LogEntry(models.Model):
action_time = models.DateTimeField(_('action time'), auto_now=True)
user = models.ForeignKey(User)
@@ -36,7 +38,7 @@ class LogEntry(models.Model):
def __repr__(self):
return smart_text(self.action_time)
- def __unicode__(self):
+ def __str__(self):
if self.action_flag == ADDITION:
return _('Added "%(object)s".') % {'object': self.object_repr}
elif self.action_flag == CHANGE:
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html
index 82d7296c85..e27875cdad 100644
--- a/django/contrib/admin/templates/admin/change_form.html
+++ b/django/contrib/admin/templates/admin/change_form.html
@@ -29,7 +29,7 @@
{% if change %}{% if not is_popup %}
<ul class="object-tools">
{% block object-tools-items %}
- <li><a href="history/" class="historylink">{% trans "History" %}</a></li>
+ <li><a href="{% url opts|admin_urlname:'history' original.pk %}" class="historylink">{% trans "History" %}</a></li>
{% if has_absolute_url %}<li><a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
{% endblock %}
</ul>
diff --git a/django/contrib/admin/templatetags/log.py b/django/contrib/admin/templatetags/log.py
index 888b5ed9c3..463e0792f0 100644
--- a/django/contrib/admin/templatetags/log.py
+++ b/django/contrib/admin/templatetags/log.py
@@ -17,7 +17,7 @@ class AdminLogNode(template.Node):
user_id = self.user
if not user_id.isdigit():
user_id = context[self.user].id
- context[self.varname] = LogEntry.objects.filter(user__id__exact=user_id).select_related('content_type', 'user')[:self.limit]
+ context[self.varname] = LogEntry.objects.filter(user__id__exact=user_id).select_related('content_type', 'user')[:int(self.limit)]
return ''
@register.tag
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
index ff90e1d007..889f692ac3 100644
--- a/django/contrib/admin/util.py
+++ b/django/contrib/admin/util.py
@@ -12,7 +12,7 @@ from django.utils import formats
from django.utils.html import format_html
from django.utils.text import capfirst
from django.utils import timezone
-from django.utils.encoding import force_text, smart_text, smart_bytes
+from django.utils.encoding import force_str, force_text, smart_text
from django.utils import six
from django.utils.translation import ungettext
from django.core.urlresolvers import reverse
@@ -277,7 +277,7 @@ def label_for_field(name, model, model_admin=None, return_attr=False):
label = force_text(model._meta.verbose_name)
attr = six.text_type
elif name == "__str__":
- label = smart_bytes(model._meta.verbose_name)
+ label = force_str(model._meta.verbose_name)
attr = bytes
else:
if callable(name):
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 3eabf3dbeb..74ef095b4b 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -6,7 +6,7 @@ from django.core.paginator import InvalidPage
from django.db import models
from django.db.models.fields import FieldDoesNotExist
from django.utils.datastructures import SortedDict
-from django.utils.encoding import force_text, smart_bytes
+from django.utils.encoding import force_str, force_text
from django.utils.translation import ugettext, ugettext_lazy
from django.utils.http import urlencode
@@ -94,7 +94,7 @@ class ChangeList(object):
# 'key' will be used as a keyword argument later, so Python
# requires it to be a string.
del lookup_params[key]
- lookup_params[smart_bytes(key)] = value
+ lookup_params[force_str(key)] = value
if not self.model_admin.lookup_allowed(key, value):
raise SuspiciousOperation("Filtering by %s not allowed" % key)
@@ -148,7 +148,7 @@ class ChangeList(object):
if remove is None: remove = []
p = self.params.copy()
for r in remove:
- for k in p.keys():
+ for k in list(p):
if k.startswith(r):
del p[k]
for k, v in new_params.items():
diff --git a/django/contrib/admindocs/utils.py b/django/contrib/admindocs/utils.py
index 0e10eb4fa3..9be0093dfc 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_bytes
+from django.utils.encoding import force_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_bytes("<%s>" % thing_being_parsed)
+ thing_being_parsed = force_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/auth/decorators.py b/django/contrib/auth/decorators.py
index 7a608d0777..beeb284998 100644
--- a/django/contrib/auth/decorators.py
+++ b/django/contrib/auth/decorators.py
@@ -7,6 +7,7 @@ from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.exceptions import PermissionDenied
from django.utils.decorators import available_attrs
+from django.utils.encoding import force_str
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
@@ -22,9 +23,11 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
if test_func(request.user):
return view_func(request, *args, **kwargs)
path = request.build_absolute_uri()
+ # urlparse chokes on lazy objects in Python 3
+ login_url_as_str = force_str(login_url or settings.LOGIN_URL)
# If the login url is the same scheme and net location then just
# use the path as the "next" url.
- login_scheme, login_netloc = urlparse(login_url or settings.LOGIN_URL)[:2]
+ login_scheme, login_netloc = urlparse(login_url_as_str)[:2]
current_scheme, current_netloc = urlparse(path)[:2]
if ((not login_scheme or login_scheme == current_scheme) and
(not login_netloc or login_netloc == current_netloc)):
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index dfd039f018..75b3ca4ece 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -63,16 +63,16 @@ class UserCreationForm(forms.ModelForm):
}
username = forms.RegexField(label=_("Username"), max_length=30,
regex=r'^[\w.@+-]+$',
- help_text = _("Required. 30 characters or fewer. Letters, digits and "
+ help_text=_("Required. 30 characters or fewer. Letters, digits and "
"@/./+/-/_ only."),
- error_messages = {
+ error_messages={
'invalid': _("This value may contain only letters, numbers and "
"@/./+/-/_ characters.")})
password1 = forms.CharField(label=_("Password"),
widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"),
widget=forms.PasswordInput,
- help_text = _("Enter the same password as above, for verification."))
+ help_text=_("Enter the same password as above, for verification."))
class Meta:
model = User
@@ -107,9 +107,9 @@ class UserCreationForm(forms.ModelForm):
class UserChangeForm(forms.ModelForm):
username = forms.RegexField(
label=_("Username"), max_length=30, regex=r"^[\w.@+-]+$",
- help_text = _("Required. 30 characters or fewer. Letters, digits and "
+ help_text=_("Required. 30 characters or fewer. Letters, digits and "
"@/./+/-/_ only."),
- error_messages = {
+ error_messages={
'invalid': _("This value may contain only letters, numbers and "
"@/./+/-/_ characters.")})
password = ReadOnlyPasswordHashField(label=_("Password"),
diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py
index 45c1f88ab2..bd0c6778c9 100644
--- a/django/contrib/auth/hashers.py
+++ b/django/contrib/auth/hashers.py
@@ -8,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_bytes
+from django.utils.encoding import force_bytes
from django.core.exceptions import ImproperlyConfigured
from django.utils.crypto import (
pbkdf2, constant_time_compare, get_random_string)
@@ -219,7 +219,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
if not iterations:
iterations = self.iterations
hash = pbkdf2(password, salt, iterations, digest=self.digest)
- hash = base64.b64encode(hash).strip()
+ hash = base64.b64encode(hash).decode('ascii').strip()
return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
def verify(self, password, encoded):
@@ -299,7 +299,7 @@ class SHA1PasswordHasher(BasePasswordHasher):
def encode(self, password, salt):
assert password
assert salt and '$' not in salt
- hash = hashlib.sha1(smart_bytes(salt + password)).hexdigest()
+ hash = hashlib.sha1(force_bytes(salt + password)).hexdigest()
return "%s$%s$%s" % (self.algorithm, salt, hash)
def verify(self, password, encoded):
@@ -327,7 +327,7 @@ class MD5PasswordHasher(BasePasswordHasher):
def encode(self, password, salt):
assert password
assert salt and '$' not in salt
- hash = hashlib.md5(smart_bytes(salt + password)).hexdigest()
+ hash = hashlib.md5(force_bytes(salt + password)).hexdigest()
return "%s$%s$%s" % (self.algorithm, salt, hash)
def verify(self, password, encoded):
@@ -361,7 +361,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
return ''
def encode(self, password, salt):
- return hashlib.md5(smart_bytes(password)).hexdigest()
+ return hashlib.md5(force_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 7abd2abcf4..23a053d985 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 import six
from django.utils.six.moves import input
@@ -85,13 +86,22 @@ def get_system_username():
username could not be determined.
"""
try:
- return getpass.getuser().decode(locale.getdefaultlocale()[1])
- except (ImportError, KeyError, UnicodeDecodeError):
+ result = getpass.getuser()
+ except (ImportError, KeyError):
# KeyError will be raised by os.getpwuid() (called by getuser())
# if there is no corresponding entry in the /etc/passwd file
# (a very restricted chroot environment, for example).
- # UnicodeDecodeError - preventive treatment for non-latin Windows.
return ''
+ if not six.PY3:
+ default_locale = locale.getdefaultlocale()[1]
+ if not default_locale:
+ return ''
+ try:
+ result = result.decode(default_locale)
+ except UnicodeDecodeError:
+ # UnicodeDecodeError - preventive treatment for non-latin Windows.
+ return ''
+ return result
def get_default_username(check_db=True):
@@ -108,7 +118,7 @@ def get_default_username(check_db=True):
default_username = get_system_username()
try:
default_username = unicodedata.normalize('NFKD', default_username)\
- .encode('ascii', 'ignore').replace(' ', '').lower()
+ .encode('ascii', 'ignore').decode('ascii').replace(' ', '').lower()
except UnicodeDecodeError:
return ''
if not RE_VALID_USERNAME.match(default_username):
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index 1099aa195b..1c21917a8c 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -16,6 +16,7 @@ from django.contrib.auth.hashers import (
check_password, make_password, is_password_usable, UNUSABLE_PASSWORD)
from django.contrib.auth.signals import user_logged_in
from django.contrib.contenttypes.models import ContentType
+from django.utils.encoding import python_2_unicode_compatible
def update_last_login(sender, user, **kwargs):
@@ -41,6 +42,7 @@ class PermissionManager(models.Manager):
)
+@python_2_unicode_compatible
class Permission(models.Model):
"""
The permissions system provides a way to assign permissions to specific
@@ -76,7 +78,7 @@ class Permission(models.Model):
ordering = ('content_type__app_label', 'content_type__model',
'codename')
- def __unicode__(self):
+ def __str__(self):
return "%s | %s | %s" % (
six.text_type(self.content_type.app_label),
six.text_type(self.content_type),
@@ -94,6 +96,7 @@ class GroupManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
+@python_2_unicode_compatible
class Group(models.Model):
"""
Groups are a generic way of categorizing users to apply permissions, or
@@ -121,7 +124,7 @@ class Group(models.Model):
verbose_name = _('group')
verbose_name_plural = _('groups')
- def __unicode__(self):
+ def __str__(self):
return self.name
def natural_key(self):
@@ -221,6 +224,7 @@ def _user_has_module_perms(user, app_label):
return False
+@python_2_unicode_compatible
class User(models.Model):
"""
Users within the Django authentication system are represented by this
@@ -259,7 +263,7 @@ class User(models.Model):
verbose_name = _('user')
verbose_name_plural = _('users')
- def __unicode__(self):
+ def __str__(self):
return self.username
def natural_key(self):
@@ -403,6 +407,7 @@ class User(models.Model):
return self._profile_cache
+@python_2_unicode_compatible
class AnonymousUser(object):
id = None
pk = None
@@ -416,11 +421,8 @@ class AnonymousUser(object):
def __init__(self):
pass
- def __unicode__(self):
- return 'AnonymousUser'
-
def __str__(self):
- return six.text_type(self).encode('utf-8')
+ return 'AnonymousUser'
def __eq__(self, other):
return isinstance(other, self.__class__)
diff --git a/django/contrib/auth/tests/basic.py b/django/contrib/auth/tests/basic.py
index 21acb2004f..710754b8f1 100644
--- a/django/contrib/auth/tests/basic.py
+++ b/django/contrib/auth/tests/basic.py
@@ -1,13 +1,11 @@
+import locale
+import traceback
+
+from django.contrib.auth.management.commands import createsuperuser
from django.contrib.auth.models import User, AnonymousUser
from django.core.management import call_command
from django.test import TestCase
from django.utils.six import StringIO
-from django.utils.unittest import skipUnless
-
-try:
- import crypt as crypt_module
-except ImportError:
- crypt_module = None
class BasicTestCase(TestCase):
@@ -111,3 +109,37 @@ class BasicTestCase(TestCase):
u = User.objects.get(username="joe+admin@somewhere.org")
self.assertEqual(u.email, 'joe@somewhere.org')
self.assertFalse(u.has_usable_password())
+
+ def test_createsuperuser_nolocale(self):
+ """
+ Check that createsuperuser does not break when no locale is set. See
+ ticket #16017.
+ """
+
+ old_getdefaultlocale = locale.getdefaultlocale
+ old_getpass = createsuperuser.getpass
+ try:
+ # Temporarily remove locale information
+ locale.getdefaultlocale = lambda: (None, None)
+
+ # Temporarily replace getpass to allow interactive code to be used
+ # non-interactively
+ class mock_getpass: pass
+ mock_getpass.getpass = staticmethod(lambda p=None: "nopasswd")
+ createsuperuser.getpass = mock_getpass
+
+ # Call the command in this new environment
+ new_io = StringIO()
+ call_command("createsuperuser", interactive=True, username="nolocale@somewhere.org", email="nolocale@somewhere.org", stdout=new_io)
+
+ except TypeError as e:
+ self.fail("createsuperuser fails if the OS provides no information about the current locale")
+
+ finally:
+ # Re-apply locale and getpass information
+ createsuperuser.getpass = old_getpass
+ locale.getdefaultlocale = old_getdefaultlocale
+
+ # If we were successful, a user should have been created
+ u = User.objects.get(username="nolocale@somewhere.org")
+ self.assertEqual(u.email, 'nolocale@somewhere.org')
diff --git a/django/contrib/auth/tests/management.py b/django/contrib/auth/tests/management.py
index c98b7491c8..ac83086dc3 100644
--- a/django/contrib/auth/tests/management.py
+++ b/django/contrib/auth/tests/management.py
@@ -4,16 +4,20 @@ from django.contrib.auth import models, management
from django.contrib.auth.management.commands import changepassword
from django.core.management.base import CommandError
from django.test import TestCase
+from django.utils import six
from django.utils.six import StringIO
class GetDefaultUsernameTestCase(TestCase):
def setUp(self):
- self._getpass_getuser = management.get_system_username
+ self.old_get_system_username = management.get_system_username
def tearDown(self):
- management.get_system_username = self._getpass_getuser
+ management.get_system_username = self.old_get_system_username
+
+ def test_actual_implementation(self):
+ self.assertIsInstance(management.get_system_username(), six.text_type)
def test_simple(self):
management.get_system_username = lambda: 'joe'
diff --git a/django/contrib/auth/tests/tokens.py b/django/contrib/auth/tests/tokens.py
index beccfc5d07..44117a4f84 100644
--- a/django/contrib/auth/tests/tokens.py
+++ b/django/contrib/auth/tests/tokens.py
@@ -1,9 +1,11 @@
+import sys
from datetime import date, timedelta
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.test import TestCase
+from django.utils import unittest
class TokenGeneratorTest(TestCase):
@@ -51,6 +53,7 @@ class TokenGeneratorTest(TestCase):
p2 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS + 1))
self.assertFalse(p2.check_token(user, tk1))
+ @unittest.skipIf(sys.version_info[:2] >= (3, 0), "Unnecessary test with Python 3")
def test_date_length(self):
"""
Make sure we don't allow overly long dates, causing a potential DoS.
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index ccfc7a1003..f93541b4bf 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -7,6 +7,7 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, QueryDict
from django.template.response import TemplateResponse
+from django.utils.encoding import force_str
from django.utils.http import base36_to_int
from django.utils.translation import ugettext as _
from django.views.decorators.debug import sensitive_post_parameters
@@ -116,10 +117,10 @@ def redirect_to_login(next, login_url=None,
"""
Redirects the user to the login page, passing the given 'next' page
"""
- if not login_url:
- login_url = settings.LOGIN_URL
+ # urlparse chokes on lazy objects in Python 3
+ login_url_as_str = force_str(login_url or settings.LOGIN_URL)
- login_url_parts = list(urlparse(login_url))
+ login_url_parts = list(urlparse(login_url_as_str))
if redirect_field_name:
querystring = QueryDict(login_url_parts[4], mutable=True)
querystring[redirect_field_name] = next
@@ -200,7 +201,7 @@ def password_reset_confirm(request, uidb36=None, token=None,
try:
uid_int = base36_to_int(uidb36)
user = User.objects.get(id=uid_int)
- except (ValueError, User.DoesNotExist):
+ except (ValueError, OverflowError, User.DoesNotExist):
user = None
if user is not None and token_generator.check_token(user, token):
diff --git a/django/contrib/comments/models.py b/django/contrib/comments/models.py
index 475b3c8dea..b043b4187a 100644
--- a/django/contrib/comments/models.py
+++ b/django/contrib/comments/models.py
@@ -8,6 +8,7 @@ from django.core import urlresolvers
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.conf import settings
+from django.utils.encoding import python_2_unicode_compatible
COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH',3000)
@@ -39,6 +40,7 @@ class BaseCommentAbstractModel(models.Model):
args=(self.content_type_id, self.object_pk)
)
+@python_2_unicode_compatible
class Comment(BaseCommentAbstractModel):
"""
A user comment about some object.
@@ -76,7 +78,7 @@ class Comment(BaseCommentAbstractModel):
verbose_name = _('comment')
verbose_name_plural = _('comments')
- def __unicode__(self):
+ def __str__(self):
return "%s: %s..." % (self.name, self.comment[:50])
def save(self, *args, **kwargs):
@@ -153,6 +155,7 @@ class Comment(BaseCommentAbstractModel):
}
return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
+@python_2_unicode_compatible
class CommentFlag(models.Model):
"""
Records a flag on a comment. This is intentionally flexible; right now, a
@@ -182,7 +185,7 @@ class CommentFlag(models.Model):
verbose_name = _('comment flag')
verbose_name_plural = _('comment flags')
- def __unicode__(self):
+ def __str__(self):
return "%s flag of comment ID %s by %s" % \
(self.flag, self.comment_id, self.user.username)
diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py
index e6d547a491..b658655bbb 100644
--- a/django/contrib/contenttypes/models.py
+++ b/django/contrib/contenttypes/models.py
@@ -1,6 +1,7 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_text, force_text
+from django.utils.encoding import python_2_unicode_compatible
class ContentTypeManager(models.Manager):
@@ -122,6 +123,7 @@ class ContentTypeManager(models.Manager):
self.__class__._cache.setdefault(using, {})[key] = ct
self.__class__._cache.setdefault(using, {})[ct.id] = ct
+@python_2_unicode_compatible
class ContentType(models.Model):
name = models.CharField(max_length=100)
app_label = models.CharField(max_length=100)
@@ -135,7 +137,7 @@ class ContentType(models.Model):
ordering = ('name',)
unique_together = (('app_label', 'model'),)
- def __unicode__(self):
+ def __str__(self):
# self.name is deprecated in favor of using model's verbose_name, which
# can be translated. Formal deprecation is delayed until we have DB
# migration to be able to remove the field from the database along with
diff --git a/django/contrib/contenttypes/tests.py b/django/contrib/contenttypes/tests.py
index cfd7e6ff32..2f92a34581 100644
--- a/django/contrib/contenttypes/tests.py
+++ b/django/contrib/contenttypes/tests.py
@@ -8,6 +8,7 @@ from django.http import HttpRequest, Http404
from django.test import TestCase
from django.utils.http import urlquote
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
class ConcreteModel(models.Model):
@@ -17,13 +18,14 @@ class ProxyModel(ConcreteModel):
class Meta:
proxy = True
+@python_2_unicode_compatible
class FooWithoutUrl(models.Model):
"""
Fake model not defining ``get_absolute_url`` for
:meth:`ContentTypesTests.test_shortcut_view_without_get_absolute_url`"""
name = models.CharField(max_length=30, unique=True)
- def __unicode__(self):
+ def __str__(self):
return self.name
diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py
index 810e039894..5f5f46f0d1 100644
--- a/django/contrib/databrowse/datastructures.py
+++ b/django/contrib/databrowse/datastructures.py
@@ -7,8 +7,9 @@ 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_text, smart_bytes, iri_to_uri
+from django.utils.encoding import smart_text, force_str, iri_to_uri
from django.db.models.query import QuerySet
+from django.utils.encoding import python_2_unicode_compatible
EMPTY_VALUE = '(None)'
DISPLAY_SIZE = 100
@@ -22,7 +23,7 @@ class EasyModel(object):
self.verbose_name_plural = model._meta.verbose_name_plural
def __repr__(self):
- return '<EasyModel for %s>' % smart_bytes(self.model._meta.object_name)
+ return force_str('<EasyModel for %s>' % self.model._meta.object_name)
def model_databrowse(self):
"Returns the ModelDatabrowse class for this model."
@@ -61,7 +62,7 @@ class EasyField(object):
self.model, self.field = easy_model, field
def __repr__(self):
- return smart_bytes('<EasyField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
+ return force_str('<EasyField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
def choices(self):
for value, label in self.field.choices:
@@ -79,27 +80,25 @@ class EasyChoice(object):
self.value, self.label = value, label
def __repr__(self):
- return smart_bytes('<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
+ return force_str('<EasyChoice for %s.%s>' % (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))
+@python_2_unicode_compatible
class EasyInstance(object):
def __init__(self, easy_model, instance):
self.model, self.instance = easy_model, instance
def __repr__(self):
- return smart_bytes('<EasyInstance for %s (%s)>' % (self.model.model._meta.object_name, self.instance._get_pk_val()))
+ return force_str('<EasyInstance for %s (%s)>' % (self.model.model._meta.object_name, self.instance._get_pk_val()))
- def __unicode__(self):
+ def __str__(self):
val = smart_text(self.instance)
if len(val) > DISPLAY_SIZE:
return val[:DISPLAY_SIZE] + '...'
return val
- def __str__(self):
- return self.__unicode__().encode('utf-8')
-
def pk(self):
return self.instance._get_pk_val()
@@ -136,7 +135,7 @@ class EasyInstanceField(object):
self.raw_value = getattr(instance.instance, field.name)
def __repr__(self):
- return smart_bytes('<EasyInstanceField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
+ return force_str('<EasyInstanceField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
def values(self):
"""
diff --git a/django/contrib/databrowse/tests.py b/django/contrib/databrowse/tests.py
index 149383cf72..d649b4af67 100644
--- a/django/contrib/databrowse/tests.py
+++ b/django/contrib/databrowse/tests.py
@@ -1,26 +1,30 @@
from django.contrib import databrowse
from django.db import models
from django.test import TestCase
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class SomeModel(models.Model):
some_field = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.some_field
+@python_2_unicode_compatible
class SomeOtherModel(models.Model):
some_other_field = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.some_other_field
+@python_2_unicode_compatible
class YetAnotherModel(models.Model):
yet_another_field = models.CharField(max_length=50)
- def __unicode__(self):
+ def __str__(self):
return self.yet_another_field
diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py
index 42ec155f34..3a5b4d6135 100644
--- a/django/contrib/flatpages/models.py
+++ b/django/contrib/flatpages/models.py
@@ -3,8 +3,10 @@ from __future__ import unicode_literals
from django.db import models
from django.contrib.sites.models import Site
from django.utils.translation import ugettext_lazy as _
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class FlatPage(models.Model):
url = models.CharField(_('URL'), max_length=100, db_index=True)
title = models.CharField(_('title'), max_length=200)
@@ -21,7 +23,7 @@ class FlatPage(models.Model):
verbose_name_plural = _('flat pages')
ordering = ('url',)
- def __unicode__(self):
+ def __str__(self):
return "%s -- %s" % (self.url, self.title)
def get_absolute_url(self):
diff --git a/django/contrib/formtools/tests/__init__.py b/django/contrib/formtools/tests/__init__.py
index ee93479cbd..15941332ed 100644
--- a/django/contrib/formtools/tests/__init__.py
+++ b/django/contrib/formtools/tests/__init__.py
@@ -1,6 +1,9 @@
+# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+import datetime
import os
+import pickle
import re
import warnings
@@ -16,6 +19,7 @@ from django.contrib.formtools.tests.wizard import *
from django.contrib.formtools.tests.forms import *
success_string = "Done was called!"
+success_string_encoded = success_string.encode()
class TestFormPreview(preview.FormPreview):
def get_context(self, request, form):
@@ -78,7 +82,7 @@ class PreviewTests(TestCase):
"""
# Pass strings for form submittal and add stage variable to
# show we previously saw first stage of the form.
- self.test_data.update({'stage': 1})
+ self.test_data.update({'stage': 1, 'date1': datetime.date(2006, 10, 25)})
response = self.client.post('/preview/', self.test_data)
# Check to confirm stage is set to 2 in output form.
stage = self.input % 2
@@ -96,13 +100,13 @@ class PreviewTests(TestCase):
"""
# Pass strings for form submittal and add stage variable to
# show we previously saw first stage of the form.
- self.test_data.update({'stage':2})
+ self.test_data.update({'stage': 2, 'date1': datetime.date(2006, 10, 25)})
response = self.client.post('/preview/', self.test_data)
- self.assertNotEqual(response.content, success_string)
+ self.assertNotEqual(response.content, success_string_encoded)
hash = self.preview.security_hash(None, TestForm(self.test_data))
self.test_data.update({'hash': hash})
response = self.client.post('/preview/', self.test_data)
- self.assertEqual(response.content, success_string)
+ self.assertEqual(response.content, success_string_encoded)
def test_bool_submit(self):
"""
@@ -122,7 +126,7 @@ class PreviewTests(TestCase):
self.test_data.update({'hash': hash, 'bool1': 'False'})
with warnings.catch_warnings(record=True):
response = self.client.post('/preview/', self.test_data)
- self.assertEqual(response.content, success_string)
+ self.assertEqual(response.content, success_string_encoded)
def test_form_submit_good_hash(self):
"""
@@ -133,11 +137,11 @@ class PreviewTests(TestCase):
# show we previously saw first stage of the form.
self.test_data.update({'stage':2})
response = self.client.post('/preview/', self.test_data)
- self.assertNotEqual(response.content, success_string)
+ self.assertNotEqual(response.content, success_string_encoded)
hash = utils.form_hmac(TestForm(self.test_data))
self.test_data.update({'hash': hash})
response = self.client.post('/preview/', self.test_data)
- self.assertEqual(response.content, success_string)
+ self.assertEqual(response.content, success_string_encoded)
def test_form_submit_bad_hash(self):
@@ -150,11 +154,11 @@ class PreviewTests(TestCase):
self.test_data.update({'stage':2})
response = self.client.post('/preview/', self.test_data)
self.assertEqual(response.status_code, 200)
- self.assertNotEqual(response.content, success_string)
+ self.assertNotEqual(response.content, success_string_encoded)
hash = utils.form_hmac(TestForm(self.test_data)) + "bad"
self.test_data.update({'hash': hash})
response = self.client.post('/previewpreview/', self.test_data)
- self.assertNotEqual(response.content, success_string)
+ self.assertNotEqual(response.content, success_string_encoded)
class FormHmacTests(unittest.TestCase):
@@ -165,8 +169,8 @@ class FormHmacTests(unittest.TestCase):
leading/trailing whitespace so as to be friendly to broken browsers that
submit it (usually in textareas).
"""
- f1 = HashTestForm({'name': 'joe', 'bio': 'Nothing notable.'})
- f2 = HashTestForm({'name': ' joe', 'bio': 'Nothing notable. '})
+ f1 = HashTestForm({'name': 'joe', 'bio': 'Speaking español.'})
+ f2 = HashTestForm({'name': ' joe', 'bio': 'Speaking español. '})
hash1 = utils.form_hmac(f1)
hash2 = utils.form_hmac(f2)
self.assertEqual(hash1, hash2)
@@ -270,7 +274,10 @@ class WizardTests(TestCase):
"""
data = {"0-field": "test",
"1-field": "test2",
- "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ "hash_0": {
+ 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ 3: "9355d5dff22d49dbad58e46189982cec649f9f5b",
+ }[pickle.HIGHEST_PROTOCOL],
"wizard_step": "1"}
response = self.client.post('/wizard1/', data)
self.assertEqual(2, response.context['step0'])
@@ -295,15 +302,24 @@ class WizardTests(TestCase):
wizard = WizardWithProcessStep([WizardPageOneForm])
data = {"0-field": "test",
"1-field": "test2",
- "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ "hash_0": {
+ 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ 3: "9355d5dff22d49dbad58e46189982cec649f9f5b",
+ }[pickle.HIGHEST_PROTOCOL],
"wizard_step": "1"}
wizard(DummyRequest(POST=data))
self.assertTrue(reached[0])
data = {"0-field": "test",
"1-field": "test2",
- "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
- "hash_1": "1e6f6315da42e62f33a30640ec7e007ad3fbf1a1",
+ "hash_0": {
+ 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ 3: "9355d5dff22d49dbad58e46189982cec649f9f5b",
+ }[pickle.HIGHEST_PROTOCOL],
+ "hash_1": {
+ 2: "1e6f6315da42e62f33a30640ec7e007ad3fbf1a1",
+ 3: "c33142ef9d01b1beae238adf22c3c6c57328f51a",
+ }[pickle.HIGHEST_PROTOCOL],
"wizard_step": "2"}
self.assertRaises(http.Http404, wizard, DummyRequest(POST=data))
@@ -325,7 +341,10 @@ class WizardTests(TestCase):
WizardPageThreeForm])
data = {"0-field": "test",
"1-field": "test2",
- "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ "hash_0": {
+ 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ 3: "9355d5dff22d49dbad58e46189982cec649f9f5b",
+ }[pickle.HIGHEST_PROTOCOL],
"wizard_step": "1"}
wizard(DummyRequest(POST=data))
self.assertTrue(reached[0])
@@ -349,7 +368,10 @@ class WizardTests(TestCase):
data = {"0-field": "test",
"1-field": "test2",
- "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ "hash_0": {
+ 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ 3: "9355d5dff22d49dbad58e46189982cec649f9f5b",
+ }[pickle.HIGHEST_PROTOCOL],
"wizard_step": "1"}
wizard(DummyRequest(POST=data))
self.assertTrue(reached[0])
@@ -375,7 +397,10 @@ class WizardTests(TestCase):
WizardPageThreeForm])
data = {"0-field": "test",
"1-field": "test2",
- "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ "hash_0": {
+ 2: "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
+ 3: "9355d5dff22d49dbad58e46189982cec649f9f5b",
+ }[pickle.HIGHEST_PROTOCOL],
"wizard_step": "1"}
wizard(DummyRequest(POST=data))
self.assertTrue(reached[0])
diff --git a/django/contrib/formtools/tests/forms.py b/django/contrib/formtools/tests/forms.py
index 9f6f596d3c..1ed2ab48bb 100644
--- a/django/contrib/formtools/tests/forms.py
+++ b/django/contrib/formtools/tests/forms.py
@@ -21,6 +21,7 @@ class TestForm(forms.Form):
field1 = forms.CharField()
field1_ = forms.CharField()
bool1 = forms.BooleanField(required=False)
+ date1 = forms.DateField(required=False)
class HashTestForm(forms.Form):
name = forms.CharField()
diff --git a/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py b/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py
index a860edd9e9..7529d89a2c 100644
--- a/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py
+++ b/django/contrib/formtools/tests/wizard/namedwizardtests/tests.py
@@ -122,6 +122,7 @@ class NamedWizardTests(object):
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
post_data = self.wizard_step_data[1]
+ post_data['form2-file1'].close()
post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post(
reverse(self.wizard_urlname,
@@ -149,7 +150,9 @@ class NamedWizardTests(object):
self.assertEqual(response.status_code, 200)
all_data = response.context['form_list']
- self.assertEqual(all_data[1]['file1'].read(), open(__file__, 'rb').read())
+ with open(__file__, 'rb') as f:
+ self.assertEqual(all_data[1]['file1'].read(), f.read())
+ all_data[1]['file1'].close()
del all_data[1]['file1']
self.assertEqual(all_data, [
{'name': 'Pony', 'thirsty': True, 'user': self.testuser},
@@ -182,9 +185,10 @@ class NamedWizardTests(object):
response = self.client.get(step2_url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
- self.assertEqual(
- response.context['wizard']['form'].files['form2-file1'].read(),
- open(__file__, 'rb').read())
+ with open(__file__, 'rb') as f:
+ self.assertEqual(
+ response.context['wizard']['form'].files['form2-file1'].read(),
+ f.read())
response = self.client.post(
reverse(self.wizard_urlname,
@@ -201,7 +205,9 @@ class NamedWizardTests(object):
self.assertEqual(response.status_code, 200)
all_data = response.context['all_cleaned_data']
- self.assertEqual(all_data['file1'].read(), open(__file__, 'rb').read())
+ with open(__file__, 'rb') as f:
+ self.assertEqual(all_data['file1'].read(), f.read())
+ all_data['file1'].close()
del all_data['file1']
self.assertEqual(
all_data,
@@ -225,6 +231,7 @@ class NamedWizardTests(object):
self.assertEqual(response.status_code, 200)
post_data = self.wizard_step_data[1]
+ post_data['form2-file1'].close()
post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post(
reverse(self.wizard_urlname,
diff --git a/django/contrib/formtools/tests/wizard/wizardtests/tests.py b/django/contrib/formtools/tests/wizard/wizardtests/tests.py
index a35664b322..586bd59341 100644
--- a/django/contrib/formtools/tests/wizard/wizardtests/tests.py
+++ b/django/contrib/formtools/tests/wizard/wizardtests/tests.py
@@ -95,7 +95,9 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200)
all_data = response.context['form_list']
- self.assertEqual(all_data[1]['file1'].read(), open(__file__, 'rb').read())
+ with open(__file__, 'rb') as f:
+ self.assertEqual(all_data[1]['file1'].read(), f.read())
+ all_data[1]['file1'].close()
del all_data[1]['file1']
self.assertEqual(all_data, [
{'name': 'Pony', 'thirsty': True, 'user': self.testuser},
@@ -112,8 +114,9 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200)
post_data = self.wizard_step_data[1]
- post_data['form2-file1'] = open(__file__, 'rb')
- response = self.client.post(self.wizard_url, post_data)
+ with open(__file__, 'rb') as post_file:
+ post_data['form2-file1'] = post_file
+ response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200)
response = self.client.post(self.wizard_url, self.wizard_step_data[2])
@@ -123,7 +126,9 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200)
all_data = response.context['all_cleaned_data']
- self.assertEqual(all_data['file1'].read(), open(__file__, 'rb').read())
+ with open(__file__, 'rb') as f:
+ self.assertEqual(all_data['file1'].read(), f.read())
+ all_data['file1'].close()
del all_data['file1']
self.assertEqual(all_data, {
'name': 'Pony', 'thirsty': True, 'user': self.testuser,
@@ -140,6 +145,7 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200)
post_data = self.wizard_step_data[1]
+ post_data['form2-file1'].close()
post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200)
@@ -167,6 +173,7 @@ class WizardTests(object):
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
post_data = self.wizard_step_data[1]
+ post_data['form2-file1'].close()
post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200)
diff --git a/django/contrib/formtools/utils.py b/django/contrib/formtools/utils.py
index 8763cded07..76277c6b49 100644
--- a/django/contrib/formtools/utils.py
+++ b/django/contrib/formtools/utils.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
# Do not try cPickle here (see #18340)
import pickle
diff --git a/django/contrib/formtools/wizard/storage/base.py b/django/contrib/formtools/wizard/storage/base.py
index 05c9f6f121..aafc833484 100644
--- a/django/contrib/formtools/wizard/storage/base.py
+++ b/django/contrib/formtools/wizard/storage/base.py
@@ -1,6 +1,5 @@
from django.core.files.uploadedfile import UploadedFile
from django.utils.datastructures import MultiValueDict
-from django.utils.encoding import smart_bytes
from django.utils.functional import lazy_property
from django.utils import six
@@ -74,8 +73,7 @@ class BaseStorage(object):
files = {}
for field, field_dict in six.iteritems(wizard_files):
- field_dict = dict((smart_bytes(k), v)
- for k, v in six.iteritems(field_dict))
+ field_dict = field_dict.copy()
tmp_name = field_dict.pop('tmp_name')
files[field] = UploadedFile(
file=self.file_storage.open(tmp_name), **field_dict)
diff --git a/django/contrib/gis/db/backends/base.py b/django/contrib/gis/db/backends/base.py
index d9f3546cff..f7af420a8d 100644
--- a/django/contrib/gis/db/backends/base.py
+++ b/django/contrib/gis/db/backends/base.py
@@ -5,6 +5,7 @@ Base/mixin classes for the spatial backend database operations and the
import re
from django.contrib.gis import gdal
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
class BaseSpatialOperations(object):
"""
@@ -131,6 +132,7 @@ class BaseSpatialOperations(object):
def spatial_ref_sys(self):
raise NotImplementedError
+@python_2_unicode_compatible
class SpatialRefSysMixin(object):
"""
The SpatialRefSysMixin is a class used by the database-dependent
@@ -325,7 +327,7 @@ class SpatialRefSysMixin(object):
radius, flattening = sphere_params
return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening)
- def __unicode__(self):
+ def __str__(self):
"""
Returns the string representation. If GDAL is installed,
it will be 'pretty' OGC WKT.
diff --git a/django/contrib/gis/db/backends/oracle/models.py b/django/contrib/gis/db/backends/oracle/models.py
index ed29f7bb38..b7deb3a946 100644
--- a/django/contrib/gis/db/backends/oracle/models.py
+++ b/django/contrib/gis/db/backends/oracle/models.py
@@ -9,7 +9,9 @@
"""
from django.contrib.gis.db import models
from django.contrib.gis.db.backends.base import SpatialRefSysMixin
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class GeometryColumns(models.Model):
"Maps to the Oracle USER_SDO_GEOM_METADATA table."
table_name = models.CharField(max_length=32)
@@ -36,7 +38,7 @@ class GeometryColumns(models.Model):
"""
return 'column_name'
- def __unicode__(self):
+ def __str__(self):
return '%s - %s (SRID: %s)' % (self.table_name, self.column_name, self.srid)
class SpatialRefSys(models.Model, SpatialRefSysMixin):
diff --git a/django/contrib/gis/db/backends/postgis/models.py b/django/contrib/gis/db/backends/postgis/models.py
index a38598343c..e8052594c6 100644
--- a/django/contrib/gis/db/backends/postgis/models.py
+++ b/django/contrib/gis/db/backends/postgis/models.py
@@ -3,7 +3,9 @@
"""
from django.db import models
from django.contrib.gis.db.backends.base import SpatialRefSysMixin
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class GeometryColumns(models.Model):
"""
The 'geometry_columns' table from the PostGIS. See the PostGIS
@@ -37,7 +39,7 @@ class GeometryColumns(models.Model):
"""
return 'f_geometry_column'
- def __unicode__(self):
+ def __str__(self):
return "%s.%s - %dD %s field (SRID: %d)" % \
(self.f_table_name, self.f_geometry_column,
self.coord_dimension, self.type, self.srid)
diff --git a/django/contrib/gis/db/backends/spatialite/creation.py b/django/contrib/gis/db/backends/spatialite/creation.py
index d0a5f82033..27332b9b57 100644
--- a/django/contrib/gis/db/backends/spatialite/creation.py
+++ b/django/contrib/gis/db/backends/spatialite/creation.py
@@ -30,6 +30,7 @@ class SpatiaLiteCreation(DatabaseCreation):
self.connection.close()
self.connection.settings_dict["NAME"] = test_database_name
+ self.connection.ops.confirm_spatial_components_versions()
# Need to load the SpatiaLite initialization SQL before running `syncdb`.
self.load_spatialite_sql()
diff --git a/django/contrib/gis/db/backends/spatialite/models.py b/django/contrib/gis/db/backends/spatialite/models.py
index 684c5d8fc7..b281f0bc62 100644
--- a/django/contrib/gis/db/backends/spatialite/models.py
+++ b/django/contrib/gis/db/backends/spatialite/models.py
@@ -3,7 +3,9 @@
"""
from django.db import models
from django.contrib.gis.db.backends.base import SpatialRefSysMixin
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class GeometryColumns(models.Model):
"""
The 'geometry_columns' table from SpatiaLite.
@@ -35,7 +37,7 @@ class GeometryColumns(models.Model):
"""
return 'f_geometry_column'
- def __unicode__(self):
+ def __str__(self):
return "%s.%s - %dD %s field (SRID: %d)" % \
(self.f_table_name, self.f_geometry_column,
self.coord_dimension, self.type, self.srid)
diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py
index 60fe0a8069..80f05ef076 100644
--- a/django/contrib/gis/db/backends/spatialite/operations.py
+++ b/django/contrib/gis/db/backends/spatialite/operations.py
@@ -113,6 +113,12 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
def __init__(self, connection):
super(DatabaseOperations, self).__init__(connection)
+ # Creating the GIS terms dictionary.
+ gis_terms = ['isnull']
+ gis_terms += self.geometry_functions.keys()
+ self.gis_terms = dict([(term, None) for term in gis_terms])
+
+ def confirm_spatial_components_versions(self):
# Determine the version of the SpatiaLite library.
try:
vtup = self.spatialite_version_tuple()
@@ -129,11 +135,6 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
'SQL loaded on this database?' %
(self.connection.settings_dict['NAME'], msg))
- # Creating the GIS terms dictionary.
- gis_terms = ['isnull']
- gis_terms += list(self.geometry_functions)
- self.gis_terms = dict([(term, None) for term in gis_terms])
-
if version >= (2, 4, 0):
# Spatialite 2.4.0-RC4 added AsGML and AsKML, however both
# RC2 (shipped in popular Debian/Ubuntu packages) and RC4
diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py
index cc61dfa4d2..d87e151aea 100644
--- a/django/contrib/gis/db/models/query.py
+++ b/django/contrib/gis/db/models/query.py
@@ -1,16 +1,15 @@
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
from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery
from django.contrib.gis.geometry.backend import Geometry
from django.contrib.gis.measure import Area, Distance
-from django.utils import six
from django.utils import six
+
class GeoQuerySet(QuerySet):
"The Geographic QuerySet."
diff --git a/django/contrib/gis/gdal/tests/__init__.py b/django/contrib/gis/gdal/tests/__init__.py
index eb72a38775..262d294a43 100644
--- a/django/contrib/gis/gdal/tests/__init__.py
+++ b/django/contrib/gis/gdal/tests/__init__.py
@@ -19,7 +19,8 @@ test_suites = [test_driver.suite(),
def suite():
"Builds a test suite for the GDAL tests."
s = TestSuite()
- map(s.addTest, test_suites)
+ for test_suite in test_suites:
+ s.addTest(test_suite)
return s
def run(verbosity=1):
diff --git a/django/contrib/gis/maps/google/overlays.py b/django/contrib/gis/maps/google/overlays.py
index 28603ac422..b82d967da6 100644
--- a/django/contrib/gis/maps/google/overlays.py
+++ b/django/contrib/gis/maps/google/overlays.py
@@ -2,8 +2,10 @@ from django.contrib.gis.geos import fromstr, Point, LineString, LinearRing, Poly
from django.utils.functional import total_ordering
from django.utils.safestring import mark_safe
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class GEvent(object):
"""
A Python wrapper for the Google GEvent object.
@@ -48,10 +50,11 @@ class GEvent(object):
self.event = event
self.action = action
- def __unicode__(self):
+ def __str__(self):
"Returns the parameter part of a GEvent."
return mark_safe('"%s", %s' %(self.event, self.action))
+@python_2_unicode_compatible
class GOverlayBase(object):
def __init__(self):
self.events = []
@@ -64,7 +67,7 @@ class GOverlayBase(object):
"Attaches a GEvent to the overlay object."
self.events.append(event)
- def __unicode__(self):
+ def __str__(self):
"The string representation is the JavaScript API call."
return mark_safe('%s(%s)' % (self.__class__.__name__, self.js_params))
diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py
index 8bcdba1b44..36e48f7d20 100644
--- a/django/contrib/gis/sitemaps/views.py
+++ b/django/contrib/gis/sitemaps/views.py
@@ -8,7 +8,6 @@ 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_bytes
from django.utils import six
from django.utils.translation import ugettext as _
@@ -61,7 +60,7 @@ def sitemap(request, sitemaps, section=None):
raise Http404(_("Page %s empty") % page)
except PageNotAnInteger:
raise Http404(_("No page '%s'") % page)
- xml = smart_bytes(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls}))
+ xml = 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/distapp/models.py b/django/contrib/gis/tests/distapp/models.py
index 76e7d3a03f..bf08829eae 100644
--- a/django/contrib/gis/tests/distapp/models.py
+++ b/django/contrib/gis/tests/distapp/models.py
@@ -1,50 +1,58 @@
from django.contrib.gis.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class SouthTexasCity(models.Model):
"City model on projected coordinate system for South Texas."
name = models.CharField(max_length=30)
point = models.PointField(srid=32140)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class SouthTexasCityFt(models.Model):
"Same City model as above, but U.S. survey feet are the units."
name = models.CharField(max_length=30)
point = models.PointField(srid=2278)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class AustraliaCity(models.Model):
"City model for Australia, using WGS84."
name = models.CharField(max_length=30)
point = models.PointField()
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class CensusZipcode(models.Model):
"Model for a few South Texas ZIP codes (in original Census NAD83)."
name = models.CharField(max_length=5)
poly = models.PolygonField(srid=4269)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class SouthTexasZipcode(models.Model):
"Model for a few South Texas ZIP codes."
name = models.CharField(max_length=5)
poly = models.PolygonField(srid=32140, null=True)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class Interstate(models.Model):
"Geodetic model for U.S. Interstates."
name = models.CharField(max_length=10)
path = models.LineStringField()
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class SouthTexasInterstate(models.Model):
"Projected model for South Texas Interstates."
name = models.CharField(max_length=10)
path = models.LineStringField(srid=32140)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
diff --git a/django/contrib/gis/tests/geo3d/models.py b/django/contrib/gis/tests/geo3d/models.py
index 3c4f77ee05..81e5f55f78 100644
--- a/django/contrib/gis/tests/geo3d/models.py
+++ b/django/contrib/gis/tests/geo3d/models.py
@@ -1,59 +1,67 @@
from django.contrib.gis.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class City3D(models.Model):
name = models.CharField(max_length=30)
point = models.PointField(dim=3)
objects = models.GeoManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Interstate2D(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField(srid=4269)
objects = models.GeoManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Interstate3D(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField(dim=3, srid=4269)
objects = models.GeoManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class InterstateProj2D(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField(srid=32140)
objects = models.GeoManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class InterstateProj3D(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField(dim=3, srid=32140)
objects = models.GeoManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Polygon2D(models.Model):
name = models.CharField(max_length=30)
poly = models.PolygonField(srid=32140)
objects = models.GeoManager()
-
- def __unicode__(self):
+
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Polygon3D(models.Model):
name = models.CharField(max_length=30)
poly = models.PolygonField(dim=3, srid=32140)
objects = models.GeoManager()
-
- def __unicode__(self):
+
+ def __str__(self):
return self.name
class Point2D(models.Model):
diff --git a/django/contrib/gis/tests/geoadmin/models.py b/django/contrib/gis/tests/geoadmin/models.py
index 51a76d1a0e..af0898823d 100644
--- a/django/contrib/gis/tests/geoadmin/models.py
+++ b/django/contrib/gis/tests/geoadmin/models.py
@@ -1,10 +1,12 @@
from django.contrib.gis.db import models
from django.contrib.gis import admin
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class City(models.Model):
name = models.CharField(max_length=30)
point = models.PointField()
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
admin.site.register(City, admin.OSMGeoAdmin)
diff --git a/django/contrib/gis/tests/geoapp/models.py b/django/contrib/gis/tests/geoapp/models.py
index 79061e1cfc..abde509c8b 100644
--- a/django/contrib/gis/tests/geoapp/models.py
+++ b/django/contrib/gis/tests/geoapp/models.py
@@ -1,20 +1,23 @@
from django.contrib.gis.db import models
from django.contrib.gis.tests.utils import mysql, spatialite
+from django.utils.encoding import python_2_unicode_compatible
# MySQL spatial indices can't handle NULL geometries.
null_flag = not mysql
+@python_2_unicode_compatible
class Country(models.Model):
name = models.CharField(max_length=30)
mpoly = models.MultiPolygonField() # SRID, by default, is 4326
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class City(models.Model):
name = models.CharField(max_length=30)
point = models.PointField()
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
# This is an inherited model from City
class PennsylvaniaCity(City):
@@ -22,28 +25,31 @@ class PennsylvaniaCity(City):
founded = models.DateTimeField(null=True)
objects = models.GeoManager() # TODO: This should be implicitly inherited.
+@python_2_unicode_compatible
class State(models.Model):
name = models.CharField(max_length=30)
poly = models.PolygonField(null=null_flag) # Allowing NULL geometries here.
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class Track(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField()
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
class Truth(models.Model):
val = models.BooleanField()
objects = models.GeoManager()
if not spatialite:
+ @python_2_unicode_compatible
class Feature(models.Model):
name = models.CharField(max_length=20)
geom = models.GeometryField()
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
class MinusOneSRID(models.Model):
geom = models.PointField(srid=-1) # Minus one SRID.
diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py
index b06d6b5e1b..fbe30e8841 100644
--- a/django/contrib/gis/tests/geoapp/tests.py
+++ b/django/contrib/gis/tests/geoapp/tests.py
@@ -186,6 +186,15 @@ class GeoModelTest(TestCase):
self.assertEqual(1, qs.count())
for pc in qs: self.assertEqual(32128, pc.point.srid)
+ def test_raw_sql_query(self):
+ "Testing raw SQL query."
+ cities1 = City.objects.all()
+ # Only PostGIS would support a 'select *' query because of its recognized
+ # HEXEWKB format for geometry fields
+ cities2 = City.objects.raw('select id, name, asText(point) from geoapp_city')
+ self.assertEqual(len(cities1), len(list(cities2)))
+ self.assertTrue(isinstance(cities2[0].point, Point))
+
class GeoLookupTest(TestCase):
diff --git a/django/contrib/gis/tests/geogapp/models.py b/django/contrib/gis/tests/geogapp/models.py
index 3696ba2ff4..7e802f9321 100644
--- a/django/contrib/gis/tests/geogapp/models.py
+++ b/django/contrib/gis/tests/geogapp/models.py
@@ -1,20 +1,24 @@
from django.contrib.gis.db import models
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class City(models.Model):
name = models.CharField(max_length=30)
point = models.PointField(geography=True)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
+@python_2_unicode_compatible
class Zipcode(models.Model):
code = models.CharField(max_length=10)
poly = models.PolygonField(geography=True)
objects = models.GeoManager()
- def __unicode__(self): return self.code
+ def __str__(self): return self.code
+@python_2_unicode_compatible
class County(models.Model):
name = models.CharField(max_length=25)
state = models.CharField(max_length=20)
mpoly = models.MultiPolygonField(geography=True)
objects = models.GeoManager()
- def __unicode__(self): return ' County, '.join([self.name, self.state])
+ def __str__(self): return ' County, '.join([self.name, self.state])
diff --git a/django/contrib/gis/tests/relatedapp/models.py b/django/contrib/gis/tests/relatedapp/models.py
index aec4e15749..659fef7a93 100644
--- a/django/contrib/gis/tests/relatedapp/models.py
+++ b/django/contrib/gis/tests/relatedapp/models.py
@@ -1,37 +1,41 @@
from django.contrib.gis.db import models
from django.contrib.localflavor.us.models import USStateField
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Location(models.Model):
point = models.PointField()
objects = models.GeoManager()
- def __unicode__(self): return self.point.wkt
+ def __str__(self): return self.point.wkt
+@python_2_unicode_compatible
class City(models.Model):
name = models.CharField(max_length=50)
state = USStateField()
location = models.ForeignKey(Location)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
class AugmentedLocation(Location):
extra_text = models.TextField(blank=True)
objects = models.GeoManager()
-
+
class DirectoryEntry(models.Model):
listing_text = models.CharField(max_length=50)
location = models.ForeignKey(AugmentedLocation)
objects = models.GeoManager()
+@python_2_unicode_compatible
class Parcel(models.Model):
name = models.CharField(max_length=30)
city = models.ForeignKey(City)
center1 = models.PointField()
# Throwing a curveball w/`db_column` here.
- center2 = models.PointField(srid=2276, db_column='mycenter')
+ center2 = models.PointField(srid=2276, db_column='mycenter')
border1 = models.PolygonField()
border2 = models.PolygonField(srid=2276)
objects = models.GeoManager()
- def __unicode__(self): return self.name
+ def __str__(self): return self.name
# These use the GeoManager but do not have any geographic fields.
class Author(models.Model):
diff --git a/django/contrib/gis/utils/ogrinspect.py b/django/contrib/gis/utils/ogrinspect.py
index f8977059d9..4266ee4b4c 100644
--- a/django/contrib/gis/utils/ogrinspect.py
+++ b/django/contrib/gis/utils/ogrinspect.py
@@ -223,4 +223,4 @@ def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=Non
if name_field:
yield ''
- yield ' def __unicode__(self): return self.%s' % name_field
+ yield ' def __str__(self): return self.%s' % name_field
diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py
index af9c842f42..18b7475ca7 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_bytes, force_text
+from django.utils.encoding import force_bytes, force_text
from django.utils.safestring import mark_safe
register = template.Library()
@@ -27,7 +27,7 @@ def textile(value):
raise template.TemplateSyntaxError("Error in 'textile' filter: The Python textile library isn't installed.")
return force_text(value)
else:
- return mark_safe(force_text(textile.textile(smart_bytes(value), encoding='utf-8', output='utf-8')))
+ return mark_safe(force_text(textile.textile(force_bytes(value), encoding='utf-8', output='utf-8')))
@register.filter(is_safe=True)
def markdown(value, arg=''):
@@ -80,5 +80,5 @@ def restructuredtext(value):
return force_text(value)
else:
docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {})
- parts = publish_parts(source=smart_bytes(value), writer_name="html4css1", settings_overrides=docutils_settings)
+ parts = publish_parts(source=force_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 5433bbff28..7fe8a077ed 100644
--- a/django/contrib/messages/storage/base.py
+++ b/django/contrib/messages/storage/base.py
@@ -1,14 +1,15 @@
from __future__ import unicode_literals
from django.conf import settings
-from django.utils.encoding import force_text, StrAndUnicode
+from django.utils.encoding import force_text, python_2_unicode_compatible
from django.contrib.messages import constants, utils
LEVEL_TAGS = utils.get_level_tags()
-class Message(StrAndUnicode):
+@python_2_unicode_compatible
+class Message(object):
"""
Represents an actual message that can be stored in any of the supported
storage classes (typically session- or cookie-based) and rendered in a view
@@ -35,7 +36,7 @@ class Message(StrAndUnicode):
return isinstance(other, Message) and self.level == other.level and \
self.message == other.message
- def __unicode__(self):
+ def __str__(self):
return force_text(self.message)
def _get_tags(self):
diff --git a/django/contrib/redirects/models.py b/django/contrib/redirects/models.py
index 4233d55793..a0376b5578 100644
--- a/django/contrib/redirects/models.py
+++ b/django/contrib/redirects/models.py
@@ -1,7 +1,9 @@
from django.db import models
from django.contrib.sites.models import Site
from django.utils.translation import ugettext_lazy as _
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class Redirect(models.Model):
site = models.ForeignKey(Site)
old_path = models.CharField(_('redirect from'), max_length=200, db_index=True,
@@ -15,6 +17,6 @@ class Redirect(models.Model):
db_table = 'django_redirect'
unique_together=(('site', 'old_path'),)
ordering = ('old_path',)
-
- def __unicode__(self):
+
+ def __str__(self):
return "%s ---> %s" % (self.old_path, self.new_path)
diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py
index 153cde9830..c8393f23c6 100644
--- a/django/contrib/sessions/backends/base.py
+++ b/django/contrib/sessions/backends/base.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
import base64
import time
from datetime import datetime, timedelta
@@ -12,6 +14,7 @@ from django.utils.crypto import constant_time_compare
from django.utils.crypto import get_random_string
from django.utils.crypto import salted_hmac
from django.utils import timezone
+from django.utils.encoding import force_bytes
class CreateError(Exception):
"""
@@ -78,15 +81,15 @@ class SessionBase(object):
"Returns the given session dictionary pickled and encoded as a string."
pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL)
hash = self._hash(pickled)
- return base64.encodestring(hash + ":" + pickled)
+ return base64.b64encode(hash.encode() + b":" + pickled).decode('ascii')
def decode(self, session_data):
- encoded_data = base64.decodestring(session_data)
+ encoded_data = base64.b64decode(force_bytes(session_data))
try:
# could produce ValueError if there is no ':'
- hash, pickled = encoded_data.split(':', 1)
+ hash, pickled = encoded_data.split(b':', 1)
expected_hash = self._hash(pickled)
- if not constant_time_compare(hash, expected_hash):
+ if not constant_time_compare(hash.decode(), expected_hash):
raise SuspiciousOperation("Session data corrupted")
else:
return pickle.loads(pickled)
diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py
index 0cc17b44c3..babdb72c27 100644
--- a/django/contrib/sessions/backends/db.py
+++ b/django/contrib/sessions/backends/db.py
@@ -1,7 +1,6 @@
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_text
from django.utils import timezone
@@ -18,7 +17,7 @@ class SessionStore(SessionBase):
session_key = self.session_key,
expire_date__gt=timezone.now()
)
- return self.decode(force_text(s.session_data))
+ return self.decode(s.session_data)
except (Session.DoesNotExist, SuspiciousOperation):
self.create()
return {}
diff --git a/django/contrib/sessions/backends/file.py b/django/contrib/sessions/backends/file.py
index 0f869088ac..20ac2c2087 100644
--- a/django/contrib/sessions/backends/file.py
+++ b/django/contrib/sessions/backends/file.py
@@ -115,7 +115,7 @@ class SessionStore(SessionBase):
renamed = False
try:
try:
- os.write(output_file_fd, self.encode(session_data))
+ os.write(output_file_fd, self.encode(session_data).encode())
finally:
os.close(output_file_fd)
os.rename(output_file_name, session_file_name)
diff --git a/django/contrib/sitemaps/tests/generic.py b/django/contrib/sitemaps/tests/generic.py
index e392cbf909..e0b0a827a6 100644
--- a/django/contrib/sitemaps/tests/generic.py
+++ b/django/contrib/sitemaps/tests/generic.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
from django.contrib.auth.models import User
from django.test.utils import override_settings
@@ -12,8 +14,9 @@ class GenericViewsSitemapTests(SitemapTestsBase):
expected = ''
for username in User.objects.values_list("username", flat=True):
expected += "<url><loc>%s/users/%s/</loc></url>" % (self.base_url, username)
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
%s
</urlset>
-""" % expected)
+""" % expected
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
diff --git a/django/contrib/sitemaps/tests/http.py b/django/contrib/sitemaps/tests/http.py
index d71907e09a..8da971876f 100644
--- a/django/contrib/sitemaps/tests/http.py
+++ b/django/contrib/sitemaps/tests/http.py
@@ -21,11 +21,12 @@ class HTTPSitemapTests(SitemapTestsBase):
def test_simple_sitemap_index(self):
"A simple sitemap index can be rendered"
response = self.client.get('/simple/index.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>%s/simple/sitemap-simple.xml</loc></sitemap>
</sitemapindex>
-""" % self.base_url)
+""" % self.base_url
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
@override_settings(
TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),)
@@ -33,30 +34,34 @@ class HTTPSitemapTests(SitemapTestsBase):
def test_simple_sitemap_custom_index(self):
"A simple sitemap index can be rendered with a custom template"
response = self.client.get('/simple/custom-index.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<!-- This is a customised template -->
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>%s/simple/sitemap-simple.xml</loc></sitemap>
</sitemapindex>
-""" % self.base_url)
+""" % self.base_url
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
+
def test_simple_sitemap_section(self):
"A simple sitemap section can be rendered"
response = self.client.get('/simple/sitemap-simple.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>%s/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
</urlset>
-""" % (self.base_url, date.today()))
+""" % (self.base_url, date.today())
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
def test_simple_sitemap(self):
"A simple sitemap can be rendered"
response = self.client.get('/simple/sitemap.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>%s/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
</urlset>
-""" % (self.base_url, date.today()))
+""" % (self.base_url, date.today())
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
@override_settings(
TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),)
@@ -64,12 +69,13 @@ class HTTPSitemapTests(SitemapTestsBase):
def test_simple_custom_sitemap(self):
"A simple sitemap can be rendered with a custom template"
response = self.client.get('/simple/custom-sitemap.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<!-- This is a customised template -->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>%s/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
</urlset>
-""" % (self.base_url, date.today()))
+""" % (self.base_url, date.today())
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
@skipUnless(settings.USE_I18N, "Internationalization is not enabled")
@override_settings(USE_L10N=True)
@@ -90,11 +96,12 @@ class HTTPSitemapTests(SitemapTestsBase):
# installed doesn't raise an exception
Site._meta.installed = False
response = self.client.get('/simple/sitemap.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>http://testserver/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
</urlset>
-""" % date.today())
+""" % date.today()
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
@skipUnless("django.contrib.sites" in settings.INSTALLED_APPS,
"django.contrib.sites app not installed.")
@@ -131,8 +138,9 @@ class HTTPSitemapTests(SitemapTestsBase):
Check that a cached sitemap index can be rendered (#2713).
"""
response = self.client.get('/cached/index.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>%s/cached/sitemap-simple.xml</loc></sitemap>
</sitemapindex>
-""" % self.base_url)
+""" % self.base_url
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
diff --git a/django/contrib/sitemaps/tests/https.py b/django/contrib/sitemaps/tests/https.py
index d4f9053fc8..26241eb30b 100644
--- a/django/contrib/sitemaps/tests/https.py
+++ b/django/contrib/sitemaps/tests/https.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
from datetime import date
from django.test.utils import override_settings
@@ -11,20 +13,22 @@ class HTTPSSitemapTests(SitemapTestsBase):
def test_secure_sitemap_index(self):
"A secure sitemap index can be rendered"
response = self.client.get('/secure/index.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>%s/secure/sitemap-simple.xml</loc></sitemap>
</sitemapindex>
-""" % self.base_url)
+""" % self.base_url
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
def test_secure_sitemap_section(self):
"A secure sitemap section can be rendered"
response = self.client.get('/secure/sitemap-simple.xml')
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>%s/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
</urlset>
-""" % (self.base_url, date.today()))
+""" % (self.base_url, date.today())
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
@override_settings(SECURE_PROXY_SSL_HEADER=False)
@@ -34,17 +38,19 @@ class HTTPSDetectionSitemapTests(SitemapTestsBase):
def test_sitemap_index_with_https_request(self):
"A sitemap index requested in HTTPS is rendered with HTTPS links"
response = self.client.get('/simple/index.xml', **self.extra)
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>%s/simple/sitemap-simple.xml</loc></sitemap>
</sitemapindex>
-""" % self.base_url.replace('http://', 'https://'))
+""" % self.base_url.replace('http://', 'https://')
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
def test_sitemap_section_with_https_request(self):
"A sitemap section requested in HTTPS is rendered with HTTPS links"
response = self.client.get('/simple/sitemap-simple.xml', **self.extra)
- self.assertEqual(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+ expected_content = """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>%s/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
</urlset>
-""" % (self.base_url.replace('http://', 'https://'), date.today()))
+""" % (self.base_url.replace('http://', 'https://'), date.today())
+ self.assertEqual(response.content, expected_content.encode('utf-8'))
diff --git a/django/contrib/sites/models.py b/django/contrib/sites/models.py
index fecbff79d8..8590740658 100644
--- a/django/contrib/sites/models.py
+++ b/django/contrib/sites/models.py
@@ -1,5 +1,6 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
+from django.utils.encoding import python_2_unicode_compatible
SITE_CACHE = {}
@@ -32,6 +33,7 @@ class SiteManager(models.Manager):
SITE_CACHE = {}
+@python_2_unicode_compatible
class Site(models.Model):
domain = models.CharField(_('domain name'), max_length=100)
@@ -44,7 +46,7 @@ class Site(models.Model):
verbose_name_plural = _('sites')
ordering = ('domain',)
- def __unicode__(self):
+ def __str__(self):
return self.domain
def save(self, *args, **kwargs):
@@ -62,6 +64,7 @@ class Site(models.Model):
pass
+@python_2_unicode_compatible
class RequestSite(object):
"""
A class that shares the primary interface of Site (i.e., it has
@@ -73,7 +76,7 @@ class RequestSite(object):
def __init__(self, request):
self.domain = self.name = request.get_host()
- def __unicode__(self):
+ def __str__(self):
return self.domain
def save(self, force_insert=False, force_update=False):
diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py
index 7dac0ffb4c..0e16e9d649 100644
--- a/django/contrib/staticfiles/management/commands/collectstatic.py
+++ b/django/contrib/staticfiles/management/commands/collectstatic.py
@@ -192,7 +192,7 @@ Type 'yes' to continue, or 'no' to cancel: """
def clear_dir(self, path):
"""
- Deletes the given relative path using the destinatin storage backend.
+ Deletes the given relative path using the destination storage backend.
"""
dirs, files = self.storage.listdir(path)
for f in files:
diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py
index 2ca54dde71..9691b7849d 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_text, smart_bytes
+from django.utils.encoding import force_bytes, force_text
from django.utils.functional import LazyObject
from django.utils.importlib import import_module
@@ -51,8 +51,8 @@ class CachedFilesMixin(object):
default_template = """url("%s")"""
patterns = (
("*.css", (
- br"""(url\(['"]{0,1}\s*(.*?)["']{0,1}\))""",
- (br"""(@import\s*["']\s*(.*?)["'])""", """@import url("%s")"""),
+ r"""(url\(['"]{0,1}\s*(.*?)["']{0,1}\))""",
+ (r"""(@import\s*["']\s*(.*?)["'])""", """@import url("%s")"""),
)),
)
@@ -87,6 +87,7 @@ class CachedFilesMixin(object):
def hashed_name(self, name, content=None):
parsed_name = urlsplit(unquote(name))
clean_name = parsed_name.path.strip()
+ opened = False
if content is None:
if not self.exists(clean_name):
raise ValueError("The file '%s' could not be found with %r." %
@@ -96,9 +97,14 @@ class CachedFilesMixin(object):
except IOError:
# Handle directory paths and fragments
return name
+ opened = True
+ try:
+ file_hash = self.file_hash(clean_name, content)
+ finally:
+ if opened:
+ content.close()
path, filename = os.path.split(clean_name)
root, ext = os.path.splitext(filename)
- file_hash = self.file_hash(clean_name, content)
if file_hash is not None:
file_hash = ".%s" % file_hash
hashed_name = os.path.join(path, "%s%s%s" %
@@ -112,7 +118,7 @@ class CachedFilesMixin(object):
return urlunsplit(unparsed_name)
def cache_key(self, name):
- return 'staticfiles:%s' % hashlib.md5(smart_bytes(name)).hexdigest()
+ return 'staticfiles:%s' % hashlib.md5(force_bytes(name)).hexdigest()
def url(self, name, force=False):
"""
@@ -248,7 +254,7 @@ class CachedFilesMixin(object):
if hashed_file_exists:
self.delete(hashed_name)
# then save the processed result
- content_file = ContentFile(smart_bytes(content))
+ content_file = ContentFile(force_bytes(content))
saved_name = self._save(hashed_name, content_file)
hashed_name = force_text(saved_name.replace('\\', '/'))
processed = True
@@ -261,7 +267,7 @@ class CachedFilesMixin(object):
hashed_name = force_text(saved_name.replace('\\', '/'))
# and then set the cache accordingly
- hashed_paths[self.cache_key(name)] = hashed_name
+ hashed_paths[self.cache_key(name.replace('\\', '/'))] = hashed_name
yield name, hashed_name, processed
# Finally set the cache
diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py
index bce7ef7cfb..4815ce5567 100644
--- a/django/contrib/syndication/views.py
+++ b/django/contrib/syndication/views.py
@@ -106,7 +106,7 @@ class Feed(object):
subtitle = self.__get_dynamic_attr('subtitle', obj),
link = link,
description = self.__get_dynamic_attr('description', obj),
- language = settings.LANGUAGE_CODE.decode(),
+ language = settings.LANGUAGE_CODE,
feed_url = add_domain(
current_site.domain,
self.__get_dynamic_attr('feed_url', obj) or request.path,
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index d527e44d8b..06e8952bfb 100644
--- a/django/core/cache/backends/base.py
+++ b/django/core/cache/backends/base.py
@@ -1,9 +1,9 @@
"Base Cache class."
+from __future__ import unicode_literals
import warnings
from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
-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_bytes(key)])
+ return ':'.join([key_prefix, str(version), 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_bytes(params.get('KEY_PREFIX', ''))
+ self.key_prefix = 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/cache/backends/db.py b/django/core/cache/backends/db.py
index f60b4e0cd1..348b03f733 100644
--- a/django/core/cache/backends/db.py
+++ b/django/core/cache/backends/db.py
@@ -12,6 +12,7 @@ from django.conf import settings
from django.core.cache.backends.base import BaseCache
from django.db import connections, router, transaction, DatabaseError
from django.utils import timezone
+from django.utils.encoding import force_bytes
class Options(object):
@@ -72,7 +73,7 @@ class DatabaseCache(BaseDatabaseCache):
transaction.commit_unless_managed(using=db)
return default
value = connections[db].ops.process_clob(row[1])
- return pickle.loads(base64.decodestring(value))
+ return pickle.loads(base64.b64decode(force_bytes(value)))
def set(self, key, value, timeout=None, version=None):
key = self.make_key(key, version=version)
@@ -103,7 +104,7 @@ class DatabaseCache(BaseDatabaseCache):
if num > self._max_entries:
self._cull(db, cursor, now)
pickled = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
- encoded = base64.encodestring(pickled).strip()
+ encoded = base64.b64encode(pickled).strip()
cursor.execute("SELECT cache_key, expires FROM %s "
"WHERE cache_key = %%s" % table, [key])
try:
@@ -166,7 +167,7 @@ class DatabaseCache(BaseDatabaseCache):
cursor.execute("SELECT COUNT(*) FROM %s" % table)
num = cursor.fetchone()[0]
if num > self._max_entries:
- cull_num = num / self._cull_frequency
+ cull_num = num // self._cull_frequency
cursor.execute(
connections[db].ops.cache_key_culling_sql() % table,
[cull_num])
diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py
index 1170996a76..96194d458f 100644
--- a/django/core/cache/backends/filebased.py
+++ b/django/core/cache/backends/filebased.py
@@ -10,6 +10,7 @@ except ImportError:
import pickle
from django.core.cache.backends.base import BaseCache
+from django.utils.encoding import force_bytes
class FileBasedCache(BaseCache):
def __init__(self, dir, params):
@@ -136,7 +137,7 @@ class FileBasedCache(BaseCache):
Thus, a cache key of "foo" gets turnned into a file named
``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``.
"""
- path = hashlib.md5(key).hexdigest()
+ path = hashlib.md5(force_bytes(key)).hexdigest()
path = os.path.join(path[:2], path[2:4], path[4:])
return os.path.join(self._dir, path)
diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py
index e7724f1b91..426a0a15c0 100644
--- a/django/core/cache/backends/memcached.py
+++ b/django/core/cache/backends/memcached.py
@@ -6,6 +6,7 @@ from threading import local
from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
from django.utils import six
+from django.utils.encoding import force_str
class BaseMemcachedCache(BaseCache):
def __init__(self, server, params, library, value_not_found_exception):
@@ -50,6 +51,10 @@ class BaseMemcachedCache(BaseCache):
timeout += int(time.time())
return int(timeout)
+ def make_key(self, key, version=None):
+ # Python 2 memcache requires the key to be a byte string.
+ return force_str(super(BaseMemcachedCache, self).make_key(key, version))
+
def add(self, key, value, timeout=0, version=None):
key = self.make_key(key, version=version)
return self._cache.add(key, value, self._get_memcache_timeout(timeout))
diff --git a/django/core/context_processors.py b/django/core/context_processors.py
index a503270cf4..ca1ac68f55 100644
--- a/django/core/context_processors.py
+++ b/django/core/context_processors.py
@@ -6,12 +6,15 @@ and returns a dictionary to add to the context.
These are referenced from the setting TEMPLATE_CONTEXT_PROCESSORS and used by
RequestContext.
"""
+from __future__ import unicode_literals
from django.conf import settings
from django.middleware.csrf import get_token
-from django.utils.encoding import smart_bytes
+from django.utils import six
+from django.utils.encoding import smart_text
from django.utils.functional import lazy
+
def csrf(request):
"""
Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if
@@ -23,10 +26,10 @@ def csrf(request):
# In order to be able to provide debugging info in the
# case of misconfiguration, we use a sentinel value
# instead of returning an empty dict.
- return b'NOTPROVIDED'
+ return 'NOTPROVIDED'
else:
- return smart_bytes(token)
- _get_val = lazy(_get_val, str)
+ return smart_text(token)
+ _get_val = lazy(_get_val, six.text_type)
return {'csrf_token': _get_val() }
diff --git a/django/core/files/base.py b/django/core/files/base.py
index d0b25250a5..b81e180292 100644
--- a/django/core/files/base.py
+++ b/django/core/files/base.py
@@ -1,11 +1,14 @@
from __future__ import unicode_literals
import os
-from io import BytesIO
+from io import BytesIO, StringIO, UnsupportedOperation
-from django.utils.encoding import smart_bytes, smart_text
+from django.utils.encoding import smart_text
from django.core.files.utils import FileProxyMixin
+from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class File(FileProxyMixin):
DEFAULT_CHUNK_SIZE = 64 * 2**10
@@ -18,9 +21,6 @@ class File(FileProxyMixin):
self.mode = file.mode
def __str__(self):
- return smart_bytes(self.name or '')
-
- def __unicode__(self):
return smart_text(self.name or '')
def __repr__(self):
@@ -65,8 +65,10 @@ class File(FileProxyMixin):
if not chunk_size:
chunk_size = self.DEFAULT_CHUNK_SIZE
- if hasattr(self, 'seek'):
+ try:
self.seek(0)
+ except (AttributeError, UnsupportedOperation):
+ pass
while True:
data = self.read(chunk_size)
@@ -124,13 +126,15 @@ class File(FileProxyMixin):
def close(self):
self.file.close()
+@python_2_unicode_compatible
class ContentFile(File):
"""
A File-like object that takes just raw content, rather than an actual file.
"""
def __init__(self, content, name=None):
content = content or b''
- super(ContentFile, self).__init__(BytesIO(content), name=name)
+ stream_class = StringIO if isinstance(content, six.text_type) else BytesIO
+ super(ContentFile, self).__init__(stream_class(content), name=name)
self.size = len(content)
def __str__(self):
diff --git a/django/core/files/move.py b/django/core/files/move.py
index f9060fd3d8..3af02634fe 100644
--- a/django/core/files/move.py
+++ b/django/core/files/move.py
@@ -66,7 +66,7 @@ def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_ove
try:
locks.lock(fd, locks.LOCK_EX)
current_chunk = None
- while current_chunk != '':
+ while current_chunk != b'':
current_chunk = old_file.read(chunk_size)
os.write(fd, current_chunk)
finally:
diff --git a/django/core/files/storage.py b/django/core/files/storage.py
index 7542dcda46..0b300cd31e 100644
--- a/django/core/files/storage.py
+++ b/django/core/files/storage.py
@@ -195,11 +195,18 @@ class FileSystemStorage(Storage):
fd = os.open(full_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, 'O_BINARY', 0))
try:
locks.lock(fd, locks.LOCK_EX)
+ _file = None
for chunk in content.chunks():
- os.write(fd, chunk)
+ if _file is None:
+ mode = 'wb' if isinstance(chunk, bytes) else 'wt'
+ _file = os.fdopen(fd, mode)
+ _file.write(chunk)
finally:
locks.unlock(fd)
- os.close(fd)
+ if _file is not None:
+ _file.close()
+ else:
+ os.close(fd)
except OSError as e:
if e.errno == errno.EEXIST:
# Ooops, the file exists. We need a new file name.
diff --git a/django/core/files/uploadedfile.py b/django/core/files/uploadedfile.py
index 3a6c632975..39b99ff78f 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_bytes
+from django.utils.encoding import force_str
__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile',
'SimpleUploadedFile')
@@ -30,7 +30,7 @@ class UploadedFile(File):
self.charset = charset
def __repr__(self):
- return smart_bytes("<%s: %s (%s)>" % (
+ return force_str("<%s: %s (%s)>" % (
self.__class__.__name__, self.name, self.content_type))
def _get_name(self):
diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py
index 68d540e595..c422945d6f 100644
--- a/django/core/files/uploadhandler.py
+++ b/django/core/files/uploadhandler.py
@@ -10,6 +10,7 @@ from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile
from django.utils import importlib
+from django.utils.encoding import python_2_unicode_compatible
__all__ = ['UploadFileException','StopUpload', 'SkipFile', 'FileUploadHandler',
'TemporaryFileUploadHandler', 'MemoryFileUploadHandler',
@@ -21,6 +22,7 @@ class UploadFileException(Exception):
"""
pass
+@python_2_unicode_compatible
class StopUpload(UploadFileException):
"""
This exception is raised when an upload must abort.
@@ -33,7 +35,7 @@ class StopUpload(UploadFileException):
"""
self.connection_reset = connection_reset
- def __unicode__(self):
+ def __str__(self):
if self.connection_reset:
return 'StopUpload: Halt current upload.'
else:
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index 5a6825f0a7..791382bac0 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
import sys
+import types
from django import http
from django.core import signals
@@ -125,10 +126,10 @@ class BaseHandler(object):
# Complain if the view returned None (a common error).
if response is None:
- try:
- view_name = callback.func_name # If it's a function
- except AttributeError:
- view_name = callback.__class__.__name__ + '.__call__' # If it's a class
+ if isinstance(callback, types.FunctionType): # FBV
+ view_name = callback.__name__
+ else: # CBV
+ view_name = callback.__class__.__name__ + '.__call__'
raise ValueError("The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name))
# If the response supports deferred rendering, apply template
@@ -152,10 +153,8 @@ class BaseHandler(object):
callback, param_dict = resolver.resolve404()
response = callback(request, **param_dict)
except:
- try:
- response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
- finally:
- signals.got_request_exception.send(sender=self.__class__, request=request)
+ signals.got_request_exception.send(sender=self.__class__, request=request)
+ response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
except exceptions.PermissionDenied:
logger.warning(
'Forbidden (Permission denied): %s', request.path,
@@ -167,12 +166,10 @@ class BaseHandler(object):
callback, param_dict = resolver.resolve403()
response = callback(request, **param_dict)
except:
- try:
- response = self.handle_uncaught_exception(request,
- resolver, sys.exc_info())
- finally:
- signals.got_request_exception.send(
+ signals.got_request_exception.send(
sender=self.__class__, request=request)
+ response = self.handle_uncaught_exception(request,
+ resolver, sys.exc_info())
except SystemExit:
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
raise
@@ -225,7 +222,7 @@ class BaseHandler(object):
# If Http500 handler is not installed, re-raise last exception
if resolver.urlconf_module is None:
- six.reraise(exc_info[1], None, exc_info[2])
+ six.reraise(*exc_info)
# Return an HttpResponse that displays a friendly error message.
callback, param_dict = resolver.resolve500()
return callback(request, **param_dict)
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
index 70b23f8515..e445d07a61 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_text, smart_bytes, iri_to_uri
+from django.utils.encoding import force_str, force_text, iri_to_uri
from django.utils.log import getLogger
logger = getLogger('django.request')
@@ -246,5 +246,5 @@ class WSGIHandler(base.BaseHandler):
response_headers = [(str(k), str(v)) for k, v in response.items()]
for c in response.cookies.values():
response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
- start_response(smart_bytes(status), response_headers)
+ start_response(force_str(status), response_headers)
return response
diff --git a/django/core/mail/message.py b/django/core/mail/message.py
index b332ffba04..db9023a0bb 100644
--- a/django/core/mail/message.py
+++ b/django/core/mail/message.py
@@ -99,7 +99,12 @@ def sanitize_address(addr, encoding):
if isinstance(addr, six.string_types):
addr = parseaddr(force_text(addr))
nm, addr = addr
- nm = Header(nm, encoding).encode()
+ # This try-except clause is needed on Python 3 < 3.2.4
+ # http://bugs.python.org/issue14291
+ try:
+ nm = Header(nm, encoding).encode()
+ except UnicodeEncodeError:
+ nm = Header(nm, 'utf-8').encode()
try:
addr.encode('ascii')
except UnicodeEncodeError: # IDN
diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
index 268cd63c38..98f75e0310 100644
--- a/django/core/management/__init__.py
+++ b/django/core/management/__init__.py
@@ -51,14 +51,19 @@ def find_management_module(app_name):
# module, we need look for the case where the project name is part
# of the app_name but the project directory itself isn't on the path.
try:
- f, path, descr = imp.find_module(part,path)
+ f, path, descr = imp.find_module(part, path)
except ImportError as e:
if os.path.basename(os.getcwd()) != part:
raise e
+ else:
+ if f:
+ f.close()
while parts:
part = parts.pop()
f, path, descr = imp.find_module(part, path and [path] or None)
+ if f:
+ f.close()
return path
def load_command_class(app_name, name):
diff --git a/django/core/management/base.py b/django/core/management/base.py
index 5e630d5207..895753ea12 100644
--- a/django/core/management/base.py
+++ b/django/core/management/base.py
@@ -12,7 +12,7 @@ import traceback
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.encoding import force_str
from django.utils.six import StringIO
@@ -65,7 +65,7 @@ class OutputWrapper(object):
msg += ending
style_func = [f for f in (style_func, self.style_func, lambda x:x)
if f is not None][0]
- self._out.write(smart_str(style_func(msg)))
+ self._out.write(force_str(style_func(msg)))
class BaseCommand(object):
diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py
index 7c868e4b60..936d5e89ab 100644
--- a/django/core/management/commands/inspectdb.py
+++ b/django/core/management/commands/inspectdb.py
@@ -1,4 +1,7 @@
+from __future__ import unicode_literals
+
import keyword
+import re
from optparse import make_option
from django.core.management.base import NoArgsCommand, CommandError
@@ -31,6 +34,7 @@ class Command(NoArgsCommand):
table_name_filter = options.get('table_name_filter')
table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '')
+ strip_prefix = lambda s: s.startswith("u'") and s[1:] or s
cursor = connection.cursor()
yield "# This is an auto-generated Django model module."
@@ -41,6 +45,7 @@ class Command(NoArgsCommand):
yield "#"
yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'"
yield "# into your database."
+ yield "from __future__ import unicode_literals"
yield ''
yield 'from %s import models' % self.db_module
yield ''
@@ -59,16 +64,19 @@ class Command(NoArgsCommand):
indexes = connection.introspection.get_indexes(cursor, table_name)
except NotImplementedError:
indexes = {}
+ used_column_names = [] # Holds column names used in the table so far
for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)):
- column_name = row[0]
- att_name = column_name.lower()
comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
extra_params = {} # Holds Field parameters such as 'db_column'.
+ column_name = row[0]
+ is_relation = i in relations
+
+ att_name, params, notes = self.normalize_col_name(
+ column_name, used_column_names, is_relation)
+ extra_params.update(params)
+ comment_notes.extend(notes)
- # If the column name can't be used verbatim as a Python
- # attribute, set the "db_column" for this Field.
- if ' ' in att_name or '-' in att_name or keyword.iskeyword(att_name) or column_name != att_name:
- extra_params['db_column'] = column_name
+ used_column_names.append(att_name)
# Add primary_key and unique, if necessary.
if column_name in indexes:
@@ -77,30 +85,12 @@ class Command(NoArgsCommand):
elif indexes[column_name]['unique']:
extra_params['unique'] = True
- # Modify the field name to make it Python-compatible.
- if ' ' in att_name:
- att_name = att_name.replace(' ', '_')
- comment_notes.append('Field renamed to remove spaces.')
-
- if '-' in att_name:
- att_name = att_name.replace('-', '_')
- comment_notes.append('Field renamed to remove dashes.')
-
- if column_name != att_name:
- comment_notes.append('Field name made lowercase.')
-
- if i in relations:
+ if is_relation:
rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
-
if rel_to in known_models:
field_type = 'ForeignKey(%s' % rel_to
else:
field_type = "ForeignKey('%s'" % rel_to
-
- if att_name.endswith('_id'):
- att_name = att_name[:-3]
- else:
- extra_params['db_column'] = column_name
else:
# Calling `get_field_type` to get the field type string and any
# additional paramters and notes.
@@ -110,16 +100,6 @@ class Command(NoArgsCommand):
field_type += '('
- if keyword.iskeyword(att_name):
- att_name += '_field'
- comment_notes.append('Field renamed because it was a Python reserved word.')
-
- if att_name[0].isdigit():
- att_name = 'number_%s' % att_name
- extra_params['db_column'] = six.text_type(column_name)
- comment_notes.append("Field renamed because it wasn't a "
- "valid Python identifier.")
-
# Don't output 'id = meta.AutoField(primary_key=True)', because
# that's assumed if it doesn't exist.
if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}:
@@ -136,7 +116,9 @@ class Command(NoArgsCommand):
if extra_params:
if not field_desc.endswith('('):
field_desc += ', '
- field_desc += ', '.join(['%s=%r' % (k, v) for k, v in extra_params.items()])
+ field_desc += ', '.join([
+ '%s=%s' % (k, strip_prefix(repr(v)))
+ for k, v in extra_params.items()])
field_desc += ')'
if comment_notes:
field_desc += ' # ' + ' '.join(comment_notes)
@@ -144,6 +126,62 @@ class Command(NoArgsCommand):
for meta_line in self.get_meta(table_name):
yield meta_line
+ def normalize_col_name(self, col_name, used_column_names, is_relation):
+ """
+ Modify the column name to make it Python-compatible as a field name
+ """
+ field_params = {}
+ field_notes = []
+
+ new_name = col_name.lower()
+ if new_name != col_name:
+ field_notes.append('Field name made lowercase.')
+
+ if is_relation:
+ if new_name.endswith('_id'):
+ new_name = new_name[:-3]
+ else:
+ field_params['db_column'] = col_name
+
+ new_name, num_repl = re.subn(r'\W', '_', new_name)
+ if num_repl > 0:
+ field_notes.append('Field renamed to remove unsuitable characters.')
+
+ if new_name.find('__') >= 0:
+ while new_name.find('__') >= 0:
+ new_name = new_name.replace('__', '_')
+ if col_name.lower().find('__') >= 0:
+ # Only add the comment if the double underscore was in the original name
+ field_notes.append("Field renamed because it contained more than one '_' in a row.")
+
+ if new_name.startswith('_'):
+ new_name = 'field%s' % new_name
+ field_notes.append("Field renamed because it started with '_'.")
+
+ if new_name.endswith('_'):
+ new_name = '%sfield' % new_name
+ field_notes.append("Field renamed because it ended with '_'.")
+
+ if keyword.iskeyword(new_name):
+ new_name += '_field'
+ field_notes.append('Field renamed because it was a Python reserved word.')
+
+ if new_name[0].isdigit():
+ new_name = 'number_%s' % new_name
+ field_notes.append("Field renamed because it wasn't a valid Python identifier.")
+
+ if new_name in used_column_names:
+ num = 0
+ while '%s_%d' % (new_name, num) in used_column_names:
+ num += 1
+ new_name = '%s_%d' % (new_name, num)
+ field_notes.append('Field renamed because of name conflict.')
+
+ if col_name != new_name and field_notes:
+ field_params['db_column'] = col_name
+
+ return new_name, field_params, field_notes
+
def get_field_type(self, connection, table_name, row):
"""
Given the database connection, the table name, and the cursor row
@@ -181,6 +219,6 @@ class Command(NoArgsCommand):
to construct the inner Meta class for the model corresponding
to the given database table name.
"""
- return [' class Meta:',
- ' db_table = %r' % table_name,
- '']
+ return [" class Meta:",
+ " db_table = '%s'" % table_name,
+ ""]
diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py
index 1896e53cee..30cf740cdf 100644
--- a/django/core/management/commands/loaddata.py
+++ b/django/core/management/commands/loaddata.py
@@ -196,6 +196,10 @@ class Command(BaseCommand):
loaded_object_count += loaded_objects_in_fixture
fixture_object_count += objects_in_fixture
label_found = True
+ except Exception as e:
+ if not isinstance(e, CommandError):
+ e.args = ("Problem installing fixture '%s': %s" % (full_path, e),)
+ raise
finally:
fixture.close()
@@ -209,7 +213,11 @@ class Command(BaseCommand):
# Since we disabled constraint checks, we must manually check for
# any invalid keys that might have been added
table_names = [model._meta.db_table for model in models]
- connection.check_constraints(table_names=table_names)
+ try:
+ connection.check_constraints(table_names=table_names)
+ except Exception as e:
+ e.args = ("Problem installing fixtures: %s" % e,)
+ raise
except (SystemExit, KeyboardInterrupt):
raise
@@ -217,8 +225,6 @@ class Command(BaseCommand):
if commit:
transaction.rollback(using=using)
transaction.leave_transaction_management(using=using)
- if not isinstance(e, CommandError):
- e.args = ("Problem installing fixture '%s': %s" % (full_path, e),)
raise
# If we found even one object in a fixture, we need to reset the
diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py
index d8e8f6cff7..81c4fdf8cc 100644
--- a/django/core/management/commands/makemessages.py
+++ b/django/core/management/commands/makemessages.py
@@ -47,32 +47,27 @@ def _popen(cmd):
output, errors = p.communicate()
return output, errors, p.returncode
-def walk(root, topdown=True, onerror=None, followlinks=False,
- ignore_patterns=None, verbosity=0, stdout=sys.stdout):
+def find_files(root, ignore_patterns, verbosity, stdout=sys.stdout, symlinks=False):
"""
- A version of os.walk that can follow symlinks for Python < 2.6
+ Helper function to get all files in the given root.
"""
- if ignore_patterns is None:
- ignore_patterns = []
dir_suffix = '%s*' % os.sep
- norm_patterns = map(lambda p: p.endswith(dir_suffix)
- and p[:-len(dir_suffix)] or p, ignore_patterns)
- for dirpath, dirnames, filenames in os.walk(root, topdown, onerror):
- remove_dirs = []
- for dirname in dirnames:
+ norm_patterns = [p[:-len(dir_suffix)] if p.endswith(dir_suffix) else p for p in ignore_patterns]
+ all_files = []
+ for dirpath, dirnames, filenames in os.walk(root, topdown=True, followlinks=symlinks):
+ for dirname in dirnames[:]:
if is_ignored(os.path.normpath(os.path.join(dirpath, dirname)), norm_patterns):
- remove_dirs.append(dirname)
- for dirname in remove_dirs:
- dirnames.remove(dirname)
- if verbosity > 1:
- stdout.write('ignoring directory %s\n' % dirname)
- yield (dirpath, dirnames, filenames)
- if followlinks:
- for d in dirnames:
- p = os.path.join(dirpath, d)
- if os.path.islink(p):
- for link_dirpath, link_dirnames, link_filenames in walk(p):
- yield (link_dirpath, link_dirnames, link_filenames)
+ dirnames.remove(dirname)
+ if verbosity > 1:
+ stdout.write('ignoring directory %s\n' % dirname)
+ for filename in filenames:
+ if is_ignored(os.path.normpath(os.path.join(dirpath, filename)), ignore_patterns):
+ if verbosity > 1:
+ stdout.write('ignoring file %s in %s\n' % (filename, dirpath))
+ else:
+ all_files.extend([(dirpath, filename)])
+ all_files.sort()
+ return all_files
def is_ignored(path, ignore_patterns):
"""
@@ -83,23 +78,6 @@ def is_ignored(path, ignore_patterns):
return True
return False
-def find_files(root, ignore_patterns, verbosity, stdout=sys.stdout, symlinks=False):
- """
- Helper function to get all files in the given root.
- """
- all_files = []
- for (dirpath, dirnames, filenames) in walk(root, followlinks=symlinks,
- ignore_patterns=ignore_patterns, verbosity=verbosity, stdout=stdout):
- for filename in filenames:
- norm_filepath = os.path.normpath(os.path.join(dirpath, filename))
- if is_ignored(norm_filepath, ignore_patterns):
- if verbosity > 1:
- stdout.write('ignoring file %s in %s\n' % (filename, dirpath))
- else:
- all_files.extend([(dirpath, filename)])
- all_files.sort()
- return all_files
-
def copy_plural_forms(msgs, locale, domain, verbosity, stdout=sys.stdout):
"""
Copies plural forms header contents from a Django catalog of locale to
@@ -144,7 +122,7 @@ def write_pot_file(potfile, msgs, file, work_file, is_templatized):
msgs = '\n'.join(dropwhile(len, msgs.split('\n')))
else:
msgs = msgs.replace('charset=CHARSET', 'charset=UTF-8')
- with open(potfile, 'ab') as fp:
+ with open(potfile, 'a') as fp:
fp.write(msgs)
def process_file(file, dirpath, potfile, domain, verbosity,
@@ -252,7 +230,7 @@ def write_po_file(pofile, potfile, domain, locale, verbosity, stdout,
msgs = copy_plural_forms(msgs, locale, domain, verbosity, stdout)
msgs = msgs.replace(
"#. #-#-#-#-# %s.pot (PACKAGE VERSION) #-#-#-#-#\n" % domain, "")
- with open(pofile, 'wb') as fp:
+ with open(pofile, 'w') as fp:
fp.write(msgs)
os.unlink(potfile)
if no_obsolete:
diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py
index 4e7d1dbbf4..52a8cab7e4 100644
--- a/django/core/management/commands/shell.py
+++ b/django/core/management/commands/shell.py
@@ -80,14 +80,14 @@ class Command(NoArgsCommand):
readline.parse_and_bind("tab:complete")
# We want to honor both $PYTHONSTARTUP and .pythonrc.py, so follow system
- # conventions and get $PYTHONSTARTUP first then import user.
+ # conventions and get $PYTHONSTARTUP first then .pythonrc.py.
if not use_plain:
- pythonrc = os.environ.get("PYTHONSTARTUP")
- if pythonrc and os.path.isfile(pythonrc):
- try:
- execfile(pythonrc)
- except NameError:
- pass
- # This will import .pythonrc.py as a side-effect
- import user
+ for pythonrc in (os.environ.get("PYTHONSTARTUP"),
+ os.path.expanduser('~/.pythonrc.py')):
+ if pythonrc and os.path.isfile(pythonrc):
+ try:
+ with open(pythonrc) as handle:
+ exec(compile(handle.read(), pythonrc, 'exec'))
+ except NameError:
+ pass
code.interact(local=imported_objects)
diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py
index cf26e754ae..cceec07be8 100644
--- a/django/core/management/commands/syncdb.py
+++ b/django/core/management/commands/syncdb.py
@@ -75,7 +75,7 @@ class Command(NoArgsCommand):
(opts.auto_created and converter(opts.auto_created._meta.db_table) in tables))
manifest = SortedDict(
- (app_name, filter(model_installed, model_list))
+ (app_name, list(filter(model_installed, model_list)))
for app_name, model_list in all_models
)
diff --git a/django/core/management/sql.py b/django/core/management/sql.py
index 7579cbe8ab..b02a548314 100644
--- a/django/core/management/sql.py
+++ b/django/core/management/sql.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
+import codecs
import os
import re
@@ -168,10 +169,10 @@ def custom_sql_for_model(model, style, connection):
os.path.join(app_dir, "%s.sql" % opts.object_name.lower())]
for sql_file in sql_files:
if os.path.exists(sql_file):
- with open(sql_file, 'U') as fp:
+ with codecs.open(sql_file, 'U', encoding=settings.FILE_CHARSET) as fp:
# Some backends can't execute more than one SQL statement at a time,
# so split into separate statements.
- output.extend(_split_statements(fp.read().decode(settings.FILE_CHARSET)))
+ output.extend(_split_statements(fp.read()))
return output
diff --git a/django/core/management/templates.py b/django/core/management/templates.py
index 52d0e5c89d..aa65593e9c 100644
--- a/django/core/management/templates.py
+++ b/django/core/management/templates.py
@@ -8,6 +8,8 @@ import shutil
import stat
import sys
import tempfile
+import codecs
+
try:
from urllib.request import urlretrieve
except ImportError: # Python 2
@@ -154,12 +156,12 @@ class TemplateCommand(BaseCommand):
# Only render the Python files, as we don't want to
# accidentally render Django templates files
- with open(old_path, 'r') as template_file:
+ with codecs.open(old_path, 'r', 'utf-8') as template_file:
content = template_file.read()
if filename.endswith(extensions) or filename in extra_files:
template = Template(content)
content = template.render(context)
- with open(new_path, 'w') as new_file:
+ with codecs.open(new_path, 'w', 'utf-8') as new_file:
new_file.write(content)
if self.verbosity >= 2:
diff --git a/django/core/management/validation.py b/django/core/management/validation.py
index 274f98ee79..6cd66f3a6a 100644
--- a/django/core/management/validation.py
+++ b/django/core/management/validation.py
@@ -1,7 +1,7 @@
import sys
from django.core.management.color import color_style
-from django.utils.encoding import smart_str
+from django.utils.encoding import force_str
from django.utils.itercompat import is_iterable
from django.utils import six
@@ -13,7 +13,7 @@ class ModelErrorCollection:
def add(self, context, error):
self.errors.append((context, error))
- self.outfile.write(self.style.ERROR(smart_str("%s: %s\n" % (context, error))))
+ self.outfile.write(self.style.ERROR(force_str("%s: %s\n" % (context, error))))
def get_validation_errors(outfile, app=None):
"""
diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py
index bdb43db9f3..276f9a4738 100644
--- a/django/core/serializers/base.py
+++ b/django/core/serializers/base.py
@@ -2,8 +2,6 @@
Module for abstract serializer/unserializer base classes.
"""
-from io import BytesIO
-
from django.db import models
from django.utils.encoding import smart_text
from django.utils import six
@@ -35,7 +33,7 @@ class Serializer(object):
"""
self.options = options
- self.stream = options.pop("stream", BytesIO())
+ self.stream = options.pop("stream", six.StringIO())
self.selected_fields = options.pop("fields", None)
self.use_natural_keys = options.pop("use_natural_keys", False)
@@ -125,7 +123,7 @@ class Deserializer(object):
"""
self.options = options
if isinstance(stream_or_string, six.string_types):
- self.stream = BytesIO(stream_or_string)
+ self.stream = six.StringIO(stream_or_string)
else:
self.stream = stream_or_string
# hack to make sure that the models have all been loaded before
diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py
index 3bac24d33a..ed65f2922c 100644
--- a/django/core/serializers/json.py
+++ b/django/core/serializers/json.py
@@ -12,7 +12,6 @@ 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_bytes
from django.utils import six
from django.utils.timezone import is_aware
@@ -61,13 +60,12 @@ def Deserializer(stream_or_string, **options):
"""
Deserialize a stream or string of JSON data.
"""
+ if not isinstance(stream_or_string, (bytes, six.string_types)):
+ stream_or_string = stream_or_string.read()
if isinstance(stream_or_string, bytes):
stream_or_string = stream_or_string.decode('utf-8')
try:
- if isinstance(stream_or_string, six.string_types):
- objects = json.loads(stream_or_string)
- else:
- objects = json.load(stream_or_string)
+ objects = json.loads(stream_or_string)
for obj in PythonDeserializer(objects, **options):
yield obj
except GeneratorExit:
diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py
index 9be1ea4492..4c11626bad 100644
--- a/django/core/serializers/pyyaml.py
+++ b/django/core/serializers/pyyaml.py
@@ -12,7 +12,6 @@ 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_bytes
from django.utils import six
diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
index 2db4e5ef6a..19b287af87 100644
--- a/django/core/servers/basehttp.py
+++ b/django/core/servers/basehttp.py
@@ -7,6 +7,8 @@ This is a simple server for use in testing or debugging Django apps. It hasn't
been reviewed for security issues. DON'T USE IT FOR PRODUCTION USE!
"""
+from __future__ import unicode_literals
+
import os
import socket
import sys
@@ -71,12 +73,12 @@ class WSGIServerException(Exception):
class ServerHandler(simple_server.ServerHandler, object):
- error_status = "500 INTERNAL SERVER ERROR"
+ error_status = str("500 INTERNAL SERVER ERROR")
def write(self, data):
- """'write()' callable as specified by PEP 333"""
+ """'write()' callable as specified by PEP 3333"""
- assert isinstance(data, str), "write() argument must be string"
+ assert isinstance(data, bytes), "write() argument must be bytestring"
if not self.status:
raise AssertionError("write() before start_response()")
@@ -200,7 +202,7 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):
def run(addr, port, wsgi_handler, ipv6=False, threading=False):
server_address = (addr, port)
if threading:
- httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, WSGIServer), {})
+ httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {})
else:
httpd_cls = WSGIServer
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
diff --git a/django/core/signing.py b/django/core/signing.py
index 9ab8c5b8b0..147e54780c 100644
--- a/django/core/signing.py
+++ b/django/core/signing.py
@@ -32,6 +32,9 @@ start of the base64 JSON.
There are 65 url-safe characters: the 64 used by url-safe base64 and the ':'.
These functions make use of all of them.
"""
+
+from __future__ import unicode_literals
+
import base64
import json
import time
@@ -41,7 +44,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_text, smart_bytes
+from django.utils.encoding import force_bytes, force_str, force_text
from django.utils.importlib import import_module
@@ -60,11 +63,11 @@ class SignatureExpired(BadSignature):
def b64_encode(s):
- return base64.urlsafe_b64encode(s).strip('=')
+ return base64.urlsafe_b64encode(s).strip(b'=')
def b64_decode(s):
- pad = '=' * (-len(s) % 4)
+ pad = b'=' * (-len(s) % 4)
return base64.urlsafe_b64decode(s + pad)
@@ -114,7 +117,7 @@ def dumps(obj, key=None, salt='django.core.signing', serializer=JSONSerializer,
value or re-using a salt value across different parts of your
application without good cause is a security risk.
"""
- data = serializer().dumps(obj)
+ data = force_bytes(serializer().dumps(obj))
# Flag for if it's been compressed or not
is_compressed = False
@@ -127,7 +130,7 @@ def dumps(obj, key=None, salt='django.core.signing', serializer=JSONSerializer,
is_compressed = True
base64d = b64_encode(data)
if is_compressed:
- base64d = '.' + base64d
+ base64d = b'.' + base64d
return TimestampSigner(key, salt=salt).sign(base64d)
@@ -135,35 +138,40 @@ def loads(s, key=None, salt='django.core.signing', serializer=JSONSerializer, ma
"""
Reverse of dumps(), raises BadSignature if signature fails
"""
- base64d = smart_bytes(
- TimestampSigner(key, salt=salt).unsign(s, max_age=max_age))
+ # TimestampSigner.unsign always returns unicode but base64 and zlib
+ # compression operate on bytes.
+ base64d = force_bytes(TimestampSigner(key, salt=salt).unsign(s, max_age=max_age))
decompress = False
- if base64d[0] == '.':
+ if base64d[0] == b'.':
# It's compressed; uncompress it first
base64d = base64d[1:]
decompress = True
data = b64_decode(base64d)
if decompress:
data = zlib.decompress(data)
- return serializer().loads(data)
+ return serializer().loads(force_str(data))
class Signer(object):
+
def __init__(self, key=None, sep=':', salt=None):
- self.sep = sep
- self.key = key or settings.SECRET_KEY
- self.salt = salt or ('%s.%s' %
- (self.__class__.__module__, self.__class__.__name__))
+ # Use of native strings in all versions of Python
+ self.sep = str(sep)
+ self.key = str(key or settings.SECRET_KEY)
+ self.salt = str(salt or
+ '%s.%s' % (self.__class__.__module__, self.__class__.__name__))
def signature(self, value):
- return base64_hmac(self.salt + 'signer', value, self.key)
+ signature = base64_hmac(self.salt + 'signer', value, self.key)
+ # Convert the signature from bytes to str only on Python 3
+ return force_str(signature)
def sign(self, value):
- value = smart_bytes(value)
- return '%s%s%s' % (value, self.sep, self.signature(value))
+ value = force_str(value)
+ return str('%s%s%s') % (value, self.sep, self.signature(value))
def unsign(self, signed_value):
- signed_value = smart_bytes(signed_value)
+ signed_value = force_str(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)
@@ -178,8 +186,9 @@ class TimestampSigner(Signer):
return baseconv.base62.encode(int(time.time()))
def sign(self, value):
- value = smart_bytes('%s%s%s' % (value, self.sep, self.timestamp()))
- return '%s%s%s' % (value, self.sep, self.signature(value))
+ value = force_str(value)
+ value = str('%s%s%s') % (value, self.sep, self.timestamp())
+ return super(TimestampSigner, self).sign(value)
def unsign(self, value, max_age=None):
result = super(TimestampSigner, self).unsign(value)
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index 2fe744e8eb..af3df83d0a 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_text, smart_bytes
+from django.utils.encoding import force_str, force_text, iri_to_uri
from django.utils.functional import memoize, lazy
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule
@@ -89,18 +89,11 @@ def get_callable(lookup_view, can_fail=False):
"""
if not callable(lookup_view):
mod_name, func_name = get_mod_func(lookup_view)
+ if func_name == '':
+ return lookup_view
+
try:
- if func_name != '':
- lookup_view = getattr(import_module(mod_name), func_name)
- if not callable(lookup_view):
- raise ViewDoesNotExist(
- "Could not import %s.%s. View is not callable." %
- (mod_name, func_name))
- except AttributeError:
- if not can_fail:
- raise ViewDoesNotExist(
- "Could not import %s. View does not exist in module %s." %
- (lookup_view, mod_name))
+ mod = import_module(mod_name)
except ImportError:
parentmod, submod = get_mod_func(mod_name)
if (not can_fail and submod != '' and
@@ -110,6 +103,18 @@ def get_callable(lookup_view, can_fail=False):
(lookup_view, mod_name))
if not can_fail:
raise
+ else:
+ try:
+ lookup_view = getattr(mod, func_name)
+ if not callable(lookup_view):
+ raise ViewDoesNotExist(
+ "Could not import %s.%s. View is not callable." %
+ (mod_name, func_name))
+ except AttributeError:
+ if not can_fail:
+ raise ViewDoesNotExist(
+ "Could not import %s. View does not exist in module %s." %
+ (lookup_view, mod_name))
return lookup_view
get_callable = memoize(get_callable, _callable_cache, 1)
@@ -190,7 +195,7 @@ class RegexURLPattern(LocaleRegexProvider):
self.name = name
def __repr__(self):
- return smart_bytes('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern))
+ return force_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern))
def add_prefix(self, prefix):
"""
@@ -240,7 +245,14 @@ class RegexURLResolver(LocaleRegexProvider):
self._app_dict = {}
def __repr__(self):
- return smart_bytes('<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern))
+ if isinstance(self.urlconf_name, list) and len(self.urlconf_name):
+ # Don't bother to output the whole list, it can be huge
+ urlconf_repr = '<%s list>' % self.urlconf_name[0].__class__.__name__
+ else:
+ urlconf_repr = repr(self.urlconf_name)
+ return force_str('<%s %s (%s:%s) %s>' % (
+ self.__class__.__name__, urlconf_repr, self.app_name,
+ self.namespace, self.regex.pattern))
def _populate(self):
lookups = MultiValueDict()
diff --git a/django/core/validators.py b/django/core/validators.py
index 456fa3cec5..317e3880bf 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_text
+from django.utils.encoding import force_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_text(value)):
+ if not self.regex.search(force_text(value)):
raise ValidationError(self.message, code=self.code)
class URLValidator(RegexValidator):
@@ -44,7 +44,8 @@ class URLValidator(RegexValidator):
r'^(?:http|ftp)s?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain...
r'localhost|' #localhost...
- r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
+ r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
+ r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
@@ -54,10 +55,10 @@ class URLValidator(RegexValidator):
except ValidationError as e:
# Trivial case failed. Try for possible IDN domain
if value:
- value = smart_text(value)
+ value = force_text(value)
scheme, netloc, path, query, fragment = urlsplit(value)
try:
- netloc = netloc.encode('idna') # IDN -> ACE
+ netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
except UnicodeError: # invalid domain part
raise e
url = urlunsplit((scheme, netloc, path, query, fragment))
@@ -84,7 +85,7 @@ class EmailValidator(RegexValidator):
if value and '@' in value:
parts = value.split('@')
try:
- parts[-1] = parts[-1].encode('idna')
+ parts[-1] = parts[-1].encode('idna').decode('ascii')
except UnicodeError:
raise e
super(EmailValidator, self).__call__('@'.join(parts))
@@ -99,7 +100,7 @@ email_re = re.compile(
r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE) # literal form, ipv4 address (SMTP 4.1.3)
validate_email = EmailValidator(email_re, _('Enter a valid e-mail address.'), 'invalid')
-slug_re = re.compile(r'^[-\w]+$')
+slug_re = re.compile(r'^[-a-zA-Z0-9_]+$')
validate_slug = RegexValidator(slug_re, _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid')
ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 5719f51445..efea9f802e 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -1,7 +1,7 @@
from django.db.utils import DatabaseError
try:
- import thread
+ from django.utils.six.moves import _thread as thread
except ImportError:
from django.utils.six.moves import _dummy_thread as thread
from contextlib import contextmanager
@@ -47,6 +47,8 @@ class BaseDatabaseWrapper(object):
def __ne__(self, other):
return not self == other
+ __hash__ = object.__hash__
+
def _commit(self):
if self.connection is not None:
return self.connection.commit()
@@ -621,7 +623,7 @@ class BaseDatabaseOperations(object):
exists for database backends to provide a better implementation
according to their own quoting schemes.
"""
- from django.utils.encoding import smart_text, force_text
+ from django.utils.encoding import force_text
# Convert params to contain Unicode values.
to_unicode = lambda s: force_text(s, strings_only=True, errors='replace')
@@ -630,7 +632,7 @@ class BaseDatabaseOperations(object):
else:
u_params = dict([(to_unicode(k), to_unicode(v)) for k, v in params.items()])
- return smart_text(sql) % u_params
+ return force_text(sql) % u_params
def last_insert_id(self, cursor, table_name, pk_name):
"""
@@ -814,8 +816,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_text
- return smart_text(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
+ from django.utils.encoding import force_text
+ return force_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.
@@ -892,19 +894,21 @@ class BaseDatabaseOperations(object):
return self.year_lookup_bounds(value)
def convert_values(self, value, field):
- """Coerce the value returned by the database backend into a consistent type that
- is compatible with the field type.
+ """
+ Coerce the value returned by the database backend into a consistent type
+ that is compatible with the field type.
"""
internal_type = field.get_internal_type()
if internal_type == 'DecimalField':
return value
- elif internal_type and internal_type.endswith('IntegerField') or internal_type == 'AutoField':
+ elif internal_type == 'FloatField':
+ return float(value)
+ elif (internal_type and (internal_type.endswith('IntegerField')
+ or internal_type == 'AutoField')):
return int(value)
elif internal_type in ('DateField', 'DateTimeField', 'TimeField'):
return value
- # No field, or the field isn't known to be a decimal or integer
- # Default to a float
- return float(value)
+ return value
def check_aggregate_support(self, aggregate_func):
"""Check that the backend supports the provided aggregate
@@ -1003,7 +1007,7 @@ class BaseDatabaseIntrospection(object):
for model in models.get_models(app):
if router.allow_syncdb(self.connection.alias, model):
all_models.append(model)
- tables = map(self.table_name_converter, tables)
+ tables = list(map(self.table_name_converter, tables))
return set([
m for m in all_models
if self.table_name_converter(m._meta.db_table) in tables
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index b3e09edc57..10649b64b9 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -39,7 +39,7 @@ from django.db.backends.mysql.introspection import DatabaseIntrospection
from django.db.backends.mysql.validation import DatabaseValidation
from django.db.backends.mysql.schema import DatabaseSchemaEditor
from django.utils.functional import cached_property
-from django.utils.safestring import SafeString, SafeUnicode
+from django.utils.safestring import SafeBytes, SafeText
from django.utils import six
from django.utils import timezone
@@ -76,7 +76,7 @@ def adapt_datetime_with_timezone_support(value, conv):
# MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like
# timedelta in terms of actual behavior as they are signed and include days --
# and Django expects time, so we still need to override that. We also need to
-# add special handling for SafeUnicode and SafeString as MySQLdb's type
+# add special handling for SafeText and SafeBytes as MySQLdb's type
# checking is too tight to catch those (see Django ticket #6052).
# Finally, MySQLdb always returns naive datetime objects. However, when
# timezone support is active, Django expects timezone-aware datetime objects.
@@ -403,8 +403,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
kwargs['client_flag'] = CLIENT.FOUND_ROWS
kwargs.update(settings_dict['OPTIONS'])
self.connection = Database.connect(**kwargs)
- self.connection.encoders[SafeUnicode] = self.connection.encoders[six.text_type]
- self.connection.encoders[SafeString] = self.connection.encoders[bytes]
+ self.connection.encoders[SafeText] = self.connection.encoders[six.text_type]
+ self.connection.encoders[SafeBytes] = self.connection.encoders[bytes]
connection_created.send(sender=self.__class__, connection=self)
cursor = self.connection.cursor()
if new_connection:
diff --git a/django/db/backends/mysql/compiler.py b/django/db/backends/mysql/compiler.py
index bd4105ff8e..d8e9b3a202 100644
--- a/django/db/backends/mysql/compiler.py
+++ b/django/db/backends/mysql/compiler.py
@@ -1,10 +1,16 @@
+try:
+ from itertools import zip_longest
+except ImportError:
+ from itertools import izip_longest as zip_longest
+
from django.db.models.sql import compiler
+
class SQLCompiler(compiler.SQLCompiler):
def resolve_columns(self, row, fields=()):
values = []
- index_extra_select = len(self.query.extra_select.keys())
- for value, field in map(None, row[index_extra_select:], fields):
+ index_extra_select = len(self.query.extra_select)
+ for value, field in zip_longest(row[index_extra_select:], fields):
if (field and field.get_internal_type() in ("BooleanField", "NullBooleanField") and
value in (0, 1)):
value = bool(value)
diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
index 310f270a0b..1fc55115a2 100644
--- a/django/db/backends/mysql/introspection.py
+++ b/django/db/backends/mysql/introspection.py
@@ -1,8 +1,9 @@
+import re
+from .base import FIELD_TYPE
+
from django.db.backends import BaseDatabaseIntrospection
from django.utils import six
-from MySQLdb import ProgrammingError, OperationalError
-from MySQLdb.constants import FIELD_TYPE
-import re
+
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
@@ -35,9 +36,20 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
return [row[0] for row in cursor.fetchall()]
def get_table_description(self, cursor, table_name):
- "Returns a description of the table, with the DB-API cursor.description interface."
+ """
+ Returns a description of the table, with the DB-API cursor.description interface."
+ """
+ # varchar length returned by cursor.description is an internal length,
+ # not visible length (#5725), use information_schema database to fix this
+ cursor.execute("""
+ SELECT column_name, character_maximum_length FROM information_schema.columns
+ WHERE table_name = %s AND table_schema = DATABASE()
+ AND character_maximum_length IS NOT NULL""", [table_name])
+ length_map = dict(cursor.fetchall())
+
cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
- return cursor.description
+ return [line[:3] + (length_map.get(line[0], line[3]),) + line[4:]
+ for line in cursor.description]
def _name_to_index(self, cursor, table_name):
"""
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 89cad12b6c..6bf2e815a7 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -10,8 +10,6 @@ import decimal
import sys
import warnings
-from django.utils import six
-
def _setup_environment(environ):
import platform
# Cygwin requires some special voodoo to set the environment variables
@@ -53,7 +51,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_bytes, force_text
+from django.utils.encoding import force_bytes, force_text
from django.utils import six
from django.utils import timezone
@@ -66,7 +64,7 @@ IntegrityError = Database.IntegrityError
if int(Database.version.split('.', 1)[0]) >= 5 and not hasattr(Database, 'UNICODE'):
convert_unicode = force_text
else:
- convert_unicode = smart_bytes
+ convert_unicode = force_bytes
class DatabaseFeatures(BaseDatabaseFeatures):
@@ -221,7 +219,10 @@ WHEN (new.%(col_name)s IS NULL)
def last_executed_query(self, cursor, sql, params):
# http://cx-oracle.sourceforge.net/html/cursor.html#Cursor.statement
# The DB API definition does not define this attribute.
- return cursor.statement.decode("utf-8")
+ if six.PY3:
+ return cursor.statement
+ else:
+ return cursor.statement.decode("utf-8")
def last_insert_id(self, cursor, table_name, pk_name):
sq_name = self._get_sequence_name(table_name)
@@ -594,10 +595,16 @@ class OracleParam(object):
param = timezone.make_aware(param, default_timezone)
param = param.astimezone(timezone.utc).replace(tzinfo=None)
+ # Oracle doesn't recognize True and False correctly in Python 3.
+ # The conversion done below works both in 2 and 3.
+ if param is True:
+ param = "1"
+ elif param is False:
+ param = "0"
if hasattr(param, 'bind_parameter'):
- self.smart_bytes = param.bind_parameter(cursor)
+ self.force_bytes = param.bind_parameter(cursor)
else:
- self.smart_bytes = convert_unicode(param, cursor.charset,
+ self.force_bytes = convert_unicode(param, cursor.charset,
strings_only)
if hasattr(param, 'input_size'):
# If parameter has `input_size` attribute, use that.
@@ -676,7 +683,7 @@ class FormatStylePlaceholderCursor(object):
self.setinputsizes(*sizes)
def _param_generator(self, params):
- return [p.smart_bytes for p in params]
+ return [p.force_bytes for p in params]
def execute(self, query, params=None):
if params is None:
diff --git a/django/db/backends/oracle/compiler.py b/django/db/backends/oracle/compiler.py
index c7b10429c2..24030cdffc 100644
--- a/django/db/backends/oracle/compiler.py
+++ b/django/db/backends/oracle/compiler.py
@@ -1,4 +1,9 @@
from django.db.models.sql import compiler
+# The izip_longest was renamed to zip_longest in py3
+try:
+ from itertools import zip_longest
+except ImportError:
+ from itertools import izip_longest as zip_longest
class SQLCompiler(compiler.SQLCompiler):
@@ -10,10 +15,10 @@ class SQLCompiler(compiler.SQLCompiler):
rn_offset = 1
else:
rn_offset = 0
- index_start = rn_offset + len(self.query.extra_select.keys())
+ index_start = rn_offset + len(self.query.extra_select)
values = [self.query.convert_values(v, None, connection=self.connection)
for v in row[rn_offset:index_start]]
- for value, field in map(None, row[index_start:], fields):
+ for value, field in zip_longest(row[index_start:], fields):
values.append(self.query.convert_values(value, field, connection=self.connection))
return tuple(values)
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
index 005c0a04b1..4cad6e06a7 100644
--- a/django/db/backends/postgresql_psycopg2/base.py
+++ b/django/db/backends/postgresql_psycopg2/base.py
@@ -15,7 +15,7 @@ from django.db.backends.postgresql_psycopg2.version import get_version
from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
from django.utils.log import getLogger
-from django.utils.safestring import SafeUnicode, SafeString
+from django.utils.safestring import SafeText, SafeBytes
from django.utils import six
from django.utils.timezone import utc
@@ -30,8 +30,8 @@ DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
-psycopg2.extensions.register_adapter(SafeString, psycopg2.extensions.QuotedString)
-psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedString)
+psycopg2.extensions.register_adapter(SafeBytes, psycopg2.extensions.QuotedString)
+psycopg2.extensions.register_adapter(SafeText, psycopg2.extensions.QuotedString)
logger = getLogger('django.db.backends')
diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py
index 1a08984bd2..916073c09f 100644
--- a/django/db/backends/postgresql_psycopg2/introspection.py
+++ b/django/db/backends/postgresql_psycopg2/introspection.py
@@ -45,8 +45,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
WHERE table_name = %s""", [table_name])
null_map = dict(cursor.fetchall())
cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
- return [tuple([item for item in line[:6]] + [null_map[line[0]]=='YES'])
- for line in cursor.description]
+ return [line[:6] + (null_map[line[0]]=='YES',)
+ for line in cursor.description]
def get_relations(self, cursor, table_name):
"""
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index 7918d5d3ef..d0a6fda78e 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -21,7 +21,7 @@ from django.db.backends.sqlite3.introspection import DatabaseIntrospection
from django.db.backends.sqlite3.schema import DatabaseSchemaEditor
from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.functional import cached_property
-from django.utils.safestring import SafeString
+from django.utils.safestring import SafeBytes
from django.utils import six
from django.utils import timezone
@@ -57,13 +57,21 @@ def adapt_datetime_with_timezone_support(value):
value = value.astimezone(timezone.utc).replace(tzinfo=None)
return value.isoformat(str(" "))
-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)
+def decoder(conv_func):
+ """ The Python sqlite3 interface returns always byte strings.
+ This function converts the received value to a regular string before
+ passing it to the receiver function.
+ """
+ return lambda s: conv_func(s.decode('utf-8'))
+
+Database.register_converter(str("bool"), decoder(lambda s: s == '1'))
+Database.register_converter(str("time"), decoder(parse_time))
+Database.register_converter(str("date"), decoder(parse_date))
+Database.register_converter(str("datetime"), decoder(parse_datetime_with_timezone_support))
+Database.register_converter(str("timestamp"), decoder(parse_datetime_with_timezone_support))
+Database.register_converter(str("TIMESTAMP"), decoder(parse_datetime_with_timezone_support))
+Database.register_converter(str("decimal"), decoder(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):
@@ -73,7 +81,7 @@ if Database.version_info >= (2, 4, 1):
# slow-down, this adapter is only registered for sqlite3 versions
# needing it (Python 2.6 and up).
Database.register_adapter(str, lambda s: s.decode('utf-8'))
- Database.register_adapter(SafeString, lambda s: s.decode('utf-8'))
+ Database.register_adapter(SafeBytes, lambda s: s.decode('utf-8'))
class DatabaseFeatures(BaseDatabaseFeatures):
# SQLite cannot handle us only partially reading from a cursor's result set
diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py
index 8135f3548c..1df4c18c1c 100644
--- a/django/db/backends/sqlite3/introspection.py
+++ b/django/db/backends/sqlite3/introspection.py
@@ -1,6 +1,14 @@
import re
from django.db.backends import BaseDatabaseIntrospection
+field_size_re = re.compile(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$')
+
+def get_field_size(name):
+ """ Extract the size number from a "varchar(11)" type name """
+ m = field_size_re.search(name)
+ return int(m.group(1)) if m else None
+
+
# This light wrapper "fakes" a dictionary interface, because some SQLite data
# types include variables in them -- e.g. "varchar(30)" -- and can't be matched
# as a simple dictionary lookup.
@@ -32,10 +40,9 @@ class FlexibleFieldLookupDict(object):
try:
return self.base_data_types_reverse[key]
except KeyError:
- import re
- m = re.search(r'^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$', key)
- if m:
- return ('CharField', {'max_length': int(m.group(1))})
+ size = get_field_size(key)
+ if size is not None:
+ return ('CharField', {'max_length': size})
raise KeyError
class DatabaseIntrospection(BaseDatabaseIntrospection):
@@ -53,7 +60,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
def get_table_description(self, cursor, table_name):
"Returns a description of the table, with the DB-API cursor.description interface."
- return [(info['name'], info['type'], None, None, None, None,
+ return [(info['name'], info['type'], None, info['size'], None, None,
info['null_ok']) for info in self._table_info(cursor, table_name)]
def get_relations(self, cursor, table_name):
@@ -171,6 +178,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
# cid, name, type, notnull, dflt_value, pk
return [{'name': field[1],
'type': field[2],
+ 'size': get_field_size(field[2]),
'null_ok': not field[3],
'pk': field[5] # undocumented
} for field in cursor.fetchall()]
diff --git a/django/db/backends/util.py b/django/db/backends/util.py
index 775bb3e182..75d4d07a66 100644
--- a/django/db/backends/util.py
+++ b/django/db/backends/util.py
@@ -6,6 +6,7 @@ import hashlib
from time import time
from django.conf import settings
+from django.utils.encoding import force_bytes
from django.utils.log import getLogger
from django.utils.timezone import utc
@@ -137,7 +138,7 @@ def truncate_name(name, length=None, hash_len=4):
if length is None or len(name) <= length:
return name
- hsh = hashlib.md5(name).hexdigest()[:hash_len]
+ hsh = hashlib.md5(force_bytes(name)).hexdigest()[:hash_len]
return '%s%s' % (name[:length-hash_len], hsh)
def format_number(value, max_digits, decimal_places):
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 569b8e876c..62024c8ee4 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -23,7 +23,7 @@ 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_bytes, force_text
+from django.utils.encoding import force_str, force_text
from django.utils import six
from django.utils.text import get_text_list, capfirst
@@ -404,10 +404,10 @@ class Model(six.with_metaclass(ModelBase, object)):
u = six.text_type(self)
except (UnicodeEncodeError, UnicodeDecodeError):
u = '[Bad Unicode data]'
- return smart_bytes('<%s: %s>' % (self.__class__.__name__, u))
+ return force_str('<%s: %s>' % (self.__class__.__name__, u))
def __str__(self):
- if hasattr(self, '__unicode__'):
+ if not six.PY3 and hasattr(self, '__unicode__'):
return force_text(self).encode('utf-8')
return '%s object' % self.__class__.__name__
@@ -475,6 +475,7 @@ class Model(six.with_metaclass(ModelBase, object)):
that the "save" must be an SQL insert or update (or equivalent for
non-SQL backends), respectively. Normally, they should not be set.
"""
+ using = using or router.db_for_write(self.__class__, instance=self)
if force_insert and (force_update or update_fields):
raise ValueError("Cannot force both insert and updating in model saving.")
@@ -502,6 +503,23 @@ class Model(six.with_metaclass(ModelBase, object)):
"model or are m2m fields: %s"
% ', '.join(non_model_fields))
+ # If saving to the same database, and this model is deferred, then
+ # automatically do a "update_fields" save on the loaded fields.
+ elif not force_insert and self._deferred and using == self._state.db:
+ field_names = set()
+ for field in self._meta.fields:
+ if not field.primary_key and not hasattr(field, 'through'):
+ field_names.add(field.attname)
+ deferred_fields = [
+ f.attname for f in self._meta.fields
+ if f.attname not in self.__dict__
+ and isinstance(self.__class__.__dict__[f.attname],
+ DeferredAttribute)]
+
+ loaded_fields = field_names.difference(deferred_fields)
+ if loaded_fields:
+ update_fields = frozenset(loaded_fields)
+
self.save_base(using=using, force_insert=force_insert,
force_update=force_update, update_fields=update_fields)
save.alters_data = True
@@ -636,7 +654,7 @@ class Model(six.with_metaclass(ModelBase, object)):
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_bytes(getattr(self, field.attname))
+ param = force_text(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)
@@ -761,7 +779,7 @@ class Model(six.with_metaclass(ModelBase, object)):
lookup_kwargs[str(field_name)] = lookup_value
# some fields were skipped, no reason to do the check
- if len(unique_check) != len(lookup_kwargs.keys()):
+ if len(unique_check) != len(lookup_kwargs):
continue
qs = model_class._default_manager.filter(**lookup_kwargs)
@@ -837,7 +855,7 @@ class Model(six.with_metaclass(ModelBase, object)):
}
# unique_together
else:
- field_labels = map(lambda f: capfirst(opts.get_field(f).verbose_name), unique_check)
+ field_labels = [capfirst(opts.get_field(f).verbose_name) for f in unique_check]
field_labels = get_text_list(field_labels, _('and'))
return _("%(model_name)s with this %(field_label)s already exists.") % {
'model_name': six.text_type(model_name),
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
index a71f4a33ef..639ef6ee10 100644
--- a/django/db/models/expressions.py
+++ b/django/db/models/expressions.py
@@ -58,8 +58,9 @@ class ExpressionNode(tree.Node):
def __mul__(self, other):
return self._combine(other, self.MUL, False)
- def __div__(self, other):
+ def __truediv__(self, other):
return self._combine(other, self.DIV, False)
+ __div__ = __truediv__ # Python 2 compatibility
def __mod__(self, other):
return self._combine(other, self.MOD, False)
@@ -79,8 +80,9 @@ class ExpressionNode(tree.Node):
def __rmul__(self, other):
return self._combine(other, self.MUL, True)
- def __rdiv__(self, other):
+ def __rtruediv__(self, other):
return self._combine(other, self.DIV, True)
+ __rdiv__ = __rtruediv__ # Python 2 compatibility
def __rmod__(self, other):
return self._combine(other, self.MOD, True)
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index d07851bbf5..58ae3413f3 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -1047,13 +1047,14 @@ class GenericIPAddressField(Field):
description = _("IP address")
default_error_messages = {}
- def __init__(self, protocol='both', unpack_ipv4=False, *args, **kwargs):
+ def __init__(self, verbose_name=None, name=None, protocol='both',
+ unpack_ipv4=False, *args, **kwargs):
self.unpack_ipv4 = unpack_ipv4
self.default_validators, invalid_error_message = \
validators.ip_address_validators(protocol, unpack_ipv4)
self.default_error_messages['invalid'] = invalid_error_message
kwargs['max_length'] = 39
- Field.__init__(self, *args, **kwargs)
+ Field.__init__(self, verbose_name, name, *args, **kwargs)
def get_internal_type(self):
return "GenericIPAddressField"
diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py
index ad4c36ca0d..f16632ef25 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_text, smart_bytes
+from django.utils.encoding import force_str, force_text
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_text(datetime.datetime.now().strftime(smart_bytes(self.upload_to))))
+ return os.path.normpath(force_text(datetime.datetime.now().strftime(force_str(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/subclassing.py b/django/db/models/fields/subclassing.py
index d4d7d75222..e6153aefe0 100644
--- a/django/db/models/fields/subclassing.py
+++ b/django/db/models/fields/subclassing.py
@@ -2,8 +2,9 @@
Convenience routines for creating non-trivial Field subclasses, as well as
backwards compatibility utilities.
-Add SubfieldBase as the __metaclass__ for your Field subclass, implement
-to_python() and the other necessary methods and everything will work seamlessly.
+Add SubfieldBase as the metaclass for your Field subclass, implement
+to_python() and the other necessary methods and everything will work
+seamlessly.
"""
class SubfieldBase(type):
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 2e8ccb49ce..6814ce27ff 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
import re
from bisect import bisect
@@ -8,9 +10,10 @@ 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_text, smart_bytes
+from django.utils.encoding import force_text, smart_text
from django.utils.datastructures import SortedDict
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
@@ -20,6 +23,7 @@ DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
'order_with_respect_to', 'app_label', 'db_tablespace',
'abstract', 'managed', 'proxy', 'auto_created')
+@python_2_unicode_compatible
class Options(object):
def __init__(self, meta, app_label=None):
self.local_fields, self.local_many_to_many = [], []
@@ -199,7 +203,7 @@ class Options(object):
return '<Options for %s>' % self.object_name
def __str__(self):
- return "%s.%s" % (smart_bytes(self.app_label), smart_bytes(self.module_name))
+ return "%s.%s" % (smart_text(self.app_label), smart_text(self.module_name))
def verbose_name_raw(self):
"""
@@ -383,7 +387,7 @@ class Options(object):
predicates.append(lambda k, v: not k.field.rel.is_hidden())
cache = (self._related_objects_proxy_cache if include_proxy_eq
else self._related_objects_cache)
- return filter(lambda t: all([p(*t) for p in predicates]), cache.items())
+ return [t for t in cache.items() if all(p(*t) for p in predicates)]
def _fill_related_objects_cache(self):
cache = SortedDict()
diff --git a/django/db/models/query.py b/django/db/models/query.py
index e8d6ae2a7b..05c049b31f 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -472,7 +472,7 @@ class QuerySet(object):
return self.get(**lookup), False
except self.model.DoesNotExist:
# Re-raise the IntegrityError with its original traceback.
- six.reraise(exc_info[1], None, exc_info[2])
+ six.reraise(*exc_info)
def latest(self, field_name=None):
"""
@@ -498,9 +498,7 @@ class QuerySet(object):
"Cannot use 'limit' or 'offset' with in_bulk"
if not id_list:
return {}
- qs = self._clone()
- qs.query.add_filter(('pk__in', id_list))
- qs.query.clear_ordering(force_empty=True)
+ qs = self.filter(pk__in=id_list).order_by()
return dict([(obj._get_pk_val(), obj) for obj in qs])
def delete(self):
@@ -1107,7 +1105,7 @@ class ValuesListQuerySet(ValuesQuerySet):
# If a field list has been specified, use it. Otherwise, use the
# full list of fields, including extras and aggregates.
if self._fields:
- fields = list(self._fields) + filter(lambda f: f not in self._fields, aggregate_names)
+ fields = list(self._fields) + [f for f in aggregate_names if f not in self._fields]
else:
fields = names
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 1311ea546c..cf7cad29bd 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -470,9 +470,7 @@ class SQLCompiler(object):
# Must use left outer joins for nullable fields and their relations.
# Ordering or distinct must not affect the returned set, and INNER
# JOINS for nullable fields could do this.
- if joins_to_promote:
- self.query.promote_alias_chain(joins_to_promote,
- self.query.alias_map[joins_to_promote[0]].join_type == self.query.LOUTER)
+ self.query.promote_joins(joins_to_promote)
return field, col, alias, joins, opts
def _final_join_removal(self, col, alias):
@@ -645,8 +643,6 @@ class SQLCompiler(object):
alias_chain.append(alias)
for (dupe_opts, dupe_col) in dupe_set:
self.query.update_dupe_avoidance(dupe_opts, dupe_col, alias)
- if self.query.alias_map[root_alias].join_type == self.query.LOUTER:
- self.query.promote_alias_chain(alias_chain, True)
else:
alias = root_alias
@@ -663,8 +659,6 @@ class SQLCompiler(object):
columns, aliases = self.get_default_columns(start_alias=alias,
opts=f.rel.to._meta, as_pairs=True)
self.query.related_select_cols.extend(columns)
- if self.query.alias_map[alias].join_type == self.query.LOUTER:
- self.query.promote_alias_chain(aliases, True)
self.query.related_select_fields.extend(f.rel.to._meta.fields)
if restricted:
next = requested.get(f.name, {})
@@ -738,7 +732,9 @@ class SQLCompiler(object):
self.query.related_select_fields.extend(model._meta.fields)
next = requested.get(f.related_query_name(), {})
- new_nullable = f.null or None
+ # Use True here because we are looking at the _reverse_ side of
+ # the relation, which is always nullable.
+ new_nullable = True
self.fill_related_selections(model._meta, table, cur_depth+1,
used, next, restricted, new_nullable)
@@ -786,7 +782,7 @@ class SQLCompiler(object):
row = self.resolve_columns(row, fields)
if has_aggregate_select:
- aggregate_start = len(self.query.extra_select.keys()) + len(self.query.select)
+ aggregate_start = len(self.query.extra_select) + len(self.query.select)
aggregate_end = aggregate_start + len(self.query.aggregate_select)
row = tuple(row[:aggregate_start]) + tuple([
self.query.resolve_aggregate(value, aggregate, self.connection)
diff --git a/django/db/models/sql/constants.py b/django/db/models/sql/constants.py
index 612755a012..b9cf2c96fd 100644
--- a/django/db/models/sql/constants.py
+++ b/django/db/models/sql/constants.py
@@ -1,7 +1,7 @@
from collections import namedtuple
import re
-# Valid query types (a dictionary is used for speedy lookups).
+# Valid query types (a set is used for speedy lookups).
QUERY_TERMS = set([
'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in',
'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year',
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index be257a5410..1915efa7b9 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -505,7 +505,7 @@ class Query(object):
# Again, some of the tables won't have aliases due to
# the trimming of unnecessary tables.
if self.alias_refcount.get(alias) or rhs.alias_refcount.get(alias):
- self.promote_alias(alias, True)
+ self.promote_joins([alias], True)
# Now relabel a copy of the rhs where-clause and add it to the current
# one.
@@ -682,32 +682,38 @@ class Query(object):
""" Decreases the reference count for this alias. """
self.alias_refcount[alias] -= amount
- def promote_alias(self, alias, unconditional=False):
+ def promote_joins(self, aliases, unconditional=False):
"""
- Promotes the join type of an alias to an outer join if it's possible
- for the join to contain NULL values on the left. If 'unconditional' is
- False, the join is only promoted if it is nullable, otherwise it is
- always promoted.
+ Promotes recursively the join type of given aliases and its children to
+ an outer join. If 'unconditional' is False, the join is only promoted if
+ it is nullable or the parent join is an outer join.
- Returns True if the join was promoted by this call.
+ Note about join promotion: When promoting any alias, we make sure all
+ joins which start from that alias are promoted, too. When adding a join
+ in join(), we make sure any join added to already existing LOUTER join
+ is generated as LOUTER. This ensures we don't ever have broken join
+ chains which contain first a LOUTER join, then an INNER JOIN, that is
+ this kind of join should never be generated: a LOUTER b INNER c. The
+ reason for avoiding this type of join chain is that the INNER after
+ the LOUTER will effectively remove any effect the LOUTER had.
"""
- if ((unconditional or self.alias_map[alias].nullable) and
- self.alias_map[alias].join_type != self.LOUTER):
- data = self.alias_map[alias]
- data = data._replace(join_type=self.LOUTER)
- self.alias_map[alias] = data
- return True
- return False
-
- def promote_alias_chain(self, chain, must_promote=False):
- """
- Walks along a chain of aliases, promoting the first nullable join and
- any joins following that. If 'must_promote' is True, all the aliases in
- the chain are promoted.
- """
- for alias in chain:
- if self.promote_alias(alias, must_promote):
- must_promote = True
+ aliases = list(aliases)
+ while aliases:
+ alias = aliases.pop(0)
+ parent_alias = self.alias_map[alias].lhs_alias
+ parent_louter = (parent_alias
+ and self.alias_map[parent_alias].join_type == self.LOUTER)
+ already_louter = self.alias_map[alias].join_type == self.LOUTER
+ if ((unconditional or self.alias_map[alias].nullable
+ or parent_louter) and not already_louter):
+ data = self.alias_map[alias]._replace(join_type=self.LOUTER)
+ self.alias_map[alias] = data
+ # Join type of 'alias' changed, so re-examine all aliases that
+ # refer to this one.
+ aliases.extend(
+ join for join in self.alias_map.keys()
+ if (self.alias_map[join].lhs_alias == alias
+ and join not in aliases))
def reset_refcounts(self, to_counts):
"""
@@ -726,19 +732,10 @@ class Query(object):
then and which ones haven't been used and promotes all of those
aliases, plus any children of theirs in the alias tree, to outer joins.
"""
- # FIXME: There's some (a lot of!) overlap with the similar OR promotion
- # in add_filter(). It's not quite identical, but is very similar. So
- # pulling out the common bits is something for later.
- considered = {}
for alias in self.tables:
- if alias not in used_aliases:
- continue
- if (alias not in initial_refcounts or
+ if alias in used_aliases and (alias not in initial_refcounts or
self.alias_refcount[alias] == initial_refcounts[alias]):
- parent = self.alias_map[alias].lhs_alias
- must_promote = considered.get(parent, False)
- promoted = self.promote_alias(alias, must_promote)
- considered[alias] = must_promote or promoted
+ self.promote_joins([alias])
def change_aliases(self, change_map):
"""
@@ -875,6 +872,9 @@ class Query(object):
LOUTER join type. This is used when joining certain types of querysets
and Q-objects together.
+ A join is always created as LOUTER if the lhs alias is LOUTER to make
+ sure we do not generate chains like a LOUTER b INNER c.
+
If 'nullable' is True, the join can potentially involve NULL values and
is a candidate for promotion (to "left outer") when combining querysets.
"""
@@ -900,8 +900,8 @@ class Query(object):
if self.alias_map[alias].lhs_alias != lhs:
continue
self.ref_alias(alias)
- if promote:
- self.promote_alias(alias)
+ if promote or (lhs and self.alias_map[lhs].join_type == self.LOUTER):
+ self.promote_joins([alias])
return alias
# No reuse is possible, so we need a new alias.
@@ -910,7 +910,12 @@ class Query(object):
# Not all tables need to be joined to anything. No join type
# means the later columns are ignored.
join_type = None
- elif promote or outer_if_first:
+ elif (promote or outer_if_first
+ or self.alias_map[lhs].join_type == self.LOUTER):
+ # We need to use LOUTER join if asked by promote or outer_if_first,
+ # or if the LHS table is left-joined in the query. Adding inner join
+ # to an existing outer join effectively cancels the effect of the
+ # outer join.
join_type = self.LOUTER
else:
join_type = self.INNER
@@ -1004,8 +1009,7 @@ class Query(object):
# If the aggregate references a model or field that requires a join,
# those joins must be LEFT OUTER - empty join rows must be returned
# in order for zeros to be returned for those aggregates.
- for column_alias in join_list:
- self.promote_alias(column_alias, unconditional=True)
+ self.promote_joins(join_list, True)
col = (join_list[-1], col)
else:
@@ -1124,7 +1128,7 @@ class Query(object):
# If the comparison is against NULL, we may need to use some left
# outer joins when creating the join chain. This is only done when
# needed, as it's less efficient at the database level.
- self.promote_alias_chain(join_list)
+ self.promote_joins(join_list)
join_promote = True
# Process the join list to see if we can remove any inner joins from
@@ -1155,16 +1159,16 @@ class Query(object):
# This means that we are dealing with two different query
# subtrees, so we don't need to do any join promotion.
continue
- join_promote = join_promote or self.promote_alias(join, unconditional)
+ join_promote = join_promote or self.promote_joins([join], unconditional)
if table != join:
- table_promote = self.promote_alias(table)
+ table_promote = self.promote_joins([table])
# We only get here if we have found a table that exists
# in the join list, but isn't on the original tables list.
# This means we've reached the point where we only have
# new tables, so we can break out of this promotion loop.
break
- self.promote_alias_chain(join_it, join_promote)
- self.promote_alias_chain(table_it, table_promote or join_promote)
+ self.promote_joins(join_it, join_promote)
+ self.promote_joins(table_it, table_promote or join_promote)
if having_clause or force_having:
if (alias, col) not in self.group_by:
@@ -1176,7 +1180,7 @@ class Query(object):
connector)
if negate:
- self.promote_alias_chain(join_list)
+ self.promote_joins(join_list)
if lookup_type != 'isnull':
if len(join_list) > 1:
for alias in join_list:
@@ -1550,7 +1554,7 @@ class Query(object):
N-to-many relation field.
"""
query = Query(self.model)
- query.add_filter(filter_expr, can_reuse=can_reuse)
+ query.add_filter(filter_expr)
query.bump_prefix()
query.clear_ordering(True)
query.set_start(prefix)
@@ -1650,7 +1654,7 @@ class Query(object):
final_alias = join.lhs_alias
col = join.lhs_join_col
joins = joins[:-1]
- self.promote_alias_chain(joins[1:])
+ self.promote_joins(joins[1:])
self.select.append((final_alias, col))
self.select_fields.append(field)
except MultiJoin:
diff --git a/django/dispatch/saferef.py b/django/dispatch/saferef.py
index d8728e219a..84d1b2183c 100644
--- a/django/dispatch/saferef.py
+++ b/django/dispatch/saferef.py
@@ -149,9 +149,11 @@ class BoundMethodWeakref(object):
self.selfName,
self.funcName,
)
-
+
__repr__ = __str__
-
+
+ __hash__ = object.__hash__
+
def __bool__( self ):
"""Whether we are still a valid reference"""
return self() is not None
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 45b758202a..3299c2becc 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_text, force_text
+from django.utils.encoding import smart_text, force_text, python_2_unicode_compatible
from django.utils.safestring import mark_safe
from django.utils import six
@@ -68,7 +68,8 @@ class DeclarativeFieldsMetaclass(type):
new_class.media = media_property(new_class)
return new_class
-class BaseForm(StrAndUnicode):
+@python_2_unicode_compatible
+class BaseForm(object):
# This is the main implementation of all the Form logic. Note that this
# class is different than Form. See the comments by the Form class for more
# information. Any improvements to the form API should be made to *this*
@@ -95,7 +96,7 @@ class BaseForm(StrAndUnicode):
# self.base_fields.
self.fields = copy.deepcopy(self.base_fields)
- def __unicode__(self):
+ def __str__(self):
return self.as_table()
def __iter__(self):
@@ -387,7 +388,8 @@ class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):
# to define a form using declarative syntax.
# BaseForm itself has no way of designating self.fields.
-class BoundField(StrAndUnicode):
+@python_2_unicode_compatible
+class BoundField(object):
"A Field plus data"
def __init__(self, form, field, name):
self.form = form
@@ -402,7 +404,7 @@ class BoundField(StrAndUnicode):
self.label = self.field.label
self.help_text = field.help_text or ''
- def __unicode__(self):
+ def __str__(self):
"""Renders this field as an HTML widget."""
if self.field.show_hidden_initial:
return self.as_widget() + self.as_hidden(only_initial=True)
diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index 4ea8dc4ca9..bdcef92dec 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -5,7 +5,7 @@ from django.forms import Form
from django.forms.fields import IntegerField, BooleanField
from django.forms.util import ErrorList
from django.forms.widgets import Media, HiddenInput
-from django.utils.encoding import StrAndUnicode
+from django.utils.encoding import python_2_unicode_compatible
from django.utils.safestring import mark_safe
from django.utils import six
from django.utils.six.moves import xrange
@@ -33,7 +33,8 @@ class ManagementForm(Form):
self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
super(ManagementForm, self).__init__(*args, **kwargs)
-class BaseFormSet(StrAndUnicode):
+@python_2_unicode_compatible
+class BaseFormSet(object):
"""
A collection of instances of the same Form class.
"""
@@ -51,7 +52,7 @@ class BaseFormSet(StrAndUnicode):
# construct the forms in the formset
self._construct_forms()
- def __unicode__(self):
+ def __str__(self):
return self.as_table()
def __iter__(self):
@@ -94,10 +95,11 @@ class BaseFormSet(StrAndUnicode):
total_forms = initial_forms + self.extra
# Allow all existing related objects/inlines to be displayed,
# but don't allow extra beyond max_num.
- if initial_forms > self.max_num >= 0:
- total_forms = initial_forms
- elif total_forms > self.max_num >= 0:
- total_forms = self.max_num
+ if self.max_num is not None:
+ if initial_forms > self.max_num >= 0:
+ total_forms = initial_forms
+ elif total_forms > self.max_num >= 0:
+ total_forms = self.max_num
return total_forms
def initial_form_count(self):
@@ -107,7 +109,7 @@ class BaseFormSet(StrAndUnicode):
else:
# Use the length of the inital data if it's there, 0 otherwise.
initial_forms = self.initial and len(self.initial) or 0
- if initial_forms > self.max_num >= 0:
+ if self.max_num is not None and (initial_forms > self.max_num >= 0):
initial_forms = self.max_num
return initial_forms
@@ -252,13 +254,10 @@ class BaseFormSet(StrAndUnicode):
errors = property(_get_errors)
def _should_delete_form(self, form):
- # The way we lookup the value of the deletion field here takes
- # more code than we'd like, but the form's cleaned_data will
- # not exist if the form is invalid.
- field = form.fields[DELETION_FIELD_NAME]
- raw_value = form._raw_value(DELETION_FIELD_NAME)
- should_delete = field.clean(raw_value)
- return should_delete
+ """
+ Returns whether or not the form was marked for deletion.
+ """
+ return form.cleaned_data.get(DELETION_FIELD_NAME, False)
def is_valid(self):
"""
diff --git a/django/forms/models.py b/django/forms/models.py
index 0b07d31d9a..1aa49eaaec 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -18,7 +18,6 @@ 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__ = (
@@ -401,13 +400,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
'formfield_callback': formfield_callback
}
- form_metaclass = ModelFormMetaclass
-
- # TODO: this doesn't work under Python 3.
- if issubclass(form, BaseModelForm) and hasattr(form, '__metaclass__'):
- form_metaclass = form.__metaclass__
-
- return form_metaclass(class_name, (form,), form_class_attrs)
+ # Instatiate type(form) in order to use the same metaclass as form.
+ return type(form)(class_name, (form,), form_class_attrs)
# ModelFormSets ##############################################################
@@ -597,6 +591,10 @@ class BaseModelFormSet(BaseFormSet):
return []
saved_instances = []
+ try:
+ forms_to_delete = self.deleted_forms
+ except AttributeError:
+ forms_to_delete = []
for form in self.initial_forms:
pk_name = self._pk_field.name
raw_pk_value = form._raw_value(pk_name)
@@ -607,7 +605,7 @@ class BaseModelFormSet(BaseFormSet):
pk_value = getattr(pk_value, 'pk', pk_value)
obj = self._existing_object(pk_value)
- if self.can_delete and self._should_delete_form(form):
+ if form in forms_to_delete:
self.deleted_objects.append(obj)
obj.delete()
continue
diff --git a/django/forms/util.py b/django/forms/util.py
index cd6b52df6f..9b1bcebe33 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_text
+from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.safestring import mark_safe
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
@@ -22,13 +22,14 @@ def flatatt(attrs):
"""
return format_html_join('', ' {0}="{1}"', attrs.items())
-class ErrorDict(dict, StrAndUnicode):
+@python_2_unicode_compatible
+class ErrorDict(dict):
"""
A collection of errors that knows how to display itself in various formats.
The dictionary keys are the field names, and the values are the errors.
"""
- def __unicode__(self):
+ def __str__(self):
return self.as_ul()
def as_ul(self):
@@ -42,11 +43,12 @@ class ErrorDict(dict, StrAndUnicode):
def as_text(self):
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):
+@python_2_unicode_compatible
+class ErrorList(list):
"""
A collection of errors that knows how to display itself in various formats.
"""
- def __unicode__(self):
+ def __str__(self):
return self.as_ul()
def as_ul(self):
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index be9ac8eb8f..fdb9f50688 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -17,11 +17,9 @@ 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_text
+from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.safestring import mark_safe
-from django.utils import six
-from django.utils import datetime_safe, formats
-from django.utils import six
+from django.utils import datetime_safe, formats, six
__all__ = (
'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput',
@@ -34,7 +32,8 @@ __all__ = (
MEDIA_TYPES = ('css','js')
-class Media(StrAndUnicode):
+@python_2_unicode_compatible
+class Media(object):
def __init__(self, media=None, **kwargs):
if media:
media_attrs = media.__dict__
@@ -51,7 +50,7 @@ class Media(StrAndUnicode):
# if media_attrs != {}:
# raise TypeError("'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys()))
- def __unicode__(self):
+ def __str__(self):
return self.render()
def render(self):
@@ -142,7 +141,8 @@ class MediaDefiningClass(type):
new_class.media = media_property(new_class)
return new_class
-class SubWidget(StrAndUnicode):
+@python_2_unicode_compatible
+class SubWidget(object):
"""
Some widgets are made of multiple HTML elements -- namely, RadioSelect.
This is a class that represents the "inner" HTML element of a widget.
@@ -152,7 +152,7 @@ class SubWidget(StrAndUnicode):
self.name, self.value = name, value
self.attrs, self.choices = attrs, choices
- def __unicode__(self):
+ def __str__(self):
args = [self.name, self.value, self.attrs]
if self.choices:
args.append(self.choices)
@@ -647,6 +647,7 @@ class SelectMultiple(Select):
data_set = set([force_text(value) for value in data])
return data_set != initial_set
+@python_2_unicode_compatible
class RadioInput(SubWidget):
"""
An object used by RadioFieldRenderer that represents a single
@@ -660,7 +661,7 @@ class RadioInput(SubWidget):
self.choice_label = force_text(choice[1])
self.index = index
- def __unicode__(self):
+ def __str__(self):
return self.render()
def render(self, name=None, value=None, attrs=None, choices=()):
@@ -685,7 +686,8 @@ class RadioInput(SubWidget):
final_attrs['checked'] = 'checked'
return format_html('<input{0} />', flatatt(final_attrs))
-class RadioFieldRenderer(StrAndUnicode):
+@python_2_unicode_compatible
+class RadioFieldRenderer(object):
"""
An object used by RadioSelect to enable customization of radio widgets.
"""
@@ -702,7 +704,7 @@ class RadioFieldRenderer(StrAndUnicode):
choice = self.choices[idx] # Let the IndexError propogate
return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
- def __unicode__(self):
+ def __str__(self):
return self.render()
def render(self):
diff --git a/django/http/__init__.py b/django/http/__init__.py
index 05824ad1d9..2198f38cbb 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -61,14 +61,14 @@ else:
if not _cookie_allows_colon_in_names:
def load(self, rawdata):
self.bad_cookies = set()
- super(SimpleCookie, self).load(smart_bytes(rawdata))
+ super(SimpleCookie, self).load(force_str(rawdata))
for key in self.bad_cookies:
del self[key]
# override private __set() method:
# (needed for using our Morsel, and for laxness with CookieError
def _BaseCookie__set(self, key, real_value, coded_value):
- key = smart_bytes(key)
+ key = force_str(key)
try:
M = self.get(key, Morsel())
M.set(key, real_value, coded_value)
@@ -85,7 +85,7 @@ from django.core.files import uploadhandler
from django.http.multipartparser import MultiPartParser
from django.http.utils import *
from django.utils.datastructures import MultiValueDict, ImmutableList
-from django.utils.encoding import smart_bytes, iri_to_uri, force_text
+from django.utils.encoding import force_bytes, force_str, force_text, iri_to_uri
from django.utils.http import cookie_date
from django.utils import six
from django.utils import timezone
@@ -113,7 +113,7 @@ def build_request_repr(request, path_override=None, GET_override=None,
get = (pformat(GET_override)
if GET_override is not None
else pformat(request.GET))
- except:
+ except Exception:
get = '<could not parse>'
if request._post_parse_error:
post = '<could not parse>'
@@ -122,22 +122,22 @@ def build_request_repr(request, path_override=None, GET_override=None,
post = (pformat(POST_override)
if POST_override is not None
else pformat(request.POST))
- except:
+ except Exception:
post = '<could not parse>'
try:
cookies = (pformat(COOKIES_override)
if COOKIES_override is not None
else pformat(request.COOKIES))
- except:
+ except Exception:
cookies = '<could not parse>'
try:
meta = (pformat(META_override)
if META_override is not None
else pformat(request.META))
- except:
+ except Exception:
meta = '<could not parse>'
path = path_override if path_override is not None else request.path
- return smart_bytes('<%s\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' %
+ return force_str('<%s\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' %
(request.__class__.__name__,
path,
six.text_type(get),
@@ -177,14 +177,14 @@ class HttpRequest(object):
# Reconstruct the host using the algorithm from PEP 333.
host = self.META['SERVER_NAME']
server_port = str(self.META['SERVER_PORT'])
- if server_port != (self.is_secure() and '443' or '80'):
+ if server_port != ('443' if self.is_secure() else '80'):
host = '%s:%s' % (host, server_port)
return host
def get_full_path(self):
# RFC 3986 requires query string arguments to be in the ASCII range.
# Rather than crash if this doesn't happen, we encode defensively.
- return '%s%s' % (self.path, self.META.get('QUERY_STRING', '') and ('?' + iri_to_uri(self.META.get('QUERY_STRING', ''))) or '')
+ return '%s%s' % (self.path, ('?' + iri_to_uri(self.META.get('QUERY_STRING', ''))) if self.META.get('QUERY_STRING', '') else '')
def get_signed_cookie(self, key, default=RAISE_ERROR, salt='', max_age=None):
"""
@@ -193,7 +193,7 @@ class HttpRequest(object):
default argument in which case that value will be returned instead.
"""
try:
- cookie_value = self.COOKIES[key].encode('utf-8')
+ cookie_value = self.COOKIES[key]
except KeyError:
if default is not RAISE_ERROR:
return default
@@ -218,7 +218,7 @@ class HttpRequest(object):
if not location:
location = self.get_full_path()
if not absolute_http_url_re.match(location):
- current_uri = '%s://%s%s' % (self.is_secure() and 'https' or 'http',
+ current_uri = '%s://%s%s' % ('https' if self.is_secure() else 'http',
self.get_host(), self.path)
location = urljoin(current_uri, location)
return iri_to_uri(location)
@@ -243,7 +243,12 @@ class HttpRequest(object):
def is_ajax(self):
return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
- def _set_encoding(self, val):
+ @property
+ def encoding(self):
+ return self._encoding
+
+ @encoding.setter
+ def encoding(self, val):
"""
Sets the encoding used for GET/POST accesses. If the GET or POST
dictionary has already been created, it is removed and recreated on the
@@ -255,33 +260,28 @@ class HttpRequest(object):
if hasattr(self, '_post'):
del self._post
- def _get_encoding(self):
- return self._encoding
-
- encoding = property(_get_encoding, _set_encoding)
-
def _initialize_handlers(self):
self._upload_handlers = [uploadhandler.load_handler(handler, self)
for handler in settings.FILE_UPLOAD_HANDLERS]
- def _set_upload_handlers(self, upload_handlers):
- if hasattr(self, '_files'):
- raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
- self._upload_handlers = upload_handlers
-
- def _get_upload_handlers(self):
+ @property
+ def upload_handlers(self):
if not self._upload_handlers:
# If there are no upload handlers defined, initialize them from settings.
self._initialize_handlers()
return self._upload_handlers
- upload_handlers = property(_get_upload_handlers, _set_upload_handlers)
+ @upload_handlers.setter
+ def upload_handlers(self, upload_handlers):
+ if hasattr(self, '_files'):
+ raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
+ self._upload_handlers = upload_handlers
def parse_file_upload(self, META, post_data):
"""Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
self.upload_handlers = ImmutableList(
self.upload_handlers,
- warning = "You cannot alter upload handlers after the upload has been processed."
+ warning="You cannot alter upload handlers after the upload has been processed."
)
parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
return parser.parse()
@@ -294,7 +294,7 @@ class HttpRequest(object):
try:
self._body = self.read()
except IOError as e:
- six.reraise(UnreadablePostError, e, sys.exc_traceback)
+ six.reraise(UnreadablePostError, UnreadablePostError(*e.args), sys.exc_info()[2])
self._stream = BytesIO(self._body)
return self._body
@@ -360,6 +360,7 @@ class HttpRequest(object):
if not buf:
break
yield buf
+
__iter__ = xreadlines
def readlines(self):
@@ -384,29 +385,36 @@ class QueryDict(MultiValueDict):
if not encoding:
encoding = settings.DEFAULT_CHARSET
self.encoding = encoding
- for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True
- self.appendlist(force_text(key, encoding, errors='replace'),
- force_text(value, encoding, errors='replace'))
+ if six.PY3:
+ for key, value in parse_qsl(query_string or '',
+ keep_blank_values=True,
+ encoding=encoding):
+ self.appendlist(key, value)
+ else:
+ for key, value in parse_qsl(query_string or '',
+ keep_blank_values=True):
+ self.appendlist(force_text(key, encoding, errors='replace'),
+ force_text(value, encoding, errors='replace'))
self._mutable = mutable
- def _get_encoding(self):
+ @property
+ def encoding(self):
if self._encoding is None:
self._encoding = settings.DEFAULT_CHARSET
return self._encoding
- def _set_encoding(self, value):
+ @encoding.setter
+ def encoding(self, value):
self._encoding = value
- encoding = property(_get_encoding, _set_encoding)
-
def _assert_mutable(self):
if not self._mutable:
raise AttributeError("This QueryDict instance is immutable")
def __setitem__(self, key, value):
self._assert_mutable()
- key = str_to_unicode(key, self.encoding)
- value = str_to_unicode(value, self.encoding)
+ key = bytes_to_text(key, self.encoding)
+ value = bytes_to_text(value, self.encoding)
super(QueryDict, self).__setitem__(key, value)
def __delitem__(self, key):
@@ -415,21 +423,21 @@ class QueryDict(MultiValueDict):
def __copy__(self):
result = self.__class__('', mutable=True, encoding=self.encoding)
- for key, value in self.iterlists():
+ for key, value in six.iterlists(self):
result.setlist(key, value)
return result
def __deepcopy__(self, memo):
result = self.__class__('', mutable=True, encoding=self.encoding)
memo[id(self)] = result
- for key, value in self.iterlists():
+ for key, value in six.iterlists(self):
result.setlist(copy.deepcopy(key, memo), copy.deepcopy(value, memo))
return result
def setlist(self, key, list_):
self._assert_mutable()
- key = str_to_unicode(key, self.encoding)
- list_ = [str_to_unicode(elt, self.encoding) for elt in list_]
+ key = bytes_to_text(key, self.encoding)
+ list_ = [bytes_to_text(elt, self.encoding) for elt in list_]
super(QueryDict, self).setlist(key, list_)
def setlistdefault(self, key, default_list=None):
@@ -438,8 +446,8 @@ class QueryDict(MultiValueDict):
def appendlist(self, key, value):
self._assert_mutable()
- key = str_to_unicode(key, self.encoding)
- value = str_to_unicode(value, self.encoding)
+ key = bytes_to_text(key, self.encoding)
+ value = bytes_to_text(value, self.encoding)
super(QueryDict, self).appendlist(key, value)
def pop(self, key, *args):
@@ -456,8 +464,8 @@ class QueryDict(MultiValueDict):
def setdefault(self, key, default=None):
self._assert_mutable()
- key = str_to_unicode(key, self.encoding)
- default = str_to_unicode(default, self.encoding)
+ key = bytes_to_text(key, self.encoding)
+ default = bytes_to_text(default, self.encoding)
return super(QueryDict, self).setdefault(key, default)
def copy(self):
@@ -481,13 +489,13 @@ class QueryDict(MultiValueDict):
"""
output = []
if safe:
- safe = smart_bytes(safe, self.encoding)
+ safe = force_bytes(safe, self.encoding)
encode = lambda k, v: '%s=%s' % ((quote(k, safe), quote(v, safe)))
else:
encode = lambda k, v: urlencode({k: v})
for k, list_ in self.lists():
- k = smart_bytes(k, self.encoding)
- output.extend([encode(k, smart_bytes(v, self.encoding))
+ k = force_bytes(k, self.encoding)
+ output.extend([encode(k, force_bytes(v, self.encoding))
for v in list_])
return '&'.join(output)
@@ -531,6 +539,7 @@ class HttpResponse(object):
if not content_type:
content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE,
self._charset)
+ # content is a bytestring. See the content property methods.
self.content = content
self.cookies = SimpleCookie()
if status:
@@ -538,30 +547,40 @@ class HttpResponse(object):
self['Content-Type'] = content_type
- def __str__(self):
- """Full HTTP message, including headers."""
- return '\n'.join(['%s: %s' % (key, value)
- for key, value in self._headers.values()]) \
- + '\n\n' + self.content
+ def serialize(self):
+ """Full HTTP message, including headers, as a bytestring."""
+ headers = [
+ ('%s: %s' % (key, value)).encode('us-ascii')
+ for key, value in self._headers.values()
+ ]
+ return b'\r\n'.join(headers) + b'\r\n\r\n' + self.content
+
+ if six.PY3:
+ __bytes__ = serialize
+ else:
+ __str__ = serialize
def _convert_to_ascii(self, *values):
"""Converts all values to ascii strings."""
for value in values:
- if isinstance(value, six.text_type):
- try:
- if not six.PY3:
- value = value.encode('us-ascii')
- else:
- # In Python 3, use a string in headers,
- # but ensure in only contains ASCII characters.
- value.encode('us-ascii')
- except UnicodeError as e:
- e.reason += ', HTTP response headers must be in US-ASCII format'
- raise
- else:
+ if not isinstance(value, six.string_types):
value = str(value)
+ try:
+ if six.PY3:
+ # Ensure string only contains ASCII
+ value.encode('us-ascii')
+ else:
+ if isinstance(value, str):
+ # Ensure string only contains ASCII
+ value.decode('us-ascii')
+ else:
+ # Convert unicode to an ASCII string
+ value = value.encode('us-ascii')
+ except UnicodeError as e:
+ e.reason += ', HTTP response headers must be in US-ASCII format'
+ raise
if '\n' in value or '\r' in value:
- raise BadHeaderError("Header values can't contain newlines (got %r)" % (value))
+ raise BadHeaderError("Header values can't contain newlines (got %r)" % value)
yield value
def __setitem__(self, header, value):
@@ -650,30 +669,40 @@ class HttpResponse(object):
self.set_cookie(key, max_age=0, path=path, domain=domain,
expires='Thu, 01-Jan-1970 00:00:00 GMT')
- def _get_content(self):
+ @property
+ def content(self):
if self.has_header('Content-Encoding'):
- return b''.join([str(e) for e in self._container])
- return b''.join([smart_bytes(e, self._charset) for e in self._container])
+ def make_bytes(value):
+ if isinstance(value, int):
+ value = six.text_type(value)
+ if isinstance(value, six.text_type):
+ value = value.encode('ascii')
+ # force conversion to bytes in case chunk is a subclass
+ return bytes(value)
+ return b''.join(make_bytes(e) for e in self._container)
+ return b''.join(force_bytes(e, self._charset) for e in self._container)
- def _set_content(self, value):
- if hasattr(value, '__iter__') and not isinstance(value, (bytes, six.text_type)):
+ @content.setter
+ def content(self, value):
+ if hasattr(value, '__iter__') and not isinstance(value, (bytes, six.string_types)):
self._container = value
self._base_content_is_iter = True
else:
self._container = [value]
self._base_content_is_iter = False
- content = property(_get_content, _set_content)
-
def __iter__(self):
self._iterator = iter(self._container)
return self
def __next__(self):
chunk = next(self._iterator)
+ if isinstance(chunk, int):
+ chunk = six.text_type(chunk)
if isinstance(chunk, six.text_type):
chunk = chunk.encode(self._charset)
- return str(chunk)
+ # force conversion to bytes in case chunk is a subclass
+ return bytes(chunk)
next = __next__ # Python 2 compatibility
@@ -699,11 +728,11 @@ class HttpResponse(object):
class HttpResponseRedirectBase(HttpResponse):
allowed_schemes = ['http', 'https', 'ftp']
- def __init__(self, redirect_to):
+ def __init__(self, redirect_to, *args, **kwargs):
parsed = urlparse(redirect_to)
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
raise SuspiciousOperation("Unsafe redirect to URL with protocol '%s'" % parsed.scheme)
- super(HttpResponseRedirectBase, self).__init__()
+ super(HttpResponseRedirectBase, self).__init__(*args, **kwargs)
self['Location'] = iri_to_uri(redirect_to)
class HttpResponseRedirect(HttpResponseRedirectBase):
@@ -715,6 +744,16 @@ class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
class HttpResponseNotModified(HttpResponse):
status_code = 304
+ def __init__(self, *args, **kwargs):
+ super(HttpResponseNotModified, self).__init__(*args, **kwargs)
+ del self['content-type']
+
+ @HttpResponse.content.setter
+ def content(self, value):
+ if value:
+ raise AttributeError("You cannot set content to a 304 (Not Modified) response")
+ self._container = []
+
class HttpResponseBadRequest(HttpResponse):
status_code = 400
@@ -727,8 +766,8 @@ class HttpResponseForbidden(HttpResponse):
class HttpResponseNotAllowed(HttpResponse):
status_code = 405
- def __init__(self, permitted_methods):
- super(HttpResponseNotAllowed, self).__init__()
+ def __init__(self, permitted_methods, *args, **kwargs):
+ super(HttpResponseNotAllowed, self).__init__(*args, **kwargs)
self['Allow'] = ', '.join(permitted_methods)
class HttpResponseGone(HttpResponse):
@@ -743,8 +782,8 @@ def get_host(request):
# It's neither necessary nor appropriate to use
# django.utils.encoding.smart_text for parsing URLs and form inputs. Thus,
-# this slightly more restricted function.
-def str_to_unicode(s, encoding):
+# this slightly more restricted function, used by QueryDict.
+def bytes_to_text(s, encoding):
"""
Converts basestring objects to unicode, using the given encoding. Illegally
encoded input characters are replaced with Unicode "unknown" codepoint
diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
index 9c66827cbb..e76a4f4208 100644
--- a/django/http/multipartparser.py
+++ b/django/http/multipartparser.py
@@ -6,7 +6,9 @@ file upload handlers for processing.
"""
from __future__ import unicode_literals
+import base64
import cgi
+
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
from django.utils.datastructures import MultiValueDict
@@ -199,7 +201,7 @@ class MultiPartParser(object):
if transfer_encoding == 'base64':
# We only special-case base64 transfer encoding
try:
- chunk = str(chunk).decode('base64')
+ chunk = base64.b64decode(chunk)
except Exception as e:
# Since this is only a chunk, any error is an unfixable error.
raise MultiPartParserError("Could not decode base64 data: %r" % e)
@@ -507,9 +509,11 @@ class BoundaryIter(object):
end = index
next = index + len(self._boundary)
# backup over CRLF
- if data[max(0,end-1)] == b'\n':
+ last = max(0, end-1)
+ if data[last:last+1] == b'\n':
end -= 1
- if data[max(0,end-1)] == b'\r':
+ last = max(0, end-1)
+ if data[last:last+1] == b'\r':
end -= 1
return end, next
@@ -613,7 +617,7 @@ def parse_header(line):
if i >= 0:
name = p[:i].strip().lower().decode('ascii')
value = p[i+1:].strip()
- if len(value) >= 2 and value[0] == value[-1] == b'"':
+ if len(value) >= 2 and value[:1] == value[-1:] == b'"':
value = value[1:-1]
value = value.replace(b'\\\\', b'\\').replace(b'\\"', b'"')
pdict[name] = value
diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py
index fd8ff30303..305b20e1c4 100644
--- a/django/middleware/csrf.py
+++ b/django/middleware/csrf.py
@@ -4,6 +4,7 @@ Cross Site Request Forgery Middleware.
This module provides a middleware that implements protection
against request forgeries from other sites.
"""
+from __future__ import unicode_literals
import hashlib
import re
@@ -12,6 +13,7 @@ import random
from django.conf import settings
from django.core.urlresolvers import get_callable
from django.utils.cache import patch_vary_headers
+from django.utils.encoding import force_text
from django.utils.http import same_origin
from django.utils.log import getLogger
from django.utils.crypto import constant_time_compare, get_random_string
@@ -51,11 +53,10 @@ def get_token(request):
def _sanitize_token(token):
- # Allow only alphanum, and ensure we return a 'str' for the sake
- # of the post processing middleware.
+ # Allow only alphanum
if len(token) > CSRF_KEY_LENGTH:
return _get_new_csrf_key()
- token = re.sub('[^a-zA-Z0-9]+', '', str(token.decode('ascii', 'ignore')))
+ token = re.sub('[^a-zA-Z0-9]+', '', force_text(token))
if token == "":
# In case the cookie has been truncated to nothing at some point.
return _get_new_csrf_key()
diff --git a/django/template/base.py b/django/template/base.py
index 661d8c092a..0a2b2c9437 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -11,7 +11,7 @@ from django.utils.importlib import import_module
from django.utils.itercompat import is_iterable
from django.utils.text import (smart_split, unescape_string_literal,
get_text_list)
-from django.utils.encoding import smart_text, force_text, smart_bytes
+from django.utils.encoding import force_str, force_text
from django.utils.translation import ugettext_lazy, pgettext_lazy
from django.utils.safestring import (SafeData, EscapeData, mark_safe,
mark_for_escaping)
@@ -20,6 +20,7 @@ from django.utils.html import escape
from django.utils.module_loading import module_has_submodule
from django.utils import six
from django.utils.timezone import template_localtime
+from django.utils.encoding import python_2_unicode_compatible
TOKEN_TEXT = 0
@@ -79,6 +80,7 @@ class TemplateDoesNotExist(Exception):
class TemplateEncodingError(Exception):
pass
+@python_2_unicode_compatible
class VariableDoesNotExist(Exception):
def __init__(self, msg, params=()):
@@ -86,9 +88,6 @@ class VariableDoesNotExist(Exception):
self.params = params
def __str__(self):
- return six.text_type(self).encode('utf-8')
-
- def __unicode__(self):
return self.msg % tuple([force_text(p, errors='replace')
for p in self.params])
@@ -117,7 +116,7 @@ class Template(object):
def __init__(self, template_string, origin=None,
name='<Unknown Template>'):
try:
- template_string = smart_text(template_string)
+ template_string = force_text(template_string)
except UnicodeDecodeError:
raise TemplateEncodingError("Templates can only be constructed "
"from unicode or UTF-8 strings.")
@@ -849,7 +848,7 @@ class TextNode(Node):
self.s = s
def __repr__(self):
- return "<Text Node: '%s'>" % smart_bytes(self.s[:25], 'ascii',
+ return force_str("<Text Node: '%s'>" % self.s[:25], 'ascii',
errors='replace')
def render(self, context):
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 16801fd573..1bfb627023 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -231,12 +231,12 @@ def make_list(value):
@stringfilter
def slugify(value):
"""
- Normalizes string, converts to lowercase, removes non-alpha characters,
- and converts spaces to hyphens.
+ Converts to lowercase, removes non-word characters (alphanumerics and
+ underscores) and converts spaces to hyphens. Also strips leading and
+ trailing whitespace.
"""
- value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
- value = six.text_type(re.sub('[^\w\s-]', '', value).strip().lower())
- return mark_safe(re.sub('[-\s]+', '-', value))
+ from django.utils.text import slugify
+ return slugify(value)
@register.filter(is_safe=True)
def stringformat(value, arg):
@@ -468,13 +468,8 @@ def safeseq(value):
@stringfilter
def removetags(value, tags):
"""Removes a space separated list of [X]HTML tags from the output."""
- tags = [re.escape(tag) for tag in tags.split()]
- tags_re = '(%s)' % '|'.join(tags)
- starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
- endtag_re = re.compile('</%s>' % tags_re)
- value = starttag_re.sub('', value)
- value = endtag_re.sub('', value)
- return value
+ from django.utils.html import remove_tags
+ return remove_tags(value, tags)
@register.filter(is_safe=True)
@stringfilter
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 14391f08e1..cb2ecd26d8 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -448,7 +448,7 @@ class WidthRatioNode(Node):
max_width = int(self.max_width.resolve(context))
except VariableDoesNotExist:
return ''
- except ValueError:
+ except (ValueError, TypeError):
raise TemplateSyntaxError("widthratio final argument must be an number")
try:
value = float(value)
@@ -456,7 +456,7 @@ class WidthRatioNode(Node):
ratio = (value / max_value) * max_width
except ZeroDivisionError:
return '0'
- except ValueError:
+ except (ValueError, TypeError):
return ''
return str(int(round(ratio)))
diff --git a/django/template/loaders/eggs.py b/django/template/loaders/eggs.py
index 42f87a459e..7e35b34ec4 100644
--- a/django/template/loaders/eggs.py
+++ b/django/template/loaders/eggs.py
@@ -1,13 +1,15 @@
# Wrapper for loading templates from eggs via pkg_resources.resource_string.
+from __future__ import unicode_literals
try:
from pkg_resources import resource_string
except ImportError:
resource_string = None
+from django.conf import settings
from django.template.base import TemplateDoesNotExist
from django.template.loader import BaseLoader
-from django.conf import settings
+from django.utils import six
class Loader(BaseLoader):
is_usable = resource_string is not None
@@ -22,9 +24,12 @@ class Loader(BaseLoader):
pkg_name = 'templates/' + template_name
for app in settings.INSTALLED_APPS:
try:
- return (resource_string(app, pkg_name).decode(settings.FILE_CHARSET), 'egg:%s:%s' % (app, pkg_name))
- except:
- pass
+ resource = resource_string(app, pkg_name)
+ except Exception:
+ continue
+ if not six.PY3:
+ resource = resource.decode(settings.FILE_CHARSET)
+ return (resource, 'egg:%s:%s' % (app, pkg_name))
raise TemplateDoesNotExist(template_name)
_loader = Loader()
diff --git a/django/template/response.py b/django/template/response.py
index 800e060c74..2cb44d127d 100644
--- a/django/template/response.py
+++ b/django/template/response.py
@@ -102,7 +102,7 @@ class SimpleTemplateResponse(HttpResponse):
"""
retval = self
if not self._is_rendered:
- self._set_content(self.rendered_content)
+ self.content = self.rendered_content
for post_callback in self._post_render_callbacks:
newretval = post_callback(retval)
if newretval is not None:
@@ -119,20 +119,20 @@ class SimpleTemplateResponse(HttpResponse):
'rendered before it can be iterated over.')
return super(SimpleTemplateResponse, self).__iter__()
- def _get_content(self):
+ @property
+ def content(self):
if not self._is_rendered:
raise ContentNotRenderedError('The response content must be '
'rendered before it can be accessed.')
- return super(SimpleTemplateResponse, self)._get_content()
+ return super(SimpleTemplateResponse, self).content
- def _set_content(self, value):
+ @content.setter
+ def content(self, value):
"""Sets the content for the response
"""
- super(SimpleTemplateResponse, self)._set_content(value)
+ HttpResponse.content.fset(self, value)
self._is_rendered = True
- content = property(_get_content, _set_content)
-
class TemplateResponse(SimpleTemplateResponse):
rendering_attrs = SimpleTemplateResponse.rendering_attrs + \
diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py
index 056eadc7de..36db4807c7 100644
--- a/django/templatetags/cache.py
+++ b/django/templatetags/cache.py
@@ -4,6 +4,7 @@ import hashlib
from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
from django.template import resolve_variable
from django.core.cache import cache
+from django.utils.encoding import force_bytes
from django.utils.http import urlquote
register = Library()
@@ -24,8 +25,9 @@ class CacheNode(Node):
expire_time = int(expire_time)
except (ValueError, TypeError):
raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
- # Build a unicode key for this fragment and all vary-on's.
- args = hashlib.md5(':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on]))
+ # Build a key for this fragment and all vary-on's.
+ key = ':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on])
+ args = hashlib.md5(force_bytes(key))
cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest())
value = cache.get(cache_key)
if value is None:
diff --git a/django/test/client.py b/django/test/client.py
index a9ae7f5db1..8fd765ec9a 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
import sys
import os
import re
@@ -19,7 +21,7 @@ from django.http import SimpleCookie, HttpRequest, QueryDict
from django.template import TemplateDoesNotExist
from django.test import signals
from django.utils.functional import curry
-from django.utils.encoding import smart_bytes
+from django.utils.encoding import force_bytes
from django.utils.http import urlencode
from django.utils.importlib import import_module
from django.utils.itercompat import is_iterable
@@ -108,7 +110,7 @@ def encode_multipart(boundary, data):
as an application/octet-stream; otherwise, str(value) will be sent.
"""
lines = []
- to_str = lambda s: smart_bytes(s, settings.DEFAULT_CHARSET)
+ to_bytes = lambda s: force_bytes(s, settings.DEFAULT_CHARSET)
# Not by any means perfect, but good enough for our purposes.
is_file = lambda thing: hasattr(thing, "read") and callable(thing.read)
@@ -124,37 +126,37 @@ def encode_multipart(boundary, data):
if is_file(item):
lines.extend(encode_file(boundary, key, item))
else:
- lines.extend([
- '--' + boundary,
- 'Content-Disposition: form-data; name="%s"' % to_str(key),
+ lines.extend([to_bytes(val) for val in [
+ '--%s' % boundary,
+ 'Content-Disposition: form-data; name="%s"' % key,
'',
- to_str(item)
- ])
+ item
+ ]])
else:
- lines.extend([
- '--' + boundary,
- 'Content-Disposition: form-data; name="%s"' % to_str(key),
+ lines.extend([to_bytes(val) for val in [
+ '--%s' % boundary,
+ 'Content-Disposition: form-data; name="%s"' % key,
'',
- to_str(value)
- ])
+ value
+ ]])
lines.extend([
- '--' + boundary + '--',
- '',
+ to_bytes('--%s--' % boundary),
+ b'',
])
- return '\r\n'.join(lines)
+ return b'\r\n'.join(lines)
def encode_file(boundary, key, file):
- to_str = lambda s: smart_bytes(s, settings.DEFAULT_CHARSET)
+ to_bytes = lambda s: force_bytes(s, settings.DEFAULT_CHARSET)
content_type = mimetypes.guess_type(file.name)[0]
if content_type is None:
content_type = 'application/octet-stream'
return [
- '--' + to_str(boundary),
- 'Content-Disposition: form-data; name="%s"; filename="%s"' \
- % (to_str(key), to_str(os.path.basename(file.name))),
- 'Content-Type: %s' % content_type,
- '',
+ to_bytes('--%s' % boundary),
+ to_bytes('Content-Disposition: form-data; name="%s"; filename="%s"' \
+ % (key, os.path.basename(file.name))),
+ to_bytes('Content-Type: %s' % content_type),
+ b'',
file.read()
]
@@ -220,7 +222,7 @@ class RequestFactory(object):
charset = match.group(1)
else:
charset = settings.DEFAULT_CHARSET
- return smart_bytes(data, encoding=charset)
+ return force_bytes(data, encoding=charset)
def _get_path(self, parsed):
# If there are parameters, add them
@@ -291,7 +293,7 @@ class RequestFactory(object):
def generic(self, method, path,
data='', content_type='application/octet-stream', **extra):
parsed = urlparse(path)
- data = smart_bytes(data, settings.DEFAULT_CHARSET)
+ data = force_bytes(data, settings.DEFAULT_CHARSET)
r = {
'PATH_INFO': self._get_path(parsed),
'QUERY_STRING': parsed[4],
@@ -385,7 +387,7 @@ class Client(RequestFactory):
if self.exc_info:
exc_info = self.exc_info
self.exc_info = None
- six.reraise(exc_info[1], None, exc_info[2])
+ six.reraise(*exc_info)
# Save the client and request that stimulated the response.
response.client = self
diff --git a/django/test/html.py b/django/test/html.py
index acdb4ffd14..274810cab4 100644
--- a/django/test/html.py
+++ b/django/test/html.py
@@ -8,6 +8,7 @@ import re
from django.utils.encoding import force_text
from django.utils.html_parser import HTMLParser, HTMLParseError
from django.utils import six
+from django.utils.encoding import python_2_unicode_compatible
WHITESPACE = re.compile('\s+')
@@ -17,6 +18,7 @@ def normalize_whitespace(string):
return WHITESPACE.sub(' ', string)
+@python_2_unicode_compatible
class Element(object):
def __init__(self, name, attributes):
self.name = name
@@ -117,7 +119,7 @@ class Element(object):
def __getitem__(self, key):
return self.children[key]
- def __unicode__(self):
+ def __str__(self):
output = '<%s' % self.name
for key, value in self.attributes:
if value:
@@ -136,11 +138,12 @@ class Element(object):
return six.text_type(self)
+@python_2_unicode_compatible
class RootElement(Element):
def __init__(self):
super(RootElement, self).__init__(None, ())
- def __unicode__(self):
+ def __str__(self):
return ''.join(six.text_type(c) for c in self.children)
diff --git a/django/test/signals.py b/django/test/signals.py
index 052b7dfa5c..5b0a9a19ca 100644
--- a/django/test/signals.py
+++ b/django/test/signals.py
@@ -4,7 +4,6 @@ import time
from django.conf import settings
from django.db import connections
from django.dispatch import receiver, Signal
-from django.template import context
from django.utils import timezone
template_rendered = Signal(providing_args=["template", "context"])
@@ -48,10 +47,18 @@ def update_connections_time_zone(**kwargs):
@receiver(setting_changed)
def clear_context_processors_cache(**kwargs):
if kwargs['setting'] == 'TEMPLATE_CONTEXT_PROCESSORS':
+ from django.template import context
context._standard_context_processors = None
@receiver(setting_changed)
+def clear_serializers_cache(**kwargs):
+ if kwargs['setting'] == 'SERIALIZATION_MODULES':
+ from django.core import serializers
+ serializers._serializers = {}
+
+
+@receiver(setting_changed)
def language_changed(**kwargs):
if kwargs['setting'] in ('LOCALE_PATHS', 'LANGUAGE_CODE'):
from django.utils.translation import trans_real
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 56ba56caf1..f12c431d3a 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -41,7 +41,7 @@ from django.test.utils import (get_warnings_state, restore_warnings_state,
override_settings)
from django.test.utils import ContextList
from django.utils import unittest as ut2
-from django.utils.encoding import smart_bytes, force_text
+from django.utils.encoding import force_text
from django.utils import six
from django.utils.unittest.util import safe_repr
from django.views.static import serve
@@ -647,14 +647,13 @@ class TransactionTestCase(SimpleTestCase):
self.assertEqual(response.status_code, status_code,
msg_prefix + "Couldn't retrieve content: Response code was %d"
" (expected %d)" % (response.status_code, status_code))
- enc_text = smart_bytes(text, response._charset)
- content = response.content
+ content = response.content.decode(response._charset)
if html:
content = assert_and_parse_html(self, content, None,
"Response's content is not valid HTML:")
- enc_text = assert_and_parse_html(self, enc_text, None,
+ text = assert_and_parse_html(self, text, None,
"Second argument is not valid HTML:")
- real_count = content.count(enc_text)
+ real_count = content.count(text)
if count is not None:
self.assertEqual(real_count, count,
msg_prefix + "Found %d instances of '%s' in response"
@@ -683,14 +682,13 @@ class TransactionTestCase(SimpleTestCase):
self.assertEqual(response.status_code, status_code,
msg_prefix + "Couldn't retrieve content: Response code was %d"
" (expected %d)" % (response.status_code, status_code))
- enc_text = smart_bytes(text, response._charset)
- content = response.content
+ content = response.content.decode(response._charset)
if html:
content = assert_and_parse_html(self, content, None,
'Response\'s content is not valid HTML:')
- enc_text = assert_and_parse_html(self, enc_text, None,
+ text = assert_and_parse_html(self, text, None,
'Second argument is not valid HTML:')
- self.assertEqual(content.count(enc_text), 0,
+ self.assertEqual(content.count(text), 0,
msg_prefix + "Response should not contain '%s'" % text)
def assertFormError(self, response, form, field, errors, msg_prefix=''):
@@ -919,23 +917,26 @@ class QuietWSGIRequestHandler(WSGIRequestHandler):
pass
-class _ImprovedEvent(threading._Event):
- """
- Does the same as `threading.Event` except it overrides the wait() method
- with some code borrowed from Python 2.7 to return the set state of the
- event (see: http://hg.python.org/cpython/rev/b5aa8aa78c0f/). This allows
- to know whether the wait() method exited normally or because of the
- timeout. This class can be removed when Django supports only Python >= 2.7.
- """
+if sys.version_info >= (2, 7, 0):
+ _ImprovedEvent = threading._Event
+else:
+ class _ImprovedEvent(threading._Event):
+ """
+ Does the same as `threading.Event` except it overrides the wait() method
+ with some code borrowed from Python 2.7 to return the set state of the
+ event (see: http://hg.python.org/cpython/rev/b5aa8aa78c0f/). This allows
+ to know whether the wait() method exited normally or because of the
+ timeout. This class can be removed when Django supports only Python >= 2.7.
+ """
- def wait(self, timeout=None):
- self._Event__cond.acquire()
- try:
- if not self._Event__flag:
- self._Event__cond.wait(timeout)
- return self._Event__flag
- finally:
- self._Event__cond.release()
+ def wait(self, timeout=None):
+ self._Event__cond.acquire()
+ try:
+ if not self._Event__flag:
+ self._Event__cond.wait(timeout)
+ return self._Event__flag
+ finally:
+ self._Event__cond.release()
class StoppableWSGIServer(WSGIServer):
@@ -1136,7 +1137,7 @@ class LiveServerTestCase(TransactionTestCase):
host, port_ranges = specified_address.split(':')
for port_range in port_ranges.split(','):
# A port range can be of either form: '8000' or '8000-8010'.
- extremes = map(int, port_range.split('-'))
+ extremes = list(map(int, port_range.split('-')))
assert len(extremes) in [1, 2]
if len(extremes) == 1:
# Port range of the form '8000'
diff --git a/django/test/utils.py b/django/test/utils.py
index 4b121bdfb0..e2c439fd80 100644
--- a/django/test/utils.py
+++ b/django/test/utils.py
@@ -221,4 +221,4 @@ class override_settings(object):
setting=key, value=new_value)
def str_prefix(s):
- return s % {'_': 'u'}
+ return s % {'_': '' if six.PY3 else 'u'}
diff --git a/django/utils/2to3_fixes/__init__.py b/django/utils/2to3_fixes/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/utils/2to3_fixes/__init__.py
diff --git a/django/utils/2to3_fixes/fix_unicode.py b/django/utils/2to3_fixes/fix_unicode.py
new file mode 100644
index 0000000000..613734ca86
--- /dev/null
+++ b/django/utils/2to3_fixes/fix_unicode.py
@@ -0,0 +1,36 @@
+"""Fixer for __unicode__ methods.
+
+Uses the django.utils.encoding.python_2_unicode_compatible decorator.
+"""
+
+from __future__ import unicode_literals
+
+from lib2to3 import fixer_base
+from lib2to3.fixer_util import find_indentation, Name, syms, touch_import
+from lib2to3.pgen2 import token
+from lib2to3.pytree import Leaf, Node
+
+
+class FixUnicode(fixer_base.BaseFix):
+
+ BM_compatible = True
+ PATTERN = """
+ classdef< 'class' any+ ':'
+ suite< any*
+ funcdef< 'def' unifunc='__unicode__'
+ parameters< '(' NAME ')' > any+ >
+ any* > >
+ """
+
+ def transform(self, node, results):
+ unifunc = results["unifunc"]
+ strfunc = Name("__str__", prefix=unifunc.prefix)
+ unifunc.replace(strfunc)
+
+ klass = node.clone()
+ klass.prefix = '\n' + find_indentation(node)
+ decorator = Node(syms.decorator, [Leaf(token.AT, "@"), Name('python_2_unicode_compatible')])
+ decorated = Node(syms.decorated, [decorator, klass], prefix=node.prefix)
+ node.replace(decorated)
+
+ touch_import('django.utils.encoding', 'python_2_unicode_compatible', decorated)
diff --git a/django/utils/archive.py b/django/utils/archive.py
index 829b55dd28..0faf1fa781 100644
--- a/django/utils/archive.py
+++ b/django/utils/archive.py
@@ -46,7 +46,8 @@ def extract(path, to_path=''):
Unpack the tar or zip file at the specified path to the directory
specified by to_path.
"""
- Archive(path).extract(to_path)
+ with Archive(path) as archive:
+ archive.extract(to_path)
class Archive(object):
@@ -77,12 +78,21 @@ class Archive(object):
"Path not a recognized archive format: %s" % filename)
return cls
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.close()
+
def extract(self, to_path=''):
self._archive.extract(to_path)
def list(self):
self._archive.list()
+ def close(self):
+ self._archive.close()
+
class BaseArchive(object):
"""
@@ -161,6 +171,9 @@ class TarArchive(BaseArchive):
if extracted:
extracted.close()
+ def close(self):
+ self._archive.close()
+
class ZipArchive(BaseArchive):
@@ -189,6 +202,9 @@ class ZipArchive(BaseArchive):
with open(filename, 'wb') as outfile:
outfile.write(data)
+ def close(self):
+ self._archive.close()
+
extension_map = {
'.tar': TarArchive,
'.tar.bz2': TarArchive,
diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py
index b6c055383c..2daafedd85 100644
--- a/django/utils/autoreload.py
+++ b/django/utils/autoreload.py
@@ -31,7 +31,7 @@
import os, sys, time, signal
try:
- import thread
+ from django.utils.six.moves import _thread as thread
except ImportError:
from django.utils.six.moves import _dummy_thread as thread
@@ -54,7 +54,8 @@ _win = (sys.platform == "win32")
def code_changed():
global _mtimes, _win
- for filename in filter(lambda v: v, map(lambda m: getattr(m, "__file__", None), sys.modules.values())):
+ filenames = [getattr(m, "__file__", None) for m in sys.modules.values()]
+ for filename in filter(None, filenames):
if filename.endswith(".pyc") or filename.endswith(".pyo"):
filename = filename[:-1]
if filename.endswith("$py.class"):
diff --git a/django/utils/cache.py b/django/utils/cache.py
index 42b0de4ce6..91c4796988 100644
--- a/django/utils/cache.py
+++ b/django/utils/cache.py
@@ -16,6 +16,7 @@ cache keys to prevent delivery of wrong content.
An example: i18n middleware would need to distinguish caches by the
"Accept-language" header.
"""
+from __future__ import unicode_literals
import hashlib
import re
@@ -23,7 +24,7 @@ import time
from django.conf import settings
from django.core.cache import get_cache
-from django.utils.encoding import iri_to_uri, force_text
+from django.utils.encoding import iri_to_uri, force_bytes, force_text
from django.utils.http import http_date
from django.utils.timezone import get_current_timezone_name
from django.utils.translation import get_language
@@ -65,7 +66,7 @@ def patch_cache_control(response, **kwargs):
# max-age, use the minimum of the two ages. In practice this happens when
# a decorator and a piece of middleware both operate on a given view.
if 'max-age' in cc and 'max_age' in kwargs:
- kwargs['max_age'] = min(cc['max-age'], kwargs['max_age'])
+ kwargs['max_age'] = min(int(cc['max-age']), kwargs['max_age'])
# Allow overriding private caching and vice versa
if 'private' in cc and 'public' in kwargs:
@@ -170,7 +171,7 @@ def _i18n_cache_key_suffix(request, cache_key):
# User-defined tzinfo classes may return absolutely anything.
# Hence this paranoid conversion to create a valid cache key.
tz_name = force_text(get_current_timezone_name(), errors='ignore')
- cache_key += '.%s' % tz_name.encode('ascii', 'ignore').replace(' ', '_')
+ cache_key += '.%s' % tz_name.encode('ascii', 'ignore').decode('ascii').replace(' ', '_')
return cache_key
def _generate_cache_key(request, method, headerlist, key_prefix):
@@ -180,14 +181,14 @@ def _generate_cache_key(request, method, headerlist, key_prefix):
value = request.META.get(header, None)
if value is not None:
ctx.update(value)
- path = hashlib.md5(iri_to_uri(request.get_full_path()))
+ path = hashlib.md5(force_bytes(iri_to_uri(request.get_full_path())))
cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
key_prefix, method, path.hexdigest(), ctx.hexdigest())
return _i18n_cache_key_suffix(request, cache_key)
def _generate_cache_header_key(key_prefix, request):
"""Returns a cache key for the header cache."""
- path = hashlib.md5(iri_to_uri(request.get_full_path()))
+ path = hashlib.md5(force_bytes(iri_to_uri(request.get_full_path())))
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
key_prefix, path.hexdigest())
return _i18n_cache_key_suffix(request, cache_key)
diff --git a/django/utils/crypto.py b/django/utils/crypto.py
index 70a07e7fde..57bc60dc4f 100644
--- a/django/utils/crypto.py
+++ b/django/utils/crypto.py
@@ -23,7 +23,8 @@ except NotImplementedError:
using_sysrandom = False
from django.conf import settings
-from django.utils.encoding import smart_bytes
+from django.utils.encoding import force_bytes
+from django.utils import six
from django.utils.six.moves import xrange
@@ -50,7 +51,7 @@ def salted_hmac(key_salt, value, secret=None):
# line is redundant and could be replaced by key = key_salt + secret, since
# the hmac module does the same thing for keys longer than the block size.
# However, we need to ensure that we *always* do this.
- return hmac.new(key, msg=smart_bytes(value), digestmod=hashlib.sha1)
+ return hmac.new(key, msg=force_bytes(value), digestmod=hashlib.sha1)
def get_random_string(length=12,
@@ -88,8 +89,12 @@ def constant_time_compare(val1, val2):
if len(val1) != len(val2):
return False
result = 0
- for x, y in zip(val1, val2):
- result |= ord(x) ^ ord(y)
+ if six.PY3 and isinstance(val1, bytes) and isinstance(val2, bytes):
+ for x, y in zip(val1, val2):
+ result |= x ^ y
+ else:
+ for x, y in zip(val1, val2):
+ result |= ord(x) ^ ord(y)
return result == 0
@@ -142,8 +147,8 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None):
assert iterations > 0
if not digest:
digest = hashlib.sha256
- password = smart_bytes(password)
- salt = smart_bytes(salt)
+ password = force_bytes(password)
+ salt = force_bytes(salt)
hlen = digest().digest_size
if not dklen:
dklen = hlen
diff --git a/django/utils/dateparse.py b/django/utils/dateparse.py
index 032eb493b6..b4bd559a66 100644
--- a/django/utils/dateparse.py
+++ b/django/utils/dateparse.py
@@ -15,16 +15,16 @@ date_re = re.compile(
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$'
)
-datetime_re = re.compile(
- r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
- r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
+time_re = re.compile(
+ r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
- r'(?P<tzinfo>Z|[+-]\d{1,2}:\d{1,2})?$'
)
-time_re = re.compile(
- r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
+datetime_re = re.compile(
+ r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
+ r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
+ r'(?P<tzinfo>Z|[+-]\d{2}:?\d{2})?$'
)
def parse_date(value):
@@ -43,8 +43,6 @@ def parse_time(value):
This function doesn't support time zone offsets.
- Sub-microsecond precision is accepted, but ignored.
-
Raises ValueError if the input is well formatted but not a valid time.
Returns None if the input isn't well formatted, in particular if it
contains an offset.
@@ -63,8 +61,6 @@ def parse_datetime(value):
This function supports time zone offsets. When the input contains one,
the output uses an instance of FixedOffset as tzinfo.
- Sub-microsecond precision is accepted, but ignored.
-
Raises ValueError if the input is well formatted but not a valid datetime.
Returns None if the input isn't well formatted.
"""
@@ -77,7 +73,7 @@ def parse_datetime(value):
if tzinfo == 'Z':
tzinfo = utc
elif tzinfo is not None:
- offset = 60 * int(tzinfo[1:3]) + int(tzinfo[4:6])
+ offset = 60 * int(tzinfo[1:3]) + int(tzinfo[-2:])
if tzinfo[0] == '-':
offset = -offset
tzinfo = FixedOffset(offset)
diff --git a/django/utils/encoding.py b/django/utils/encoding.py
index eb60cfde8b..3b284f3ed0 100644
--- a/django/utils/encoding.py
+++ b/django/utils/encoding.py
@@ -8,6 +8,7 @@ try:
from urllib.parse import quote
except ImportError: # Python 2
from urllib import quote
+import warnings
from django.utils.functional import Promise
from django.utils import six
@@ -32,6 +33,12 @@ class StrAndUnicode(object):
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__.
"""
+ def __init__(self, *args, **kwargs):
+ warnings.warn("StrAndUnicode is deprecated. Define a __str__ method "
+ "and apply the @python_2_unicode_compatible decorator "
+ "instead.", PendingDeprecationWarning, stacklevel=2)
+ super(StrAndUnicode, self).__init__(*args, **kwargs)
+
if six.PY3:
def __str__(self):
return self.__unicode__()
@@ -39,6 +46,19 @@ class StrAndUnicode(object):
def __str__(self):
return self.__unicode__().encode('utf-8')
+def python_2_unicode_compatible(klass):
+ """
+ A decorator that defines __unicode__ and __str__ methods under Python 2.
+ Under Python 3 it does nothing.
+
+ To support Python 2 and 3 with a single code base, define a __str__ method
+ returning text and apply this decorator to the class.
+ """
+ if not six.PY3:
+ klass.__unicode__ = klass.__str__
+ klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+ return klass
+
def smart_text(s, encoding='utf-8', strings_only=False, errors='strict'):
"""
Returns a text object representing 's' -- unicode on Python 2 and str on
@@ -99,8 +119,8 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'):
errors) for arg in s])
else:
# Note: We use .decode() here, instead of six.text_type(s, encoding,
- # errors), so that if s is a SafeString, it ends up being a
- # SafeUnicode at the end.
+ # errors), so that if s is a SafeBytes, it ends up being a
+ # SafeText at the end.
s = s.decode(encoding, errors)
except UnicodeDecodeError as e:
if not isinstance(s, Exception):
@@ -121,6 +141,19 @@ def smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
If strings_only is True, don't convert (some) non-string-like objects.
"""
+ if isinstance(s, Promise):
+ # The input is the result of a gettext_lazy() call.
+ return s
+ return force_bytes(s, encoding, strings_only, errors)
+
+
+def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
+ """
+ Similar to smart_bytes, 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.
+ """
if isinstance(s, bytes):
if encoding == 'utf-8':
return s
@@ -141,7 +174,7 @@ def smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
# An Exception subclass containing non-ASCII data that doesn't
# know how to print itself properly. We shouldn't raise a
# further exception.
- return ' '.join([smart_bytes(arg, encoding, strings_only,
+ return b' '.join([force_bytes(arg, encoding, strings_only,
errors) for arg in s])
return six.text_type(s).encode(encoding, errors)
else:
@@ -149,8 +182,10 @@ def smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
if six.PY3:
smart_str = smart_text
+ force_str = force_text
else:
smart_str = smart_bytes
+ force_str = force_bytes
# backwards compatibility for Python 2
smart_unicode = smart_text
force_unicode = force_text
@@ -161,6 +196,10 @@ Apply smart_text in Python 3 and smart_bytes in Python 2.
This is suitable for writing to sys.stdout (for instance).
"""
+force_str.__doc__ = """\
+Apply force_text in Python 3 and force_bytes in Python 2.
+"""
+
def iri_to_uri(iri):
"""
Convert an Internationalized Resource Identifier (IRI) portion to a URI
@@ -186,7 +225,7 @@ def iri_to_uri(iri):
# converted.
if iri is None:
return iri
- return quote(smart_bytes(iri), safe=b"/#%[]=:;$&()+,!?*@'~")
+ return quote(force_bytes(iri), safe=b"/#%[]=:;$&()+,!?*@'~")
def filepath_to_uri(path):
"""Convert an file system path to a URI portion that is suitable for
@@ -205,7 +244,7 @@ def filepath_to_uri(path):
return path
# I know about `os.sep` and `os.altsep` but I want to leave
# some flexibility for hardcoding separators.
- return quote(smart_bytes(path).replace("\\", "/"), safe=b"/~!*()'")
+ return quote(force_bytes(path.replace("\\", "/")), safe=b"/~!*()'")
# The encoding of the default system locale but falls back to the
# given fallback encoding if the encoding is unsupported by python or could
diff --git a/django/utils/formats.py b/django/utils/formats.py
index 2e54d792fa..555982eede 100644
--- a/django/utils/formats.py
+++ b/django/utils/formats.py
@@ -4,7 +4,7 @@ import datetime
from django.conf import settings
from django.utils import dateformat, numberformat, datetime_safe
from django.utils.importlib import import_module
-from django.utils.encoding import smart_str
+from django.utils.encoding import force_str
from django.utils.functional import lazy
from django.utils.safestring import mark_safe
from django.utils import six
@@ -66,7 +66,7 @@ def get_format(format_type, lang=None, use_l10n=None):
If use_l10n is provided and is not None, that will force the value to
be localized (or not), overriding the value of settings.USE_L10N.
"""
- format_type = smart_str(format_type)
+ format_type = force_str(format_type)
if use_l10n or (use_l10n is None and settings.USE_L10N):
if lang is None:
lang = get_language()
@@ -160,14 +160,14 @@ def localize_input(value, default=None):
return number_format(value)
elif isinstance(value, datetime.datetime):
value = datetime_safe.new_datetime(value)
- format = smart_str(default or get_format('DATETIME_INPUT_FORMATS')[0])
+ format = force_str(default or get_format('DATETIME_INPUT_FORMATS')[0])
return value.strftime(format)
elif isinstance(value, datetime.date):
value = datetime_safe.new_date(value)
- format = smart_str(default or get_format('DATE_INPUT_FORMATS')[0])
+ format = force_str(default or get_format('DATE_INPUT_FORMATS')[0])
return value.strftime(format)
elif isinstance(value, datetime.time):
- format = smart_str(default or get_format('TIME_INPUT_FORMATS')[0])
+ format = force_str(default or get_format('TIME_INPUT_FORMATS')[0])
return value.strftime(format)
return value
diff --git a/django/utils/functional.py b/django/utils/functional.py
index 085ec40b63..085a8fce59 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -238,7 +238,6 @@ class LazyObject(object):
raise NotImplementedError
# introspection support:
- __members__ = property(lambda self: self.__dir__())
__dir__ = new_method_proxy(dir)
diff --git a/django/utils/html.py b/django/utils/html.py
index 7dd53a1353..2b669cc8ec 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -11,7 +11,7 @@ except ImportError: # Python 2
from urlparse import urlsplit, urlunsplit
from django.utils.safestring import SafeData, mark_safe
-from django.utils.encoding import smart_bytes, force_text
+from django.utils.encoding import force_bytes, force_text
from django.utils.functional import allow_lazy
from django.utils import six
from django.utils.text import normalize_newlines
@@ -123,6 +123,17 @@ def strip_tags(value):
return re.sub(r'<[^>]*?>', '', force_text(value))
strip_tags = allow_lazy(strip_tags)
+def remove_tags(html, tags):
+ """Returns the given HTML with given tags removed."""
+ tags = [re.escape(tag) for tag in tags.split()]
+ tags_re = '(%s)' % '|'.join(tags)
+ starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
+ endtag_re = re.compile('</%s>' % tags_re)
+ html = starttag_re.sub('', html)
+ html = endtag_re.sub('', html)
+ return html
+remove_tags = allow_lazy(remove_tags, six.text_type)
+
def strip_spaces_between_tags(value):
"""Returns the given HTML with spaces between tags removed."""
return re.sub(r'>\s+<', '><', force_text(value))
@@ -143,7 +154,7 @@ def smart_urlquote(url):
# Handle IDN before quoting.
scheme, netloc, path, query, fragment = urlsplit(url)
try:
- netloc = netloc.encode('idna') # IDN -> ACE
+ netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
except UnicodeError: # invalid domain part
pass
else:
@@ -153,7 +164,7 @@ def smart_urlquote(url):
# contains a % not followed by two hexadecimal digits. See #9655.
if '%' not in url or unquoted_percents_re.search(url):
# See http://bugs.python.org/issue2637
- url = quote(smart_bytes(url), safe=b'!*\'();:@&=+$,/?#[]~')
+ url = quote(force_bytes(url), safe=b'!*\'();:@&=+$,/?#[]~')
return force_text(url)
@@ -206,7 +217,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
elif not ':' in middle and simple_email_re.match(middle):
local, domain = middle.rsplit('@', 1)
try:
- domain = domain.encode('idna')
+ domain = domain.encode('idna').decode('ascii')
except UnicodeError:
continue
url = 'mailto:%s@%s' % (local, domain)
diff --git a/django/utils/html_parser.py b/django/utils/html_parser.py
index ee56c01aec..d7311f253b 100644
--- a/django/utils/html_parser.py
+++ b/django/utils/html_parser.py
@@ -1,102 +1,114 @@
from django.utils.six.moves import html_parser as _html_parser
import re
+import sys
-tagfind = re.compile('([a-zA-Z][-.a-zA-Z0-9:_]*)(?:\s|/(?!>))*')
+current_version = sys.version_info
+
+use_workaround = (
+ (current_version < (2, 6, 8)) or
+ (current_version >= (2, 7) and current_version < (2, 7, 3)) or
+ (current_version >= (3, 0) and current_version < (3, 2, 3))
+)
HTMLParseError = _html_parser.HTMLParseError
-class HTMLParser(_html_parser.HTMLParser):
- """
- Patched version of stdlib's HTMLParser with patch from:
- http://bugs.python.org/issue670664
- """
- def __init__(self):
- _html_parser.HTMLParser.__init__(self)
- self.cdata_tag = None
+if not use_workaround:
+ HTMLParser = _html_parser.HTMLParser
+else:
+ tagfind = re.compile('([a-zA-Z][-.a-zA-Z0-9:_]*)(?:\s|/(?!>))*')
- def set_cdata_mode(self, tag):
- try:
- self.interesting = _html_parser.interesting_cdata
- except AttributeError:
- self.interesting = re.compile(r'</\s*%s\s*>' % tag.lower(), re.I)
- self.cdata_tag = tag.lower()
+ class HTMLParser(_html_parser.HTMLParser):
+ """
+ Patched version of stdlib's HTMLParser with patch from:
+ http://bugs.python.org/issue670664
+ """
+ def __init__(self):
+ _html_parser.HTMLParser.__init__(self)
+ self.cdata_tag = None
- def clear_cdata_mode(self):
- self.interesting = _html_parser.interesting_normal
- self.cdata_tag = None
+ def set_cdata_mode(self, tag):
+ try:
+ self.interesting = _html_parser.interesting_cdata
+ except AttributeError:
+ self.interesting = re.compile(r'</\s*%s\s*>' % tag.lower(), re.I)
+ self.cdata_tag = tag.lower()
- # Internal -- handle starttag, return end or -1 if not terminated
- def parse_starttag(self, i):
- self.__starttag_text = None
- endpos = self.check_for_whole_start_tag(i)
- if endpos < 0:
- return endpos
- rawdata = self.rawdata
- self.__starttag_text = rawdata[i:endpos]
+ def clear_cdata_mode(self):
+ self.interesting = _html_parser.interesting_normal
+ self.cdata_tag = None
- # Now parse the data between i+1 and j into a tag and attrs
- attrs = []
- match = tagfind.match(rawdata, i + 1)
- assert match, 'unexpected call to parse_starttag()'
- k = match.end()
- self.lasttag = tag = match.group(1).lower()
+ # Internal -- handle starttag, return end or -1 if not terminated
+ def parse_starttag(self, i):
+ self.__starttag_text = None
+ endpos = self.check_for_whole_start_tag(i)
+ if endpos < 0:
+ return endpos
+ rawdata = self.rawdata
+ self.__starttag_text = rawdata[i:endpos]
- while k < endpos:
- m = _html_parser.attrfind.match(rawdata, k)
- if not m:
- break
- attrname, rest, attrvalue = m.group(1, 2, 3)
- if not rest:
- attrvalue = None
- elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
- attrvalue[:1] == '"' == attrvalue[-1:]:
- attrvalue = attrvalue[1:-1]
- if attrvalue:
- attrvalue = self.unescape(attrvalue)
- attrs.append((attrname.lower(), attrvalue))
- k = m.end()
+ # Now parse the data between i+1 and j into a tag and attrs
+ attrs = []
+ match = tagfind.match(rawdata, i + 1)
+ assert match, 'unexpected call to parse_starttag()'
+ k = match.end()
+ self.lasttag = tag = match.group(1).lower()
- end = rawdata[k:endpos].strip()
- if end not in (">", "/>"):
- lineno, offset = self.getpos()
- if "\n" in self.__starttag_text:
- lineno = lineno + self.__starttag_text.count("\n")
- offset = len(self.__starttag_text) \
- - self.__starttag_text.rfind("\n")
+ while k < endpos:
+ m = _html_parser.attrfind.match(rawdata, k)
+ if not m:
+ break
+ attrname, rest, attrvalue = m.group(1, 2, 3)
+ if not rest:
+ attrvalue = None
+ elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
+ attrvalue[:1] == '"' == attrvalue[-1:]:
+ attrvalue = attrvalue[1:-1]
+ if attrvalue:
+ attrvalue = self.unescape(attrvalue)
+ attrs.append((attrname.lower(), attrvalue))
+ k = m.end()
+
+ end = rawdata[k:endpos].strip()
+ if end not in (">", "/>"):
+ lineno, offset = self.getpos()
+ if "\n" in self.__starttag_text:
+ lineno = lineno + self.__starttag_text.count("\n")
+ offset = len(self.__starttag_text) \
+ - self.__starttag_text.rfind("\n")
+ else:
+ offset = offset + len(self.__starttag_text)
+ self.error("junk characters in start tag: %r"
+ % (rawdata[k:endpos][:20],))
+ if end.endswith('/>'):
+ # XHTML-style empty tag: <span attr="value" />
+ self.handle_startendtag(tag, attrs)
else:
- offset = offset + len(self.__starttag_text)
- self.error("junk characters in start tag: %r"
- % (rawdata[k:endpos][:20],))
- if end.endswith('/>'):
- # XHTML-style empty tag: <span attr="value" />
- self.handle_startendtag(tag, attrs)
- else:
- self.handle_starttag(tag, attrs)
- if tag in self.CDATA_CONTENT_ELEMENTS:
- self.set_cdata_mode(tag) # <--------------------------- Changed
- return endpos
+ self.handle_starttag(tag, attrs)
+ if tag in self.CDATA_CONTENT_ELEMENTS:
+ self.set_cdata_mode(tag) # <--------------------------- Changed
+ return endpos
- # Internal -- parse endtag, return end or -1 if incomplete
- def parse_endtag(self, i):
- rawdata = self.rawdata
- assert rawdata[i:i + 2] == "</", "unexpected call to parse_endtag"
- match = _html_parser.endendtag.search(rawdata, i + 1) # >
- if not match:
- return -1
- j = match.end()
- match = _html_parser.endtagfind.match(rawdata, i) # </ + tag + >
- if not match:
- if self.cdata_tag is not None: # *** add ***
- self.handle_data(rawdata[i:j]) # *** add ***
- return j # *** add ***
- self.error("bad end tag: %r" % (rawdata[i:j],))
- # --- changed start ---------------------------------------------------
- tag = match.group(1).strip()
- if self.cdata_tag is not None:
- if tag.lower() != self.cdata_tag:
- self.handle_data(rawdata[i:j])
- return j
- # --- changed end -----------------------------------------------------
- self.handle_endtag(tag.lower())
- self.clear_cdata_mode()
- return j
+ # Internal -- parse endtag, return end or -1 if incomplete
+ def parse_endtag(self, i):
+ rawdata = self.rawdata
+ assert rawdata[i:i + 2] == "</", "unexpected call to parse_endtag"
+ match = _html_parser.endendtag.search(rawdata, i + 1) # >
+ if not match:
+ return -1
+ j = match.end()
+ match = _html_parser.endtagfind.match(rawdata, i) # </ + tag + >
+ if not match:
+ if self.cdata_tag is not None: # *** add ***
+ self.handle_data(rawdata[i:j]) # *** add ***
+ return j # *** add ***
+ self.error("bad end tag: %r" % (rawdata[i:j],))
+ # --- changed start ---------------------------------------------------
+ tag = match.group(1).strip()
+ if self.cdata_tag is not None:
+ if tag.lower() != self.cdata_tag:
+ self.handle_data(rawdata[i:j])
+ return j
+ # --- changed end -----------------------------------------------------
+ self.handle_endtag(tag.lower())
+ self.clear_cdata_mode()
+ return j
diff --git a/django/utils/http.py b/django/utils/http.py
index 22e81a33d7..d3c70f1209 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
import calendar
import datetime
import re
@@ -13,7 +15,7 @@ except ImportError: # Python 2
from email.utils import formatdate
from django.utils.datastructures import MultiValueDict
-from django.utils.encoding import force_text, smart_str
+from django.utils.encoding import force_str, force_text
from django.utils.functional import allow_lazy
from django.utils import six
@@ -37,7 +39,7 @@ def urlquote(url, safe='/'):
can safely be used as part of an argument to a subsequent iri_to_uri() call
without double-quoting occurring.
"""
- return force_text(urllib_parse.quote(smart_str(url), smart_str(safe)))
+ return force_text(urllib_parse.quote(force_str(url), force_str(safe)))
urlquote = allow_lazy(urlquote, six.text_type)
def urlquote_plus(url, safe=''):
@@ -47,7 +49,7 @@ def urlquote_plus(url, safe=''):
returned string can safely be used as part of an argument to a subsequent
iri_to_uri() call without double-quoting occurring.
"""
- return force_text(urllib_parse.quote_plus(smart_str(url), smart_str(safe)))
+ return force_text(urllib_parse.quote_plus(force_str(url), force_str(safe)))
urlquote_plus = allow_lazy(urlquote_plus, six.text_type)
def urlunquote(quoted_url):
@@ -55,7 +57,7 @@ def urlunquote(quoted_url):
A wrapper for Python's urllib.unquote() function that can operate on
the result of django.utils.http.urlquote().
"""
- return force_text(urllib_parse.unquote(smart_str(quoted_url)))
+ return force_text(urllib_parse.unquote(force_str(quoted_url)))
urlunquote = allow_lazy(urlunquote, six.text_type)
def urlunquote_plus(quoted_url):
@@ -63,7 +65,7 @@ def urlunquote_plus(quoted_url):
A wrapper for Python's urllib.unquote_plus() function that can operate on
the result of django.utils.http.urlquote_plus().
"""
- return force_text(urllib_parse.unquote_plus(smart_str(quoted_url)))
+ return force_text(urllib_parse.unquote_plus(force_str(quoted_url)))
urlunquote_plus = allow_lazy(urlunquote_plus, six.text_type)
def urlencode(query, doseq=0):
@@ -77,8 +79,8 @@ def urlencode(query, doseq=0):
elif hasattr(query, 'items'):
query = query.items()
return urllib_parse.urlencode(
- [(smart_str(k),
- [smart_str(i) for i in v] if isinstance(v, (list,tuple)) else smart_str(v))
+ [(force_str(k),
+ [force_str(i) for i in v] if isinstance(v, (list,tuple)) else force_str(v))
for k, v in query],
doseq)
@@ -211,7 +213,7 @@ def parse_etags(etag_str):
if not etags:
# etag_str has wrong format, treat it as an opaque string then
return [etag_str]
- etags = [e.decode('string_escape') for e in etags]
+ etags = [e.encode('ascii').decode('unicode_escape') for e in etags]
return etags
def quote_etag(etag):
diff --git a/django/utils/ipv6.py b/django/utils/ipv6.py
index 2ccae8cdd0..7624bb9c76 100644
--- a/django/utils/ipv6.py
+++ b/django/utils/ipv6.py
@@ -263,6 +263,6 @@ def _is_shorthand_ip(ip_str):
"""
if ip_str.count('::') == 1:
return True
- if filter(lambda x: len(x) < 4, ip_str.split(':')):
+ if any(len(x) < 4 for x in ip_str.split(':')):
return True
return False
diff --git a/django/utils/safestring.py b/django/utils/safestring.py
index bfaefd07ee..07e0bf4cea 100644
--- a/django/utils/safestring.py
+++ b/django/utils/safestring.py
@@ -10,36 +10,43 @@ from django.utils import six
class EscapeData(object):
pass
-class EscapeString(bytes, EscapeData):
+class EscapeBytes(bytes, EscapeData):
"""
- A string that should be HTML-escaped when output.
+ A byte string that should be HTML-escaped when output.
"""
pass
-class EscapeUnicode(six.text_type, EscapeData):
+class EscapeText(six.text_type, EscapeData):
"""
- A unicode object that should be HTML-escaped when output.
+ A unicode string object that should be HTML-escaped when output.
"""
pass
+if six.PY3:
+ EscapeString = EscapeText
+else:
+ EscapeString = EscapeBytes
+ # backwards compatibility for Python 2
+ EscapeUnicode = EscapeText
+
class SafeData(object):
pass
-class SafeString(bytes, SafeData):
+class SafeBytes(bytes, SafeData):
"""
- A string subclass that has been specifically marked as "safe" (requires no
+ A bytes subclass that has been specifically marked as "safe" (requires no
further escaping) for HTML output purposes.
"""
def __add__(self, rhs):
"""
- Concatenating a safe string with another safe string or safe unicode
- object is safe. Otherwise, the result is no longer safe.
+ Concatenating a safe byte string with another safe byte string or safe
+ unicode string is safe. Otherwise, the result is no longer safe.
"""
- t = super(SafeString, self).__add__(rhs)
- if isinstance(rhs, SafeUnicode):
- return SafeUnicode(t)
- elif isinstance(rhs, SafeString):
- return SafeString(t)
+ t = super(SafeBytes, self).__add__(rhs)
+ if isinstance(rhs, SafeText):
+ return SafeText(t)
+ elif isinstance(rhs, SafeBytes):
+ return SafeBytes(t)
return t
def _proxy_method(self, *args, **kwargs):
@@ -51,25 +58,25 @@ class SafeString(bytes, SafeData):
method = kwargs.pop('method')
data = method(self, *args, **kwargs)
if isinstance(data, bytes):
- return SafeString(data)
+ return SafeBytes(data)
else:
- return SafeUnicode(data)
+ return SafeText(data)
decode = curry(_proxy_method, method=bytes.decode)
-class SafeUnicode(six.text_type, SafeData):
+class SafeText(six.text_type, SafeData):
"""
- A unicode subclass that has been specifically marked as "safe" for HTML
- output purposes.
+ A unicode (Python 2) / str (Python 3) subclass that has been specifically
+ marked as "safe" for HTML output purposes.
"""
def __add__(self, rhs):
"""
- Concatenating a safe unicode object with another safe string or safe
- unicode object is safe. Otherwise, the result is no longer safe.
+ Concatenating a safe unicode string with another safe byte string or
+ safe unicode string is safe. Otherwise, the result is no longer safe.
"""
- t = super(SafeUnicode, self).__add__(rhs)
+ t = super(SafeText, self).__add__(rhs)
if isinstance(rhs, SafeData):
- return SafeUnicode(t)
+ return SafeText(t)
return t
def _proxy_method(self, *args, **kwargs):
@@ -81,12 +88,19 @@ class SafeUnicode(six.text_type, SafeData):
method = kwargs.pop('method')
data = method(self, *args, **kwargs)
if isinstance(data, bytes):
- return SafeString(data)
+ return SafeBytes(data)
else:
- return SafeUnicode(data)
+ return SafeText(data)
encode = curry(_proxy_method, method=six.text_type.encode)
+if six.PY3:
+ SafeString = SafeText
+else:
+ SafeString = SafeBytes
+ # backwards compatibility for Python 2
+ SafeUnicode = SafeText
+
def mark_safe(s):
"""
Explicitly mark a string as safe for (HTML) output purposes. The returned
@@ -97,10 +111,10 @@ def mark_safe(s):
if isinstance(s, SafeData):
return s
if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes):
- return SafeString(s)
+ return SafeBytes(s)
if isinstance(s, (six.text_type, Promise)):
- return SafeUnicode(s)
- return SafeString(bytes(s))
+ return SafeText(s)
+ return SafeString(str(s))
def mark_for_escaping(s):
"""
@@ -113,8 +127,8 @@ def mark_for_escaping(s):
if isinstance(s, (SafeData, EscapeData)):
return s
if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes):
- return EscapeString(s)
+ return EscapeBytes(s)
if isinstance(s, (six.text_type, Promise)):
- return EscapeUnicode(s)
- return EscapeString(bytes(s))
+ return EscapeText(s)
+ return EscapeBytes(bytes(s))
diff --git a/django/utils/six.py b/django/utils/six.py
index ceb5d70e58..e4ce939844 100644
--- a/django/utils/six.py
+++ b/django/utils/six.py
@@ -5,7 +5,7 @@ import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
-__version__ = "1.1.0"
+__version__ = "1.2.0"
# True if we are running on Python 3.
@@ -26,19 +26,23 @@ else:
text_type = unicode
binary_type = str
- # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
- class X(object):
- def __len__(self):
- return 1 << 31
- try:
- len(X())
- except OverflowError:
- # 32-bit
+ if sys.platform == "java":
+ # Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
- # 64-bit
- MAXSIZE = int((1 << 63) - 1)
- del X
+ # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+ class X(object):
+ def __len__(self):
+ return 1 << 31
+ try:
+ len(X())
+ except OverflowError:
+ # 32-bit
+ MAXSIZE = int((1 << 31) - 1)
+ else:
+ # 64-bit
+ MAXSIZE = int((1 << 63) - 1)
+ del X
def _add_doc(func, doc):
@@ -201,12 +205,19 @@ else:
_iteritems = "iteritems"
+try:
+ advance_iterator = next
+except NameError:
+ def advance_iterator(it):
+ return it.next()
+next = advance_iterator
+
+
if PY3:
def get_unbound_function(unbound):
return unbound
-
- advance_iterator = next
+ Iterator = object
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
@@ -214,9 +225,10 @@ else:
def get_unbound_function(unbound):
return unbound.im_func
+ class Iterator(object):
- def advance_iterator(it):
- return it.next()
+ def next(self):
+ return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
@@ -231,15 +243,15 @@ get_function_defaults = operator.attrgetter(_func_defaults)
def iterkeys(d):
"""Return an iterator over the keys of a dictionary."""
- return getattr(d, _iterkeys)()
+ return iter(getattr(d, _iterkeys)())
def itervalues(d):
"""Return an iterator over the values of a dictionary."""
- return getattr(d, _itervalues)()
+ return iter(getattr(d, _itervalues)())
def iteritems(d):
"""Return an iterator over the (key, value) pairs of a dictionary."""
- return getattr(d, _iteritems)()
+ return iter(getattr(d, _iteritems)())
if PY3:
@@ -365,4 +377,6 @@ def iterlists(d):
"""Return an iterator over the values of a MultiValueDict."""
return getattr(d, _iterlists)()
+
add_move(MovedModule("_dummy_thread", "dummy_thread"))
+add_move(MovedModule("_thread", "thread"))
diff --git a/django/utils/text.py b/django/utils/text.py
index 0838d79c65..c19708458b 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -4,13 +4,19 @@ import re
import unicodedata
import warnings
from gzip import GzipFile
-from django.utils.six.moves import html_entities
from io import BytesIO
from django.utils.encoding import force_text
from django.utils.functional import allow_lazy, SimpleLazyObject
from django.utils import six
+from django.utils.six.moves import html_entities
from django.utils.translation import ugettext_lazy, ugettext as _, pgettext
+from django.utils.safestring import mark_safe
+
+if not six.PY3:
+ # Import force_unicode even though this module doesn't use it, because some
+ # people rely on it being here.
+ from django.utils.encoding import force_unicode
# Capitalizes the first letter of a string.
capfirst = lambda x: x and force_text(x)[0].upper() + force_text(x)[1:]
@@ -287,7 +293,7 @@ ustring_re = re.compile("([\u0080-\uffff])")
def javascript_quote(s, quote_double_quotes=False):
def fix(match):
- return b"\u%04x" % ord(match.group(1))
+ return "\\u%04x" % ord(match.group(1))
if type(s) == bytes:
s = s.decode('utf-8')
@@ -378,3 +384,14 @@ def unescape_string_literal(s):
quote = s[0]
return s[1:-1].replace(r'\%s' % quote, quote).replace(r'\\', '\\')
unescape_string_literal = allow_lazy(unescape_string_literal)
+
+def slugify(value):
+ """
+ Converts to lowercase, removes non-word characters (alphanumerics and
+ underscores) and converts spaces to hyphens. Also strips leading and
+ trailing whitespace.
+ """
+ value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
+ value = re.sub('[^\w\s-]', '', value).strip().lower()
+ return mark_safe(re.sub('[-\s]+', '-', value))
+slugify = allow_lazy(slugify, six.text_type)
diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py
index febde404a5..f3cc6348f6 100644
--- a/django/utils/translation/__init__.py
+++ b/django/utils/translation/__init__.py
@@ -79,10 +79,10 @@ def pgettext(context, message):
def npgettext(context, singular, plural, number):
return _trans.npgettext(context, singular, plural, number)
-ngettext_lazy = lazy(ngettext, bytes)
-gettext_lazy = lazy(gettext, bytes)
-ungettext_lazy = lazy(ungettext, six.text_type)
+gettext_lazy = lazy(gettext, str)
+ngettext_lazy = lazy(ngettext, str)
ugettext_lazy = lazy(ugettext, six.text_type)
+ungettext_lazy = lazy(ungettext, six.text_type)
pgettext_lazy = lazy(pgettext, six.text_type)
npgettext_lazy = lazy(npgettext, six.text_type)
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index 9e6eadcd48..9fd33a7ea8 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -9,7 +9,7 @@ import gettext as gettext_module
from threading import local
from django.utils.importlib import import_module
-from django.utils.encoding import smart_str, smart_text
+from django.utils.encoding import force_str, force_text
from django.utils.safestring import mark_safe, SafeData
from django.utils import six
from django.utils.six import StringIO
@@ -259,6 +259,11 @@ def do_translate(message, translation_function):
return result
def gettext(message):
+ """
+ Returns a string of the translation of the message.
+
+ Returns a string on Python 3 and an UTF-8-encoded bytestring on Python 2.
+ """
return do_translate(message, 'gettext')
if six.PY3:
@@ -296,8 +301,10 @@ def do_ntranslate(singular, plural, number, translation_function):
def ngettext(singular, plural, number):
"""
- Returns a UTF-8 bytestring of the translation of either the singular or
- plural, based on the number.
+ Returns a string of the translation of either the singular or plural,
+ based on the number.
+
+ Returns a string on Python 3 and an UTF-8-encoded bytestring on Python 2.
"""
return do_ntranslate(singular, plural, number, 'ngettext')
@@ -447,7 +454,7 @@ def templatize(src, origin=None):
from django.conf import settings
from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK,
TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK)
- src = smart_text(src, settings.FILE_CHARSET)
+ src = force_text(src, settings.FILE_CHARSET)
out = StringIO()
message_context = None
intrans = False
@@ -462,7 +469,7 @@ def templatize(src, origin=None):
content = ''.join(comment)
translators_comment_start = None
for lineno, line in enumerate(content.splitlines(True)):
- if line.lstrip().startswith(smart_text(TRANSLATOR_COMMENT_MARK)):
+ if line.lstrip().startswith(TRANSLATOR_COMMENT_MARK):
translators_comment_start = lineno
for lineno, line in enumerate(content.splitlines(True)):
if translators_comment_start is not None and lineno >= translators_comment_start:
@@ -577,7 +584,7 @@ def templatize(src, origin=None):
out.write(' # %s' % t.contents)
else:
out.write(blankout(t.contents, 'X'))
- return smart_str(out.getvalue())
+ return force_str(out.getvalue())
def parse_accept_lang_header(lang_string):
"""
diff --git a/django/utils/tzinfo.py b/django/utils/tzinfo.py
index 208b7e7191..654c99778e 100644
--- a/django/utils/tzinfo.py
+++ b/django/utils/tzinfo.py
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import time
from datetime import timedelta, tzinfo
-from django.utils.encoding import smart_text, smart_str, DEFAULT_LOCALE_ENCODING
+from django.utils.encoding import force_str, force_text, DEFAULT_LOCALE_ENCODING
# Python's doc say: "A tzinfo subclass must have an __init__() method that can
# be called with no arguments". FixedOffset and LocalTimezone don't honor this
@@ -53,7 +53,7 @@ class LocalTimezone(tzinfo):
self._tzname = self.tzname(dt)
def __repr__(self):
- return smart_str(self._tzname)
+ return force_str(self._tzname)
def __getinitargs__(self):
return self.__dt,
@@ -72,7 +72,7 @@ class LocalTimezone(tzinfo):
def tzname(self, dt):
try:
- return smart_text(time.tzname[self._isdst(dt)],
+ return force_text(time.tzname[self._isdst(dt)],
DEFAULT_LOCALE_ENCODING)
except UnicodeDecodeError:
return None
diff --git a/django/views/debug.py b/django/views/debug.py
index b275ef9e73..ed99d8dfe6 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -14,7 +14,7 @@ from django.template import Template, Context, TemplateDoesNotExist
from django.template.defaultfilters import force_escape, pprint
from django.utils.html import escape
from django.utils.importlib import import_module
-from django.utils.encoding import smart_text, smart_bytes
+from django.utils.encoding import force_bytes, smart_text
from django.utils import six
HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE')
@@ -440,7 +440,7 @@ def technical_404_response(request, exception):
'root_urlconf': settings.ROOT_URLCONF,
'request_path': request.path_info[1:], # Trim leading slash
'urlpatterns': tried,
- 'reason': smart_bytes(exception, errors='replace'),
+ 'reason': force_bytes(exception, errors='replace'),
'request': request,
'settings': get_safe_settings(),
})
diff --git a/django/views/generic/base.py b/django/views/generic/base.py
index 6554e898ca..e11412ba4d 100644
--- a/django/views/generic/base.py
+++ b/django/views/generic/base.py
@@ -18,6 +18,8 @@ class ContextMixin(object):
"""
def get_context_data(self, **kwargs):
+ if 'view' not in kwargs:
+ kwargs['view'] = self
return kwargs
@@ -140,11 +142,11 @@ class TemplateResponseMixin(object):
class TemplateView(TemplateResponseMixin, ContextMixin, View):
"""
- A view that renders a template. This view is different from all the others
- insofar as it also passes ``kwargs`` as ``params`` to the template context.
+ A view that renders a template. This view will also pass into the context
+ any keyword arguments passed by the url conf.
"""
def get(self, request, *args, **kwargs):
- context = self.get_context_data(params=kwargs)
+ context = self.get_context_data(**kwargs)
return self.render_to_response(context)
diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py
index 08b7318738..d44246f0b7 100644
--- a/django/views/generic/dates.py
+++ b/django/views/generic/dates.py
@@ -327,6 +327,7 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
Abstract base class for date-based views displaying a list of objects.
"""
allow_empty = False
+ date_list_period = 'year'
def get(self, request, *args, **kwargs):
self.date_list, self.object_list, extra_context = self.get_dated_items()
@@ -370,13 +371,18 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
return qs
- def get_date_list(self, queryset, date_type):
+ def get_date_list_period(self):
+ return self.date_list_period
+
+ def get_date_list(self, queryset, date_type=None):
"""
Get a date list by calling `queryset.dates()`, checking along the way
for empty lists that aren't allowed.
"""
date_field = self.get_date_field()
allow_empty = self.get_allow_empty()
+ if date_type is None:
+ date_type = self.get_date_list_period()
date_list = queryset.dates(date_field, date_type)[::-1]
if date_list is not None and not date_list and not allow_empty:
@@ -400,7 +406,7 @@ class BaseArchiveIndexView(BaseDateListView):
Return (date_list, items, extra_context) for this request.
"""
qs = self.get_dated_queryset(ordering='-%s' % self.get_date_field())
- date_list = self.get_date_list(qs, 'year')
+ date_list = self.get_date_list(qs)
if not date_list:
qs = qs.none()
@@ -419,6 +425,7 @@ class BaseYearArchiveView(YearMixin, BaseDateListView):
"""
List of objects published in a given year.
"""
+ date_list_period = 'month'
make_object_list = False
def get_dated_items(self):
@@ -438,7 +445,7 @@ class BaseYearArchiveView(YearMixin, BaseDateListView):
}
qs = self.get_dated_queryset(ordering='-%s' % date_field, **lookup_kwargs)
- date_list = self.get_date_list(qs, 'month')
+ date_list = self.get_date_list(qs)
if not self.get_make_object_list():
# We need this to be a queryset since parent classes introspect it
@@ -470,6 +477,8 @@ class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView):
"""
List of objects published in a given year.
"""
+ date_list_period = 'day'
+
def get_dated_items(self):
"""
Return (date_list, items, extra_context) for this request.
@@ -489,7 +498,7 @@ class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView):
}
qs = self.get_dated_queryset(**lookup_kwargs)
- date_list = self.get_date_list(qs, 'day')
+ date_list = self.get_date_list(qs)
return (date_list, qs, {
'month': date,
diff --git a/django/views/static.py b/django/views/static.py
index bcac9475e2..2ff22ce13f 100644
--- a/django/views/static.py
+++ b/django/views/static.py
@@ -61,7 +61,7 @@ def serve(request, path, document_root=None, show_indexes=False):
mimetype = mimetype or 'application/octet-stream'
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
statobj.st_mtime, statobj.st_size):
- return HttpResponseNotModified(content_type=mimetype)
+ return HttpResponseNotModified()
with open(fullpath, 'rb') as f:
response = HttpResponse(f.read(), content_type=mimetype)
response["Last-Modified"] = http_date(statobj.st_mtime)