summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2007-07-04 12:11:04 +0000
committerMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2007-07-04 12:11:04 +0000
commit953badbea5a04159adbfa970f5805c0232b6a401 (patch)
tree9569f74b5d382b222613a1085efd0de21937e95f
parent4c958b15b250866b70ded7d82aa532f1e57f96ae (diff)
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes. Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702 git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--AUTHORS2
-rwxr-xr-xdjango/bin/make-messages.py2
-rw-r--r--django/conf/global_settings.py3
-rw-r--r--django/contrib/admin/filterspecs.py14
-rw-r--r--django/contrib/admin/media/js/urlify.js95
-rw-r--r--django/contrib/admin/models.py9
-rw-r--r--django/contrib/admin/templates/admin/filter.html2
-rw-r--r--django/contrib/admin/templatetags/admin_list.py35
-rw-r--r--django/contrib/admin/templatetags/admin_modify.py27
-rw-r--r--django/contrib/admin/templatetags/adminapplist.py5
-rw-r--r--django/contrib/admin/views/auth.py1
-rw-r--r--django/contrib/admin/views/decorators.py4
-rw-r--r--django/contrib/admin/views/doc.py1
-rw-r--r--django/contrib/admin/views/main.py74
-rw-r--r--django/contrib/auth/forms.py2
-rw-r--r--django/contrib/auth/management.py4
-rw-r--r--django/contrib/auth/models.py35
-rw-r--r--django/contrib/auth/views.py1
-rw-r--r--django/contrib/comments/feeds.py4
-rw-r--r--django/contrib/comments/models.py2
-rw-r--r--django/contrib/comments/views/comments.py9
-rw-r--r--django/contrib/comments/views/karma.py1
-rw-r--r--django/contrib/contenttypes/generic.py4
-rw-r--r--django/contrib/contenttypes/management.py3
-rw-r--r--django/contrib/contenttypes/models.py13
-rw-r--r--django/contrib/databrowse/datastructures.py32
-rw-r--r--django/contrib/databrowse/plugins/calendars.py9
-rw-r--r--django/contrib/databrowse/plugins/fieldchoices.py12
-rw-r--r--django/contrib/databrowse/sites.py2
-rw-r--r--django/contrib/databrowse/templates/databrowse/fieldchoice_list.html2
-rw-r--r--django/contrib/flatpages/models.py6
-rw-r--r--django/contrib/humanize/templatetags/humanize.py18
-rw-r--r--django/contrib/localflavor/au/forms.py12
-rw-r--r--django/contrib/localflavor/br/forms.py20
-rw-r--r--django/contrib/localflavor/ch/ch_states.py2
-rw-r--r--django/contrib/localflavor/ch/forms.py6
-rw-r--r--django/contrib/localflavor/cl/forms.py15
-rw-r--r--django/contrib/localflavor/de/de_states.py2
-rw-r--r--django/contrib/localflavor/de/forms.py12
-rw-r--r--django/contrib/localflavor/fi/forms.py12
-rw-r--r--django/contrib/localflavor/fr/forms.py8
-rw-r--r--django/contrib/localflavor/is_/forms.py9
-rw-r--r--django/contrib/localflavor/it/forms.py14
-rw-r--r--django/contrib/localflavor/it/it_province.py2
-rw-r--r--django/contrib/localflavor/it/util.py26
-rw-r--r--django/contrib/localflavor/jp/forms.py6
-rw-r--r--django/contrib/localflavor/jp/jp_prefectures.py96
-rw-r--r--django/contrib/localflavor/no/forms.py12
-rw-r--r--django/contrib/localflavor/no/no_municipalities.py10
-rw-r--r--django/contrib/localflavor/uk/forms.py4
-rw-r--r--django/contrib/localflavor/us/forms.py20
-rw-r--r--django/contrib/markup/templatetags/markup.py15
-rw-r--r--django/contrib/redirects/models.py6
-rw-r--r--django/contrib/sessions/models.py2
-rw-r--r--django/contrib/sitemaps/views.py3
-rw-r--r--django/contrib/sites/models.py4
-rw-r--r--django/contrib/syndication/feeds.py13
-rw-r--r--django/contrib/webdesign/lorem_ipsum.py46
-rw-r--r--django/contrib/webdesign/templatetags/webdesign.py2
-rw-r--r--django/contrib/webdesign/tests.py4
-rw-r--r--django/core/handlers/modpython.py6
-rw-r--r--django/core/handlers/wsgi.py10
-rw-r--r--django/core/mail.py41
-rw-r--r--django/core/management.py6
-rw-r--r--django/core/serializers/__init__.py16
-rw-r--r--django/core/serializers/base.py5
-rw-r--r--django/core/serializers/python.py49
-rw-r--r--django/core/serializers/pyyaml.py4
-rw-r--r--django/core/serializers/xml_serializer.py82
-rw-r--r--django/core/urlresolvers.py27
-rw-r--r--django/core/validators.py119
-rw-r--r--django/db/backends/mysql/base.py2
-rw-r--r--django/db/backends/mysql_old/base.py2
-rw-r--r--django/db/backends/oracle/base.py64
-rw-r--r--django/db/backends/oracle/creation.py10
-rw-r--r--django/db/backends/postgresql/base.py50
-rw-r--r--django/db/backends/postgresql_psycopg2/base.py4
-rw-r--r--django/db/backends/sqlite3/base.py19
-rw-r--r--django/db/backends/util.py19
-rw-r--r--django/db/models/base.py9
-rw-r--r--django/db/models/fields/__init__.py53
-rw-r--r--django/db/models/fields/related.py11
-rw-r--r--django/db/models/manipulators.py6
-rw-r--r--django/db/models/options.py22
-rw-r--r--django/db/models/query.py3
-rw-r--r--django/http/__init__.py98
-rw-r--r--django/newforms/fields.py72
-rw-r--r--django/newforms/forms.py16
-rw-r--r--django/newforms/models.py14
-rw-r--r--django/newforms/util.py18
-rw-r--r--django/newforms/widgets.py40
-rw-r--r--django/oldforms/__init__.py172
-rw-r--r--django/template/__init__.py41
-rw-r--r--django/template/defaultfilters.py139
-rw-r--r--django/template/defaulttags.py7
-rw-r--r--django/template/loaders/app_directories.py2
-rw-r--r--django/template/loaders/eggs.py2
-rw-r--r--django/template/loaders/filesystem.py2
-rw-r--r--django/templatetags/i18n.py8
-rw-r--r--django/test/client.py33
-rw-r--r--django/test/testcases.py12
-rw-r--r--django/test/utils.py4
-rw-r--r--django/utils/cache.py3
-rw-r--r--django/utils/dateformat.py54
-rw-r--r--django/utils/dates.py6
-rw-r--r--django/utils/encoding.py84
-rw-r--r--django/utils/feedgenerator.py50
-rw-r--r--django/utils/functional.py55
-rw-r--r--django/utils/html.py41
-rw-r--r--django/utils/http.py35
-rw-r--r--django/utils/stopwords.py2
-rw-r--r--django/utils/text.py54
-rw-r--r--django/utils/timesince.py20
-rw-r--r--django/utils/translation/__init__.py23
-rw-r--r--django/utils/translation/trans_null.py11
-rw-r--r--django/utils/translation/trans_real.py95
-rw-r--r--django/utils/tzinfo.py13
-rw-r--r--django/views/debug.py14
-rw-r--r--django/views/generic/create_update.py8
-rw-r--r--docs/contributing.txt1
-rw-r--r--docs/db-api.txt6
-rw-r--r--docs/forms.txt2
-rw-r--r--docs/i18n.txt176
-rw-r--r--docs/model-api.txt50
-rw-r--r--docs/newforms.txt2
-rw-r--r--docs/overview.txt4
-rw-r--r--docs/settings.txt10
-rw-r--r--docs/templates.txt10
-rw-r--r--docs/tutorial01.txt31
-rw-r--r--docs/unicode.txt363
-rw-r--r--tests/modeltests/basic/models.py5
-rw-r--r--tests/modeltests/choices/models.py6
-rw-r--r--tests/modeltests/custom_columns/models.py18
-rw-r--r--tests/modeltests/custom_managers/models.py8
-rw-r--r--tests/modeltests/custom_methods/models.py2
-rw-r--r--tests/modeltests/custom_pk/models.py10
-rw-r--r--tests/modeltests/field_defaults/models.py2
-rw-r--r--tests/modeltests/fixtures/models.py20
-rw-r--r--tests/modeltests/generic_relations/models.py16
-rw-r--r--tests/modeltests/get_latest/models.py4
-rw-r--r--tests/modeltests/get_object_or_404/models.py4
-rw-r--r--tests/modeltests/get_or_create/models.py4
-rw-r--r--tests/modeltests/lookup/models.py32
-rw-r--r--tests/modeltests/m2m_and_m2o/models.py4
-rw-r--r--tests/modeltests/m2m_intermediary/models.py10
-rw-r--r--tests/modeltests/m2m_multiple/models.py4
-rw-r--r--tests/modeltests/m2m_recursive/models.py2
-rw-r--r--tests/modeltests/m2o_recursive/models.py2
-rw-r--r--tests/modeltests/m2o_recursive2/models.py2
-rw-r--r--tests/modeltests/manipulators/models.py16
-rw-r--r--tests/modeltests/many_to_many/models.py4
-rw-r--r--tests/modeltests/many_to_one/models.py15
-rw-r--r--tests/modeltests/many_to_one_null/models.py4
-rw-r--r--tests/modeltests/model_forms/models.py16
-rw-r--r--tests/modeltests/model_inheritance/models.py12
-rw-r--r--tests/modeltests/one_to_one/models.py12
-rw-r--r--tests/modeltests/or_lookups/models.py4
-rw-r--r--tests/modeltests/ordering/models.py2
-rw-r--r--tests/modeltests/pagination/models.py2
-rw-r--r--tests/modeltests/reserved_names/models.py2
-rw-r--r--tests/modeltests/reverse_lookup/models.py6
-rw-r--r--tests/modeltests/save_delete_hooks/models.py4
-rw-r--r--tests/modeltests/select_related/models.py16
-rw-r--r--tests/modeltests/serializers/models.py10
-rw-r--r--tests/modeltests/str/models.py36
-rw-r--r--tests/modeltests/test_client/models.py10
-rw-r--r--tests/modeltests/test_client/views.py2
-rw-r--r--tests/modeltests/transactions/models.py6
-rw-r--r--tests/modeltests/validation/models.py14
-rw-r--r--tests/regressiontests/dateformat/tests.py46
-rw-r--r--tests/regressiontests/defaultfilters/tests.py446
-rw-r--r--tests/regressiontests/fixtures_regress/models.py6
-rw-r--r--tests/regressiontests/forms/localflavor.py14
-rw-r--r--tests/regressiontests/forms/regressions.py49
-rw-r--r--tests/regressiontests/forms/tests.py2
-rw-r--r--tests/regressiontests/httpwrappers/tests.py76
-rw-r--r--tests/regressiontests/humanize/tests.py10
-rw-r--r--tests/regressiontests/i18n/__init__.py0
-rw-r--r--tests/regressiontests/i18n/models.py0
-rw-r--r--tests/regressiontests/i18n/tests.py33
-rw-r--r--tests/regressiontests/model_regress/__init__.py0
-rw-r--r--tests/regressiontests/model_regress/models.py34
-rw-r--r--tests/regressiontests/null_queries/models.py8
-rw-r--r--tests/regressiontests/one_to_one_regress/models.py12
-rw-r--r--tests/regressiontests/serializers_regress/tests.py3
-rw-r--r--tests/regressiontests/string_lookup/models.py38
-rw-r--r--tests/regressiontests/templates/tests.py21
-rw-r--r--tests/regressiontests/templates/unicode.py33
-rw-r--r--tests/regressiontests/templates/urls.py2
-rw-r--r--tests/regressiontests/test_client_regress/models.py12
-rw-r--r--tests/regressiontests/test_client_regress/urls.py2
-rw-r--r--tests/regressiontests/test_client_regress/views.py14
-rw-r--r--tests/regressiontests/text/tests.py31
193 files changed, 3000 insertions, 1598 deletions
diff --git a/AUTHORS b/AUTHORS
index d78dabbb4b..190b633080 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -113,6 +113,7 @@ answer newbie questions, and generally made Django that much better:
Simon Greenhill <dev@simon.net.nz>
Owen Griffiths
Espen Grindhaug <http://grindhaug.org/>
+ Thomas Güttler <hv@tbz-pariv.de>
Brian Harring <ferringb@gmail.com>
Brant Harris
Hawkeye
@@ -147,6 +148,7 @@ answer newbie questions, and generally made Django that much better:
Bruce Kroeze <http://coderseye.com/>
Joseph Kocherhans
konrad@gwu.edu
+ kurtiss@meetro.com
lakin.wecker@gmail.com
Nick Lane <nick.lane.au@gmail.com>
Stuart Langridge <http://www.kryogenix.org/>
diff --git a/django/bin/make-messages.py b/django/bin/make-messages.py
index bf9e7a1962..3e5acb70cb 100755
--- a/django/bin/make-messages.py
+++ b/django/bin/make-messages.py
@@ -103,7 +103,7 @@ def make_messages():
open(os.path.join(dirpath, '%s.py' % file), "wb").write(templatize(src))
thefile = '%s.py' % file
if verbose: sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
- cmd = 'xgettext %s -d %s -L Python --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % (
+ cmd = 'xgettext %s -d %s -L Python --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --keyword=ugettext_noop --keyword=ugettext_lazy --keyword=ungettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % (
os.path.exists(potfile) and '--omit-header' or '', domain, os.path.join(dirpath, thefile))
(stdin, stdout, stderr) = os.popen3(cmd, 'b')
msgs = stdout.read()
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index ea92d13eb3..75ba271c1e 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -97,6 +97,9 @@ MANAGERS = ADMINS
DEFAULT_CONTENT_TYPE = 'text/html'
DEFAULT_CHARSET = 'utf-8'
+# Encoding of files read from disk (template and initial SQL files).
+FILE_CHARSET = 'utf-8'
+
# E-mail address that error messages come from.
SERVER_EMAIL = 'root@localhost'
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py
index 8c2b82147e..5aa950cbf4 100644
--- a/django/contrib/admin/filterspecs.py
+++ b/django/contrib/admin/filterspecs.py
@@ -7,6 +7,8 @@ certain test -- e.g. being a DateField or ForeignKey.
"""
from django.db import models
+from django.utils.encoding import smart_unicode, iri_to_uri
+from django.utils.translation import ugettext as _
import datetime
class FilterSpec(object):
@@ -37,12 +39,12 @@ class FilterSpec(object):
def output(self, cl):
t = []
if self.has_output():
- t.append(_('<h3>By %s:</h3>\n<ul>\n') % self.title())
+ t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % self.title())
for choice in self.choices(cl):
- t.append('<li%s><a href="%s">%s</a></li>\n' % \
+ t.append(u'<li%s><a href="%s">%s</a></li>\n' % \
((choice['selected'] and ' class="selected"' or ''),
- choice['query_string'] ,
+ iri_to_uri(choice['query_string']),
choice['display']))
t.append('</ul>\n\n')
return "".join(t)
@@ -70,7 +72,7 @@ class RelatedFilterSpec(FilterSpec):
'display': _('All')}
for val in self.lookup_choices:
pk_val = getattr(val, self.field.rel.to._meta.pk.attname)
- yield {'selected': self.lookup_val == str(pk_val),
+ yield {'selected': self.lookup_val == smart_unicode(pk_val),
'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}),
'display': val}
@@ -87,7 +89,7 @@ class ChoicesFilterSpec(FilterSpec):
'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
'display': _('All')}
for k, v in self.field.choices:
- yield {'selected': str(k) == self.lookup_val,
+ yield {'selected': smart_unicode(k) == self.lookup_val,
'query_string': cl.get_query_string({self.lookup_kwarg: k}),
'display': v}
@@ -168,7 +170,7 @@ class AllValuesFilterSpec(FilterSpec):
'query_string': cl.get_query_string({}, [self.field.name]),
'display': _('All')}
for val in self.lookup_choices:
- val = str(val[self.field.name])
+ val = smart_unicode(val[self.field.name])
yield {'selected': self.lookup_val == val,
'query_string': cl.get_query_string({self.field.name: val}),
'display': val}
diff --git a/django/contrib/admin/media/js/urlify.js b/django/contrib/admin/media/js/urlify.js
index 9b87113628..cd382751e2 100644
--- a/django/contrib/admin/media/js/urlify.js
+++ b/django/contrib/admin/media/js/urlify.js
@@ -1,15 +1,110 @@
+var LATIN_MAP =
+{
+ 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE', 'Ç':
+ 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', 'Î': 'I',
+ 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö':
+ 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'Ý': 'Y', 'Þ': 'TH',
+ 'ß': 'ss', 'à':'a', 'á':'a', 'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ':
+ 'ae', 'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', 'ì': 'i', 'í': 'i',
+ 'î': 'i', 'ï': 'i', 'ð': 'o', 'ñ': 'n', 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ':
+ 'o', 'ö': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u', 'ý': 'y',
+ 'þ': 'th', 'ÿ': 'y',
+}
+var LATIN_SYMBOLS_MAP =
+{
+ '©':'(c)',
+}
+var GREEK_MAP =
+{
+ 'α':'a', 'β':'b', 'γ':'g', 'δ':'d', 'ε':'e', 'ζ':'z', 'η':'h', 'θ':'8',
+ 'ι':'i', 'κ':'k', 'λ':'l', 'μ':'m', 'ν':'n', 'ξ':'3', 'ο':'o', 'π':'p',
+ 'ρ':'r', 'σ':'s', 'τ':'t', 'υ':'y', 'φ':'f', 'χ':'x', 'ψ':'ps', 'ω':'w',
+ 'ά':'a', 'έ':'e', 'ί':'i', 'ό':'o', 'ύ':'y', 'ή':'h', 'ώ':'w', 'ς':'s',
+ 'ϊ':'i', 'ΰ':'y', 'ϋ':'y', 'ΐ':'i',
+ 'Α':'A', 'Β':'B', 'Γ':'G', 'Δ':'D', 'Ε':'E', 'Ζ':'Z', 'Η':'H', 'Θ':'8',
+ 'Ι':'I', 'Κ':'K', 'Λ':'L', 'Μ':'M', 'Ν':'N', 'Ξ':'3', 'Ο':'O', 'Π':'P',
+ 'Ρ':'R', 'Σ':'S', 'Τ':'T', 'Υ':'Y', 'Φ':'F', 'Χ':'X', 'Ψ':'PS', 'Ω':'W',
+ 'Ά':'A', 'Έ':'E', 'Ί':'I', 'Ό':'O', 'Ύ':'Y', 'Ή':'H', 'Ώ':'W', 'Ϊ':'I',
+ 'Ϋ':'Y'
+}
+var TURKISH_MAP = {
+ 'ş':'s', 'Ş':'S', 'ı':'i', 'İ':'I', 'ç':'c', 'Ç':'C', 'ü':'u', 'Ü':'U',
+ 'ö':'o', 'Ö':'O', 'ğ':'g', 'Ğ':'G',
+}
+// var RUSSIAN_MAP =
+// {
+// }
+
+var ALL_DOWNCODE_MAPS=new Array()
+ALL_DOWNCODE_MAPS[0]=LATIN_MAP
+ALL_DOWNCODE_MAPS[1]=LATIN_SYMBOLS_MAP
+ALL_DOWNCODE_MAPS[2]=GREEK_MAP
+ALL_DOWNCODE_MAPS[3]=TURKISH_MAP
+//ALL_DOWNCODE_MAPS[4]=RUSSIAN_MAP
+
+var Downcoder = new Object();
+Downcoder.Initialize = function()
+{
+ if (Downcoder.map) // already made
+ return ;
+ Downcoder.map ={}
+ Downcoder.chars = '' ;
+ for(var i in ALL_DOWNCODE_MAPS)
+ {
+ var lookup = ALL_DOWNCODE_MAPS[i]
+ for (var c in lookup)
+ {
+ Downcoder.map[c] = lookup[c] ;
+ Downcoder.chars += c ;
+ }
+ }
+ Downcoder.regex = new RegExp('[' + Downcoder.chars + ']|[^' + Downcoder.chars + ']+','g') ;
+}
+
+downcode= function( slug )
+{
+ Downcoder.Initialize() ;
+ var downcoded =""
+ var pieces = slug.match(Downcoder.regex);
+ if(pieces)
+ {
+ for (var i = 0 ; i < pieces.length ; i++)
+ {
+ if (pieces[i].length == 1)
+ {
+ var mapped = Downcoder.map[pieces[i]] ;
+ if (mapped != null)
+ {
+ downcoded+=mapped;
+ continue ;
+ }
+ }
+ downcoded+=pieces[i];
+ }
+ }
+ else
+ {
+ downcoded = slug;
+ }
+ return downcoded;
+}
+
+
function URLify(s, num_chars) {
// changes, e.g., "Petty theft" to "petty_theft"
// remove all these words from the string before urlifying
+ s = downcode(s);
removelist = ["a", "an", "as", "at", "before", "but", "by", "for", "from",
"is", "in", "into", "like", "of", "off", "on", "onto", "per",
"since", "than", "the", "this", "that", "to", "up", "via",
"with"];
r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi');
s = s.replace(r, '');
+ // if downcode doesn't hit, the char will be stripped here
s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars
s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces
s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens
s = s.toLowerCase(); // convert to lowercase
return s.substring(0, num_chars);// trim to first num_chars chars
}
+
diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py
index 7144c0b20b..b5b779a3ac 100644
--- a/django/contrib/admin/models.py
+++ b/django/contrib/admin/models.py
@@ -1,7 +1,8 @@
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ugettext_lazy as _
+from django.utils.encoding import smart_unicode
ADDITION = 1
CHANGE = 2
@@ -9,7 +10,7 @@ DELETION = 3
class LogEntryManager(models.Manager):
def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
- e = self.model(None, None, user_id, content_type_id, str(object_id), object_repr[:200], action_flag, change_message)
+ e = self.model(None, None, user_id, content_type_id, smart_unicode(object_id), object_repr[:200], action_flag, change_message)
e.save()
class LogEntry(models.Model):
@@ -28,7 +29,7 @@ class LogEntry(models.Model):
ordering = ('-action_time',)
def __repr__(self):
- return str(self.action_time)
+ return smart_unicode(self.action_time)
def is_addition(self):
return self.action_flag == ADDITION
@@ -48,4 +49,4 @@ class LogEntry(models.Model):
Returns the admin URL to edit the object represented by this log entry.
This is relative to the Django admin index page.
"""
- return "%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id)
+ return u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id)
diff --git a/django/contrib/admin/templates/admin/filter.html b/django/contrib/admin/templates/admin/filter.html
index 8b5b521437..bcc64ac7e4 100644
--- a/django/contrib/admin/templates/admin/filter.html
+++ b/django/contrib/admin/templates/admin/filter.html
@@ -3,6 +3,6 @@
<ul>
{% for choice in choices %}
<li{% if choice.selected %} class="selected"{% endif %}>
- <a href="{{ choice.query_string }}">{{ choice.display|escape }}</a></li>
+ <a href="{{ choice.query_string|iriencode }}">{{ choice.display|escape }}</a></li>
{% endfor %}
</ul>
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 7ad66c250d..206a453383 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -6,7 +6,8 @@ from django.db import models
from django.utils import dateformat
from django.utils.html import escape
from django.utils.text import capfirst
-from django.utils.translation import get_date_formats, get_partial_date_formats
+from django.utils.translation import get_date_formats, get_partial_date_formats, ugettext as _
+from django.utils.encoding import smart_unicode, smart_str, force_unicode
from django.template import Library
import datetime
@@ -16,11 +17,11 @@ DOT = '.'
def paginator_number(cl,i):
if i == DOT:
- return '... '
+ return u'... '
elif i == cl.page_num:
- return '<span class="this-page">%d</span> ' % (i+1)
+ return u'<span class="this-page">%d</span> ' % (i+1)
else:
- return '<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1)
+ return u'<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1)
paginator_number = register.simple_tag(paginator_number)
def pagination(cl):
@@ -75,10 +76,12 @@ def result_headers(cl):
admin_order_field = None
except models.FieldDoesNotExist:
# For non-field list_display values, check for the function
- # attribute "short_description". If that doesn't exist, fall
- # back to the method name. And __str__ is a special-case.
- if field_name == '__str__':
- header = lookup_opts.verbose_name
+ # attribute "short_description". If that doesn't exist, fall back
+ # to the method name. And __str__ and __unicode__ are special-cases.
+ if field_name == '__unicode__':
+ header = force_unicode(lookup_opts.verbose_name)
+ elif field_name == '__str__':
+ header = smart_str(lookup_opts.verbose_name)
else:
attr = getattr(cl.model, field_name) # Let AttributeErrors propagate.
try:
@@ -114,7 +117,7 @@ def result_headers(cl):
def _boolean_icon(field_val):
BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
- return '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
+ return u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
def items_for_result(cl, result):
first = True
@@ -136,7 +139,7 @@ def items_for_result(cl, result):
allow_tags = True
result_repr = _boolean_icon(attr)
else:
- result_repr = str(attr)
+ result_repr = smart_unicode(attr)
except (AttributeError, ObjectDoesNotExist):
result_repr = EMPTY_CHANGELIST_VALUE
else:
@@ -179,19 +182,19 @@ def items_for_result(cl, result):
elif f.choices:
result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE)
else:
- result_repr = escape(str(field_val))
- if result_repr == '':
+ result_repr = escape(field_val)
+ if force_unicode(result_repr) == '':
result_repr = '&nbsp;'
# If list_display_links not defined, add the link tag to the first field
- if (first and not cl.lookup_opts.admin.list_display_links) or field_name in cl.lookup_opts.admin.list_display_links:
+ if (first and not cl.lookup_opts.admin.list_display_links) or field_name in cl.lookup_opts.admin.list_display_links:
table_tag = {True:'th', False:'td'}[first]
first = False
url = cl.url_for_result(result)
- result_id = str(getattr(result, pk)) # str() is needed in case of 23L (long ints)
- yield ('<%s%s><a href="%s"%s>%s</a></%s>' % \
+ result_id = smart_unicode(getattr(result, pk)) # conversion to string is needed in case of 23L (long ints)
+ yield (u'<%s%s><a href="%s"%s>%s</a></%s>' % \
(table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %r); return false;"' % result_id or ''), result_repr, table_tag))
else:
- yield ('<td%s>%s</td>' % (row_class, result_repr))
+ yield (u'<td%s>%s</td>' % (row_class, result_repr))
def results(cl):
for res in cl.result_list:
diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py
index f9cad005d5..9bc41c6450 100644
--- a/django/contrib/admin/templatetags/admin_modify.py
+++ b/django/contrib/admin/templatetags/admin_modify.py
@@ -2,6 +2,7 @@ from django import template
from django.contrib.admin.views.main import AdminBoundField
from django.template import loader
from django.utils.text import capfirst
+from django.utils.encoding import force_unicode
from django.db import models
from django.db.models.fields import Field
from django.db.models.related import BoundRelatedObject
@@ -14,7 +15,7 @@ word_re = re.compile('[A-Z][a-z]+')
absolute_url_re = re.compile(r'^(?:http(?:s)?:/)?/', re.IGNORECASE)
def class_name_to_underscored(name):
- return '_'.join([s.lower() for s in word_re.findall(name)[:-1]])
+ return u'_'.join([s.lower() for s in word_re.findall(name)[:-1]])
def include_admin_script(script_path):
"""
@@ -31,7 +32,7 @@ def include_admin_script(script_path):
"""
if not absolute_url_re.match(script_path):
script_path = '%s%s' % (settings.ADMIN_MEDIA_PREFIX, script_path)
- return '<script type="text/javascript" src="%s"></script>' % script_path
+ return u'<script type="text/javascript" src="%s"></script>' % script_path
include_admin_script = register.simple_tag(include_admin_script)
def submit_row(context):
@@ -61,9 +62,9 @@ def field_label(bound_field):
if not bound_field.first:
class_names.append('inline')
colon = ":"
- class_str = class_names and ' class="%s"' % ' '.join(class_names) or ''
- return '<label for="%s"%s>%s%s</label> ' % (bound_field.element_id, class_str, \
- capfirst(bound_field.field.verbose_name), colon)
+ class_str = class_names and u' class="%s"' % u' '.join(class_names) or u''
+ return u'<label for="%s"%s>%s%s</label> ' % (bound_field.element_id, class_str, \
+ force_unicode(capfirst(bound_field.field.verbose_name)), colon)
field_label = register.simple_tag(field_label)
class FieldWidgetNode(template.Node):
@@ -77,7 +78,7 @@ class FieldWidgetNode(template.Node):
if klass not in cls.nodelists:
try:
field_class_name = klass.__name__
- template_name = "widget/%s.html" % class_name_to_underscored(field_class_name)
+ template_name = u"widget/%s.html" % class_name_to_underscored(field_class_name)
nodelist = loader.get_template(template_name).nodelist
except template.TemplateDoesNotExist:
super_klass = bool(klass.__bases__) and klass.__bases__[0] or None
@@ -175,30 +176,30 @@ class EditInlineNode(template.Node):
return output
def output_all(form_fields):
- return ''.join([str(f) for f in form_fields])
+ return u''.join([force_unicode(f) for f in form_fields])
output_all = register.simple_tag(output_all)
def auto_populated_field_script(auto_pop_fields, change = False):
t = []
for field in auto_pop_fields:
if change:
- t.append('document.getElementById("id_%s")._changed = true;' % field.name)
+ t.append(u'document.getElementById("id_%s")._changed = true;' % field.name)
else:
- t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name)
+ t.append(u'document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name)
- add_values = ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from])
+ add_values = u' + " " + '.join([u'document.getElementById("id_%s").value' % g for g in field.prepopulate_from])
for f in field.prepopulate_from:
- t.append('document.getElementById("id_%s").onkeyup = function() {' \
+ t.append(u'document.getElementById("id_%s").onkeyup = function() {' \
' var e = document.getElementById("id_%s");' \
' if(!e._changed) { e.value = URLify(%s, %s);} }; ' % (
f, field.name, add_values, field.maxlength))
- return ''.join(t)
+ return u''.join(t)
auto_populated_field_script = register.simple_tag(auto_populated_field_script)
def filter_interface_script_maybe(bound_field):
f = bound_field.field
if f.rel and isinstance(f.rel, models.ManyToManyRel) and f.rel.filter_interface:
- return '<script type="text/javascript">addEvent(window, "load", function(e) {' \
+ return u'<script type="text/javascript">addEvent(window, "load", function(e) {' \
' SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % (
f.name, f.verbose_name.replace('"', '\\"'), f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX)
else:
diff --git a/django/contrib/admin/templatetags/adminapplist.py b/django/contrib/admin/templatetags/adminapplist.py
index 10e09ca0b6..673b77a1e0 100644
--- a/django/contrib/admin/templatetags/adminapplist.py
+++ b/django/contrib/admin/templatetags/adminapplist.py
@@ -1,5 +1,6 @@
from django import template
from django.db.models import get_models
+from django.utils.encoding import force_unicode
register = template.Library()
@@ -36,8 +37,8 @@ class AdminApplistNode(template.Node):
# If so, add the module to the model_list.
if True in perms.values():
model_list.append({
- 'name': capfirst(m._meta.verbose_name_plural),
- 'admin_url': '%s/%s/' % (app_label, m.__name__.lower()),
+ 'name': force_unicode(capfirst(m._meta.verbose_name_plural)),
+ 'admin_url': u'%s/%s/' % (force_unicode(app_label), m.__name__.lower()),
'perms': perms,
})
diff --git a/django/contrib/admin/views/auth.py b/django/contrib/admin/views/auth.py
index c6ad0c3a95..0c8104831b 100644
--- a/django/contrib/admin/views/auth.py
+++ b/django/contrib/admin/views/auth.py
@@ -6,6 +6,7 @@ from django import oldforms, template
from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect
from django.utils.html import escape
+from django.utils.translation import ugettext as _
def user_add_stage(request):
if not request.user.has_perm('auth.change_user'):
diff --git a/django/contrib/admin/views/decorators.py b/django/contrib/admin/views/decorators.py
index 5389ca4dff..9ae0170b54 100644
--- a/django/contrib/admin/views/decorators.py
+++ b/django/contrib/admin/views/decorators.py
@@ -3,11 +3,11 @@ from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login
from django.shortcuts import render_to_response
-from django.utils.translation import gettext_lazy
+from django.utils.translation import ugettext_lazy, ugettext as _
import base64, datetime, md5
import cPickle as pickle
-ERROR_MESSAGE = gettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
+ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
LOGIN_FORM_KEY = 'this_is_the_login_form'
def _display_login_form(request, error_message=''):
diff --git a/django/contrib/admin/views/doc.py b/django/contrib/admin/views/doc.py
index 6430252690..a21eb99f09 100644
--- a/django/contrib/admin/views/doc.py
+++ b/django/contrib/admin/views/doc.py
@@ -9,6 +9,7 @@ from django.http import Http404, get_host
from django.core import urlresolvers
from django.contrib.admin import utils
from django.contrib.sites.models import Site
+from django.utils.translation import ugettext as _
import inspect, os, re
# Exclude methods starting with these strings from documentation
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index e372b45439..7e3f36c291 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -12,6 +12,8 @@ from django.db.models.query import handle_legacy_orderlist, QuerySet
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.utils.html import escape
from django.utils.text import capfirst, get_text_list
+from django.utils.encoding import force_unicode, smart_str
+from django.utils.translation import ugettext as _
import operator
try:
@@ -130,11 +132,11 @@ class AdminBoundField(object):
if max([bool(f.errors()) for f in self.form_fields]):
classes.append('error')
if classes:
- self.cell_class_attribute = ' class="%s" ' % ' '.join(classes)
+ self.cell_class_attribute = u' class="%s" ' % ' '.join(classes)
self._repr_filled = False
if field.rel:
- self.related_url = '../../../%s/%s/' % (field.rel.to._meta.app_label, field.rel.to._meta.object_name.lower())
+ self.related_url = u'../../../%s/%s/' % (field.rel.to._meta.app_label, field.rel.to._meta.object_name.lower())
def original_value(self):
if self.original:
@@ -145,9 +147,9 @@ class AdminBoundField(object):
return self._display
except AttributeError:
if isinstance(self.field.rel, models.ManyToOneRel):
- self._display = getattr(self.original, self.field.name)
+ self._display = force_unicode(getattr(self.original, self.field.name), strings_only=True)
elif isinstance(self.field.rel, models.ManyToManyRel):
- self._display = ", ".join([str(obj) for obj in getattr(self.original, self.field.name).all()])
+ self._display = u", ".join([force_unicode(obj) for obj in getattr(self.original, self.field.name).all()])
return self._display
def __repr__(self):
@@ -258,8 +260,8 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po
if not errors:
new_object = manipulator.save(new_data)
pk_value = new_object._get_pk_val()
- LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), ADDITION)
- msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object}
+ LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, force_unicode(new_object), ADDITION)
+ msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': new_object}
# Here, we distinguish between different save types by checking for
# the presence of keys in request.POST.
if "_continue" in request.POST:
@@ -271,9 +273,9 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po
if type(pk_value) is str: # Quote if string, so JavaScript doesn't think it's a variable.
pk_value = '"%s"' % pk_value.replace('"', '\\"')
return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, %s, "%s");</script>' % \
- (pk_value, str(new_object).replace('"', '\\"')))
+ (pk_value, force_unicode(new_object).replace('"', '\\"')))
elif "_addanother" in request.POST:
- request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
+ request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
return HttpResponseRedirect(request.path)
else:
request.user.message_set.create(message=msg)
@@ -291,7 +293,7 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po
form = oldforms.FormWrapper(manipulator, new_data, errors)
c = template.RequestContext(request, {
- 'title': _('Add %s') % opts.verbose_name,
+ 'title': _('Add %s') % force_unicode(opts.verbose_name),
'form': form,
'is_popup': '_popup' in request.REQUEST,
'show_delete': show_delete,
@@ -345,9 +347,9 @@ def change_stage(request, app_label, model_name, object_id):
change_message = ' '.join(change_message)
if not change_message:
change_message = _('No fields changed.')
- LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), CHANGE, change_message)
+ LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, force_unicode(new_object), CHANGE, change_message)
- msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object}
+ msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': new_object}
if "_continue" in request.POST:
request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
if '_popup' in request.REQUEST:
@@ -355,10 +357,10 @@ def change_stage(request, app_label, model_name, object_id):
else:
return HttpResponseRedirect(request.path)
elif "_saveasnew" in request.POST:
- request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
+ request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': new_object})
return HttpResponseRedirect("../%s/" % pk_value)
elif "_addanother" in request.POST:
- request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
+ request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
return HttpResponseRedirect("../add/")
else:
request.user.message_set.create(message=msg)
@@ -393,7 +395,7 @@ def change_stage(request, app_label, model_name, object_id):
form.order_objects.extend(orig_list)
c = template.RequestContext(request, {
- 'title': _('Change %s') % opts.verbose_name,
+ 'title': _('Change %s') % force_unicode(opts.verbose_name),
'form': form,
'object_id': object_id,
'original': manipulator.original_object,
@@ -434,11 +436,11 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
if related.field.rel.edit_inline or not related.opts.admin:
# Don't display link to edit, because it either has no
# admin or is edited inline.
- nh(deleted_objects, current_depth, ['%s: %s' % (capfirst(related.opts.verbose_name), sub_obj), []])
+ nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj), []])
else:
# Display a link to the admin page.
- nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
- (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.object_name.lower(),
+ nh(deleted_objects, current_depth, [u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
+ (force_unicode(capfirst(related.opts.verbose_name)), related.opts.app_label, related.opts.object_name.lower(),
sub_obj._get_pk_val(), sub_obj), []])
_get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
else:
@@ -448,11 +450,11 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
if related.field.rel.edit_inline or not related.opts.admin:
# Don't display link to edit, because it either has no
# admin or is edited inline.
- nh(deleted_objects, current_depth, ['%s: %s' % (capfirst(related.opts.verbose_name), escape(str(sub_obj))), []])
+ nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), escape(sub_obj)), []])
else:
# Display a link to the admin page.
- nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
- (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(str(sub_obj))), []])
+ nh(deleted_objects, current_depth, [u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
+ (force_unicode(capfirst(related.opts.verbose_name)), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(sub_obj)), []])
_get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
# If there were related objects, and the user doesn't have
# permission to delete them, add the missing perm to perms_needed.
@@ -466,7 +468,7 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
opts_seen.append(related.opts)
rel_opts_name = related.get_accessor_name()
has_related_objs = False
-
+
# related.get_accessor_name() could return None for symmetrical relationships
if rel_opts_name:
rel_objs = getattr(obj, rel_opts_name, None)
@@ -479,17 +481,17 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
# Don't display link to edit, because it either has no
# admin or is edited inline.
nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \
- {'fieldname': related.field.verbose_name, 'name': related.opts.verbose_name, 'obj': escape(str(sub_obj))}, []])
+ {'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name), 'obj': escape(sub_obj)}, []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [
- (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.verbose_name, 'name':related.opts.verbose_name}) + \
- (' <a href="../../../../%s/%s/%s/">%s</a>' % \
- (related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(str(sub_obj)))), []])
+ (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name)}) + \
+ (u' <a href="../../../../%s/%s/%s/">%s</a>' % \
+ (related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(sub_obj))), []])
# If there were related objects, and the user doesn't have
# permission to change them, add the missing perm to perms_needed.
if related.opts.admin and has_related_objs:
- p = '%s.%s' % (related.opts.app_label, related.opts.get_change_permission())
+ p = u'%s.%s' % (related.opts.app_label, related.opts.get_change_permission())
if not user.has_perm(p):
perms_needed.add(related.opts.verbose_name)
@@ -505,21 +507,21 @@ def delete_stage(request, app_label, model_name, object_id):
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
- deleted_objects = ['%s: <a href="../../%s/">%s</a>' % (capfirst(opts.verbose_name), object_id, escape(str(obj))), []]
+ deleted_objects = [u'%s: <a href="../../%s/">%s</a>' % (force_unicode(capfirst(opts.verbose_name)), force_unicode(object_id), escape(obj)), []]
perms_needed = set()
_get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1)
if request.POST: # The user has already confirmed the deletion.
if perms_needed:
raise PermissionDenied
- obj_display = str(obj)
+ obj_display = force_unicode(obj)
obj.delete()
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, object_id, obj_display, DELETION)
- request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': opts.verbose_name, 'obj': obj_display})
+ request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': obj_display})
return HttpResponseRedirect("../../")
extra_context = {
"title": _("Are you sure?"),
- "object_name": opts.verbose_name,
+ "object_name": force_unicode(opts.verbose_name),
"object": obj,
"deleted_objects": deleted_objects,
"perms_lacking": perms_needed,
@@ -542,7 +544,7 @@ def history(request, app_label, model_name, object_id):
extra_context = {
'title': _('Change history: %s') % obj,
'action_list': action_list,
- 'module_name': capfirst(model._meta.verbose_name_plural),
+ 'module_name': force_unicode(capfirst(model._meta.verbose_name_plural)),
'object': obj,
}
return render_to_response(["admin/%s/%s/object_history.html" % (app_label, model._meta.object_name.lower()),
@@ -574,7 +576,7 @@ class ChangeList(object):
self.query = request.GET.get(SEARCH_VAR, '')
self.query_set = self.get_query_set()
self.get_results(request)
- self.title = (self.is_popup and _('Select %s') % self.opts.verbose_name or _('Select %s to change') % self.opts.verbose_name)
+ self.title = (self.is_popup and _('Select %s') % force_unicode(self.opts.verbose_name) or _('Select %s to change') % force_unicode(self.opts.verbose_name))
self.filter_specs, self.has_filters = self.get_filters(request)
self.pk_attname = self.lookup_opts.pk.attname
@@ -602,7 +604,7 @@ class ChangeList(object):
del p[k]
elif v is not None:
p[k] = v
- return '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
+ return '?' + '&amp;'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
def get_results(self, request):
paginator = ObjectPaginator(self.query_set, self.lookup_opts.admin.list_per_page)
@@ -688,6 +690,12 @@ class ChangeList(object):
for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
if i in lookup_params:
del lookup_params[i]
+ for key, value in lookup_params.items():
+ if not isinstance(key, str):
+ # 'key' will be used as a keyword argument later, so Python
+ # requires it to be a string.
+ del lookup_params[key]
+ lookup_params[smart_str(key)] = value
# Apply lookup parameters from the query string.
qs = qs.filter(**lookup_params)
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index 9d5a48c2f1..2a68e94766 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -4,7 +4,7 @@ from django.contrib.sites.models import Site
from django.template import Context, loader
from django.core import validators
from django import oldforms
-from django.utils.translation import gettext as _
+from django.utils.translation import ugettext as _
class UserCreationForm(oldforms.Manipulator):
"A form that creates a user, with no privileges, from the given username and password."
diff --git a/django/contrib/auth/management.py b/django/contrib/auth/management.py
index 3f52681747..2b4cb8bd19 100644
--- a/django/contrib/auth/management.py
+++ b/django/contrib/auth/management.py
@@ -7,13 +7,13 @@ from django.db.models import get_models, signals
from django.contrib.auth import models as auth_app
def _get_permission_codename(action, opts):
- return '%s_%s' % (action, opts.object_name.lower())
+ return u'%s_%s' % (action, opts.object_name.lower())
def _get_all_permissions(opts):
"Returns (codename, name) for all permissions in the given opts."
perms = []
for action in ('add', 'change', 'delete'):
- perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name)))
+ perms.append((_get_permission_codename(action, opts), u'Can %s %s' % (action, opts.verbose_name_raw)))
return perms + list(opts.permissions)
def create_permissions(app, created_models, verbosity):
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index 5de8927bd6..fa53a7d9e1 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -2,8 +2,10 @@ from django.core import validators
from django.core.exceptions import ImproperlyConfigured
from django.db import backend, connection, models
from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import gettext_lazy as _
+from django.utils.encoding import smart_str
+from django.utils.translation import ugettext_lazy as _
import datetime
+import urllib
try:
set
@@ -18,16 +20,16 @@ def check_password(raw_password, enc_password):
algo, salt, hsh = enc_password.split('$')
if algo == 'md5':
import md5
- return hsh == md5.new(salt+raw_password).hexdigest()
+ return hsh == md5.new(smart_str(salt + raw_password)).hexdigest()
elif algo == 'sha1':
import sha
- return hsh == sha.new(salt+raw_password).hexdigest()
+ return hsh == sha.new(smart_str(salt + raw_password)).hexdigest()
elif algo == 'crypt':
try:
import crypt
except ImportError:
raise ValueError, "Crypt password algorithm not supported in this environment."
- return hsh == crypt.crypt(raw_password, salt)
+ return hsh == crypt.crypt(smart_str(raw_password), smart_str(salt))
raise ValueError, "Got unknown password algorithm type in password."
class SiteProfileNotAvailable(Exception):
@@ -56,8 +58,8 @@ class Permission(models.Model):
unique_together = (('content_type', 'codename'),)
ordering = ('content_type', 'codename')
- def __str__(self):
- return "%s | %s | %s" % (self.content_type.app_label, self.content_type, self.name)
+ def __unicode__(self):
+ return u"%s | %s | %s" % (self.content_type.app_label, self.content_type, self.name)
class Group(models.Model):
"""Groups are a generic way of categorizing users to apply permissions, or some other label, to those users. A user can belong to any number of groups.
@@ -77,7 +79,7 @@ class Group(models.Model):
class Admin:
search_fields = ('name',)
- def __str__(self):
+ def __unicode__(self):
return self.name
class UserManager(models.Manager):
@@ -133,11 +135,11 @@ class User(models.Model):
list_filter = ('is_staff', 'is_superuser')
search_fields = ('username', 'first_name', 'last_name', 'email')
- def __str__(self):
+ def __unicode__(self):
return self.username
def get_absolute_url(self):
- return "/users/%s/" % self.username
+ return "/users/%s/" % urllib.quote(smart_str(self.username))
def is_anonymous(self):
"Always returns False. This is a way of comparing User objects to anonymous users."
@@ -150,14 +152,14 @@ class User(models.Model):
def get_full_name(self):
"Returns the first_name plus the last_name, with a space in between."
- full_name = '%s %s' % (self.first_name, self.last_name)
+ full_name = u'%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def set_password(self, raw_password):
import sha, random
algo = 'sha1'
salt = sha.new(str(random.random())).hexdigest()[:5]
- hsh = sha.new(salt+raw_password).hexdigest()
+ hsh = sha.new(salt + smart_str(raw_password)).hexdigest()
self.password = '%s$%s$%s' % (algo, salt, hsh)
def check_password(self, raw_password):
@@ -169,7 +171,7 @@ class User(models.Model):
# algorithm or salt.
if '$' not in self.password:
import md5
- is_correct = (self.password == md5.new(raw_password).hexdigest())
+ is_correct = (self.password == md5.new(smart_str(raw_password)).hexdigest())
if is_correct:
# Convert the password to the new, more secure format.
self.set_password(raw_password)
@@ -209,7 +211,7 @@ class User(models.Model):
def get_all_permissions(self):
if not hasattr(self, '_perm_cache'):
- self._perm_cache = set(["%s.%s" % (p.content_type.app_label, p.codename) for p in self.user_permissions.select_related()])
+ self._perm_cache = set([u"%s.%s" % (p.content_type.app_label, p.codename) for p in self.user_permissions.select_related()])
self._perm_cache.update(self.get_group_permissions())
return self._perm_cache
@@ -271,7 +273,7 @@ class Message(models.Model):
user = models.ForeignKey(User)
message = models.TextField(_('message'))
- def __str__(self):
+ def __unicode__(self):
return self.message
class AnonymousUser(object):
@@ -281,9 +283,12 @@ class AnonymousUser(object):
def __init__(self):
pass
- def __str__(self):
+ def __unicode__(self):
return 'AnonymousUser'
+ def __str__(self):
+ return unicode(self).encode('utf-8')
+
def __eq__(self, other):
return isinstance(other, self.__class__)
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index 77350b9a8f..141f710bfd 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -7,6 +7,7 @@ from django.contrib.sites.models import Site
from django.http import HttpResponseRedirect
from django.contrib.auth.decorators import login_required
from django.contrib.auth import REDIRECT_FIELD_NAME
+from django.utils.translation import ugettext as _
def login(request, template_name='registration/login.html'):
"Displays the login form and handles the login action."
diff --git a/django/contrib/comments/feeds.py b/django/contrib/comments/feeds.py
index 1ad638469b..417e1d928a 100644
--- a/django/contrib/comments/feeds.py
+++ b/django/contrib/comments/feeds.py
@@ -11,7 +11,7 @@ class LatestFreeCommentsFeed(Feed):
def title(self):
if not hasattr(self, '_site'):
self._site = Site.objects.get_current()
- return "%s comments" % self._site.name
+ return u"%s comments" % self._site.name
def link(self):
if not hasattr(self, '_site'):
@@ -21,7 +21,7 @@ class LatestFreeCommentsFeed(Feed):
def description(self):
if not hasattr(self, '_site'):
self._site = Site.objects.get_current()
- return "Latest comments on %s" % self._site.name
+ return u"Latest comments on %s" % self._site.name
def get_query_set(self):
return self.comments_class.objects.filter(site__pk=settings.SITE_ID, is_public=True)
diff --git a/django/contrib/comments/models.py b/django/contrib/comments/models.py
index fa6c6aa363..e01937d5d8 100644
--- a/django/contrib/comments/models.py
+++ b/django/contrib/comments/models.py
@@ -2,7 +2,7 @@ from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.contrib.auth.models import User
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ugettext_lazy as _
from django.conf import settings
import datetime
diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
index 73a9b2c480..435bfa6f73 100644
--- a/django/contrib/comments/views/comments.py
+++ b/django/contrib/comments/views/comments.py
@@ -11,7 +11,8 @@ from django.contrib.auth.forms import AuthenticationForm
from django.http import HttpResponseRedirect
from django.utils.text import normalize_newlines
from django.conf import settings
-from django.utils.translation import ngettext
+from django.utils.translation import ungettext, ugettext as _
+from django.utils.encoding import smart_unicode
import base64, datetime
COMMENTS_PER_PAGE = 20
@@ -108,7 +109,7 @@ class PublicCommentManipulator(AuthenticationForm):
# If the commentor has posted fewer than COMMENTS_FIRST_FEW comments,
# send the comment to the managers.
if self.user_cache.comment_set.count() <= settings.COMMENTS_FIRST_FEW:
- message = ngettext('This comment was posted by a user who has posted fewer than %(count)s comment:\n\n%(text)s',
+ message = ungettext('This comment was posted by a user who has posted fewer than %(count)s comment:\n\n%(text)s',
'This comment was posted by a user who has posted fewer than %(count)s comments:\n\n%(text)s', settings.COMMENTS_FIRST_FEW) % \
{'count': settings.COMMENTS_FIRST_FEW, 'text': c.get_as_text()}
mail_managers("Comment posted by rookie user", message)
@@ -248,7 +249,7 @@ def post_comment(request):
# If the IP is banned, mail the admins, do NOT save the comment, and
# serve up the "Thanks for posting" page as if the comment WAS posted.
if request.META['REMOTE_ADDR'] in settings.BANNED_IPS:
- mail_admins("Banned IP attempted to post comment", str(request.POST) + "\n\n" + str(request.META))
+ mail_admins("Banned IP attempted to post comment", smart_unicode(request.POST) + "\n\n" + str(request.META))
else:
manipulator.do_html2python(new_data)
comment = manipulator.save(new_data)
@@ -312,7 +313,7 @@ def post_free_comment(request):
# serve up the "Thanks for posting" page as if the comment WAS posted.
if request.META['REMOTE_ADDR'] in settings.BANNED_IPS:
from django.core.mail import mail_admins
- mail_admins("Practical joker", str(request.POST) + "\n\n" + str(request.META))
+ mail_admins("Practical joker", smart_unicode(request.POST) + "\n\n" + str(request.META))
else:
manipulator.do_html2python(new_data)
comment = manipulator.save(new_data)
diff --git a/django/contrib/comments/views/karma.py b/django/contrib/comments/views/karma.py
index 8c18523feb..c6ad301dee 100644
--- a/django/contrib/comments/views/karma.py
+++ b/django/contrib/comments/views/karma.py
@@ -2,6 +2,7 @@ from django.http import Http404
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.contrib.comments.models import Comment, KarmaScore
+from django.utils.translation import ugettext as _
def vote(request, comment_id, vote):
"""
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index f995ab2044..efa49edf73 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -49,7 +49,7 @@ class GenericForeignKey(object):
def __get__(self, instance, instance_type=None):
if instance is None:
- raise AttributeError, "%s must be accessed via instance" % self.name
+ raise AttributeError, u"%s must be accessed via instance" % self.name
try:
return getattr(instance, self.cache_attr)
@@ -66,7 +66,7 @@ class GenericForeignKey(object):
def __set__(self, instance, value):
if instance is None:
- raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
+ raise AttributeError, u"%s must be accessed via instance" % self.related.opts.object_name
ct = None
fk = None
diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py
index 3572d93049..cb52e0805e 100644
--- a/django/contrib/contenttypes/management.py
+++ b/django/contrib/contenttypes/management.py
@@ -4,6 +4,7 @@ Creates content types for all installed models.
from django.dispatch import dispatcher
from django.db.models import get_apps, get_models, signals
+from django.utils.encoding import smart_unicode
def create_contenttypes(app, created_models, verbosity=2):
from django.contrib.contenttypes.models import ContentType
@@ -17,7 +18,7 @@ def create_contenttypes(app, created_models, verbosity=2):
ContentType.objects.get(app_label=opts.app_label,
model=opts.object_name.lower())
except ContentType.DoesNotExist:
- ct = ContentType(name=str(opts.verbose_name),
+ ct = ContentType(name=smart_unicode(opts.verbose_name_raw),
app_label=opts.app_label, model=opts.object_name.lower())
ct.save()
if verbosity >= 2:
diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py
index 0a5e68f37e..a825b4f3dd 100644
--- a/django/contrib/contenttypes/models.py
+++ b/django/contrib/contenttypes/models.py
@@ -1,5 +1,6 @@
from django.db import models
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ugettext_lazy as _
+from django.utils.encoding import smart_unicode
CONTENT_TYPE_CACHE = {}
class ContentTypeManager(models.Manager):
@@ -13,13 +14,13 @@ class ContentTypeManager(models.Manager):
try:
ct = CONTENT_TYPE_CACHE[key]
except KeyError:
- # The str() is needed around opts.verbose_name because it's a
- # django.utils.functional.__proxy__ object.
+ # The smart_unicode() is needed around opts.verbose_name_raw because it might
+ # be a django.utils.functional.__proxy__ object.
ct, created = self.model._default_manager.get_or_create(app_label=key[0],
- model=key[1], defaults={'name': str(opts.verbose_name)})
+ model=key[1], defaults={'name': smart_unicode(opts.verbose_name_raw)})
CONTENT_TYPE_CACHE[key] = ct
return ct
-
+
def clear_cache(self):
"""
Clear out the content-type cache. This needs to happen during database
@@ -42,7 +43,7 @@ class ContentType(models.Model):
ordering = ('name',)
unique_together = (('app_label', 'model'),)
- def __str__(self):
+ def __unicode__(self):
return self.name
def model_class(self):
diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py
index 24f2e68f46..fa4fb76714 100644
--- a/django/contrib/databrowse/datastructures.py
+++ b/django/contrib/databrowse/datastructures.py
@@ -7,6 +7,7 @@ from django.db import models
from django.utils import dateformat
from django.utils.text import capfirst
from django.utils.translation import get_date_formats
+from django.utils.encoding import smart_unicode, smart_str, iri_to_uri
EMPTY_VALUE = '(None)'
@@ -19,7 +20,7 @@ class EasyModel(object):
self.verbose_name_plural = model._meta.verbose_name_plural
def __repr__(self):
- return '<EasyModel for %s>' % self.model._meta.object_name
+ return '<EasyModel for %s>' % smart_str(self.model._meta.object_name)
def model_databrowse(self):
"Returns the ModelDatabrowse class for this model."
@@ -54,7 +55,7 @@ class EasyField(object):
self.model, self.field = easy_model, field
def __repr__(self):
- return '<EasyField for %s.%s>' % (self.model.model._meta.object_name, self.field.name)
+ return smart_str(u'<EasyField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
def choices(self):
for value, label in self.field.choices:
@@ -72,29 +73,32 @@ class EasyChoice(object):
self.value, self.label = value, label
def __repr__(self):
- return '<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name)
+ return smart_str(u'<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, self.value)
+ 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))
class EasyInstance(object):
def __init__(self, easy_model, instance):
self.model, self.instance = easy_model, instance
def __repr__(self):
- return '<EasyInstance for %s (%s)>' % (self.model.model._meta.object_name, self.instance._get_pk_val())
+ return smart_str(u'<EasyInstance for %s (%s)>' % (self.model.model._meta.object_name, self.instance._get_pk_val()))
- def __str__(self):
- val = str(self.instance)
+ def __unicode__(self):
+ val = smart_unicode(self.instance)
if len(val) > 30:
- return val[:30] + '...'
+ return val[:30] + u'...'
return val
+ def __str__(self):
+ return self.__unicode__().encode('utf-8')
+
def pk(self):
return self.instance._get_pk_val()
def url(self):
- return '%s%s/%s/objects/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.pk())
+ return '%s%s/%s/objects/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, iri_to_uri(self.pk()))
def fields(self):
"""
@@ -126,7 +130,7 @@ class EasyInstanceField(object):
self.raw_value = getattr(instance.instance, field.name)
def __repr__(self):
- return '<EasyInstanceField for %s.%s>' % (self.model.model._meta.object_name, self.field.name)
+ return smart_str(u'<EasyInstanceField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
def values(self):
"""
@@ -175,18 +179,18 @@ class EasyInstanceField(object):
if self.field.rel.to in self.model.model_list:
lst = []
for value in self.values():
- url = '%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, value._get_pk_val())
- lst.append((str(value), url))
+ url = '%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val()))
+ lst.append((smart_unicode(value), url))
else:
lst = [(value, None) for value in self.values()]
elif self.field.choices:
lst = []
for value in self.values():
- url = '%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, self.raw_value)
+ url = '%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value))
lst.append((value, url))
elif isinstance(self.field, models.URLField):
val = self.values()[0]
- lst = [(val, val)]
+ lst = [(val, iri_to_uri(val))]
else:
lst = [(self.values()[0], None)]
return lst
diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py
index 7977524cdb..651abd7695 100644
--- a/django/contrib/databrowse/plugins/calendars.py
+++ b/django/contrib/databrowse/plugins/calendars.py
@@ -6,6 +6,7 @@ from django.shortcuts import render_to_response
from django.utils.text import capfirst
from django.utils.translation import get_date_formats
from django.views.generic import date_based
+from django.utils.encoding import force_unicode
import datetime
import time
@@ -27,13 +28,13 @@ class CalendarPlugin(DatabrowsePlugin):
def model_index_html(self, request, model, site):
fields = self.field_dict(model)
if not fields:
- return ''
- return '<p class="filter"><strong>View calendar by:</strong> %s</p>' % \
- ', '.join(['<a href="calendars/%s/">%s</a>' % (f.name, capfirst(f.verbose_name)) for f in fields.values()])
+ return u''
+ return u'<p class="filter"><strong>View calendar by:</strong> %s</p>' % \
+ u', '.join(['<a href="calendars/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])
def urls(self, plugin_name, easy_instance_field):
if isinstance(easy_instance_field.field, models.DateField):
- return ['%s%s/%s/%s/%s/%s/' % (easy_instance_field.model.url(),
+ return [u'%s%s/%s/%s/%s/%s/' % (easy_instance_field.model.url(),
plugin_name, easy_instance_field.field.name,
easy_instance_field.raw_value.year,
easy_instance_field.raw_value.strftime('%b').lower(),
diff --git a/django/contrib/databrowse/plugins/fieldchoices.py b/django/contrib/databrowse/plugins/fieldchoices.py
index 49d17ff148..fc93ecaa63 100644
--- a/django/contrib/databrowse/plugins/fieldchoices.py
+++ b/django/contrib/databrowse/plugins/fieldchoices.py
@@ -4,9 +4,11 @@ from django.contrib.databrowse.datastructures import EasyModel
from django.contrib.databrowse.sites import DatabrowsePlugin
from django.shortcuts import render_to_response
from django.utils.text import capfirst
+from django.utils.encoding import smart_str, force_unicode
from django.views.generic import date_based
import datetime
import time
+import urllib
class FieldChoicePlugin(DatabrowsePlugin):
def __init__(self, field_filter=None):
@@ -29,15 +31,15 @@ class FieldChoicePlugin(DatabrowsePlugin):
def model_index_html(self, request, model, site):
fields = self.field_dict(model)
if not fields:
- return ''
- return '<p class="filter"><strong>View by:</strong> %s</p>' % \
- ', '.join(['<a href="fields/%s/">%s</a>' % (f.name, capfirst(f.verbose_name)) for f in fields.values()])
+ return u''
+ return u'<p class="filter"><strong>View by:</strong> %s</p>' % \
+ u', '.join(['<a href="fields/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])
def urls(self, plugin_name, easy_instance_field):
if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values():
- return ['%s%s/%s/%s/' % (easy_instance_field.model.url(),
+ return [u'%s%s/%s/%s/' % (easy_instance_field.model.url(),
plugin_name, easy_instance_field.field.name,
- easy_instance_field.raw_value)]
+ urllib.quote(smart_str(easy_instance_field.raw_value)))]
def model_view(self, request, model_databrowse, url):
self.model, self.site = model_databrowse.model, model_databrowse.site
diff --git a/django/contrib/databrowse/sites.py b/django/contrib/databrowse/sites.py
index 8521343e50..c07ec2129b 100644
--- a/django/contrib/databrowse/sites.py
+++ b/django/contrib/databrowse/sites.py
@@ -60,7 +60,7 @@ class ModelDatabrowse(object):
def main_view(self, request):
easy_model = EasyModel(self.site, self.model)
- html_snippets = '\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()])
+ html_snippets = u'\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()])
return render_to_response('databrowse/model_detail.html', {
'model': easy_model,
'root_url': self.site.root_url,
diff --git a/django/contrib/databrowse/templates/databrowse/fieldchoice_list.html b/django/contrib/databrowse/templates/databrowse/fieldchoice_list.html
index 686e6bc533..952e771af6 100644
--- a/django/contrib/databrowse/templates/databrowse/fieldchoice_list.html
+++ b/django/contrib/databrowse/templates/databrowse/fieldchoice_list.html
@@ -10,7 +10,7 @@
<ul class="objectlist">
{% for object in object_list %}
-<li class="{% cycle odd,even %}"><a href="{{ object }}/">{{ object|escape }}</a></li>
+<li class="{% cycle odd,even %}"><a href="{{ object|iriencode }}/">{{ object|escape }}</a></li>
{% endfor %}
</ul>
diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py
index 8d0b2c0d90..c9e9090d7d 100644
--- a/django/contrib/flatpages/models.py
+++ b/django/contrib/flatpages/models.py
@@ -1,7 +1,7 @@
from django.core import validators
from django.db import models
from django.contrib.sites.models import Site
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ugettext_lazy as _
class FlatPage(models.Model):
url = models.CharField(_('URL'), maxlength=100, validator_list=[validators.isAlphaNumericURL], db_index=True,
@@ -26,8 +26,8 @@ class FlatPage(models.Model):
list_filter = ('sites',)
search_fields = ('url', 'title')
- def __str__(self):
- return "%s -- %s" % (self.url, self.title)
+ def __unicode__(self):
+ return u"%s -- %s" % (self.url, self.title)
def get_absolute_url(self):
return self.url
diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py
index a16cbcc9bb..699d9300b8 100644
--- a/django/contrib/humanize/templatetags/humanize.py
+++ b/django/contrib/humanize/templatetags/humanize.py
@@ -1,5 +1,5 @@
-from django.utils.translation import ngettext
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ungettext, ugettext as _
+from django.utils.encoding import force_unicode
from django import template
import re
@@ -16,8 +16,8 @@ def ordinal(value):
return value
t = (_('th'), _('st'), _('nd'), _('rd'), _('th'), _('th'), _('th'), _('th'), _('th'), _('th'))
if value % 100 in (11, 12, 13): # special case
- return "%d%s" % (value, t[0])
- return '%d%s' % (value, t[value % 10])
+ return u"%d%s" % (value, t[0])
+ return u'%d%s' % (value, t[value % 10])
register.filter(ordinal)
def intcomma(value):
@@ -25,8 +25,8 @@ def intcomma(value):
Converts an integer to a string containing commas every three digits.
For example, 3000 becomes '3,000' and 45000 becomes '45,000'.
"""
- orig = str(value)
- new = re.sub("^(-?\d+)(\d{3})", '\g<1>,\g<2>', str(value))
+ orig = force_unicode(value)
+ new = re.sub("^(-?\d+)(\d{3})", '\g<1>,\g<2>', orig)
if orig == new:
return new
else:
@@ -44,13 +44,13 @@ def intword(value):
return value
if value < 1000000000:
new_value = value / 1000000.0
- return ngettext('%(value).1f million', '%(value).1f million', new_value) % {'value': new_value}
+ return ungettext('%(value).1f million', '%(value).1f million', new_value) % {'value': new_value}
if value < 1000000000000:
new_value = value / 1000000000.0
- return ngettext('%(value).1f billion', '%(value).1f billion', new_value) % {'value': new_value}
+ return ungettext('%(value).1f billion', '%(value).1f billion', new_value) % {'value': new_value}
if value < 1000000000000000:
new_value = value / 1000000000000.0
- return ngettext('%(value).1f trillion', '%(value).1f trillion', new_value) % {'value': new_value}
+ return ungettext('%(value).1f trillion', '%(value).1f trillion', new_value) % {'value': new_value}
return value
register.filter(intword)
diff --git a/django/contrib/localflavor/au/forms.py b/django/contrib/localflavor/au/forms.py
index b81a903d13..33e883511b 100644
--- a/django/contrib/localflavor/au/forms.py
+++ b/django/contrib/localflavor/au/forms.py
@@ -5,7 +5,7 @@ Australian-specific Form helpers
from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
from django.newforms.util import smart_unicode
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
import re
PHONE_DIGITS_RE = re.compile(r'^(\d{10})$')
@@ -15,14 +15,14 @@ class AUPostCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(AUPostCodeField, self).__init__(r'^\d{4}$',
max_length=None, min_length=None,
- error_message=gettext(u'Enter a 4 digit post code.'),
- *args, **kwargs)
+ error_message=ugettext('Enter a 4 digit post code.'),
+ *args, **kwargs)
class AUPhoneNumberField(Field):
"""Australian phone number field."""
def clean(self, value):
- """Validate a phone number. Strips parentheses, whitespace and
- hyphens.
+ """
+ Validate a phone number. Strips parentheses, whitespace and hyphens.
"""
super(AUPhoneNumberField, self).clean(value)
if value in EMPTY_VALUES:
@@ -39,5 +39,5 @@ class AUStateSelect(Select):
choices.
"""
def __init__(self, attrs=None):
- from au_states import STATE_CHOICES # relative import
+ from au_states import STATE_CHOICES
super(AUStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
diff --git a/django/contrib/localflavor/br/forms.py b/django/contrib/localflavor/br/forms.py
index 3487787643..c7082c0f9e 100644
--- a/django/contrib/localflavor/br/forms.py
+++ b/django/contrib/localflavor/br/forms.py
@@ -6,7 +6,7 @@ BR-specific Form helpers
from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, CharField, Select, EMPTY_VALUES
from django.utils.encoding import smart_unicode
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
import re
phone_digits_re = re.compile(r'^(\d{2})[-\.]?(\d{4})[-\.]?(\d{4})$')
@@ -15,8 +15,8 @@ class BRZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(BRZipCodeField, self).__init__(r'^\d{5}-\d{3}$',
max_length=None, min_length=None,
- error_message=gettext('Enter a zip code in the format XXXXX-XXX.'),
- *args, **kwargs)
+ error_message=ugettext('Enter a zip code in the format XXXXX-XXX.'),
+ *args, **kwargs)
class BRPhoneNumberField(Field):
def clean(self, value):
@@ -27,7 +27,7 @@ class BRPhoneNumberField(Field):
m = phone_digits_re.search(value)
if m:
return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
- raise ValidationError(gettext(u'Phone numbers must be in XX-XXXX-XXXX format.'))
+ raise ValidationError(ugettext('Phone numbers must be in XX-XXXX-XXXX format.'))
class BRStateSelect(Select):
"""
@@ -35,7 +35,7 @@ class BRStateSelect(Select):
as its choices.
"""
def __init__(self, attrs=None):
- from br_states import STATE_CHOICES # relative import
+ from br_states import STATE_CHOICES
super(BRStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
@@ -69,9 +69,9 @@ class BRCPFField(CharField):
try:
int(value)
except ValueError:
- raise ValidationError(gettext("This field requires only numbers."))
+ raise ValidationError(ugettext("This field requires only numbers."))
if len(value) != 11:
- raise ValidationError(gettext("This field requires at most 11 digits or 14 characters."))
+ raise ValidationError(ugettext("This field requires at most 11 digits or 14 characters."))
orig_dv = value[-2:]
new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(10, 1, -1))])
@@ -81,7 +81,7 @@ class BRCPFField(CharField):
new_2dv = DV_maker(new_2dv % 11)
value = value[:-1] + str(new_2dv)
if value[-2:] != orig_dv:
- raise ValidationError(gettext("Invalid CPF number."))
+ raise ValidationError(ugettext("Invalid CPF number."))
return orig_value
@@ -103,7 +103,7 @@ class BRCNPJField(Field):
raise ValidationError("This field requires only numbers.")
if len(value) != 14:
raise ValidationError(
- gettext("This field requires at least 14 digits"))
+ ugettext("This field requires at least 14 digits"))
orig_dv = value[-2:]
new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(5, 1, -1) + range(9, 1, -1))])
@@ -113,7 +113,7 @@ class BRCNPJField(Field):
new_2dv = DV_maker(new_2dv % 11)
value = value[:-1] + str(new_2dv)
if value[-2:] != orig_dv:
- raise ValidationError(gettext("Invalid CNPJ number."))
+ raise ValidationError(ugettext("Invalid CNPJ number."))
return orig_value
diff --git a/django/contrib/localflavor/ch/ch_states.py b/django/contrib/localflavor/ch/ch_states.py
index e9bbcc6268..ba5934a4d4 100644
--- a/django/contrib/localflavor/ch/ch_states.py
+++ b/django/contrib/localflavor/ch/ch_states.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ugettext_lazy as _
STATE_CHOICES = (
('AG', _('Aargau')),
diff --git a/django/contrib/localflavor/ch/forms.py b/django/contrib/localflavor/ch/forms.py
index 51e52dc0e9..5e601f5d74 100644
--- a/django/contrib/localflavor/ch/forms.py
+++ b/django/contrib/localflavor/ch/forms.py
@@ -5,7 +5,7 @@ Swiss-specific Form helpers
from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
from django.utils.encoding import smart_unicode
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
import re
id_re = re.compile(r"^(?P<idnumber>\w{8})(?P<pos9>(\d{1}|<))(?P<checksum>\d{1})$")
@@ -15,7 +15,7 @@ class CHZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(CHZipCodeField, self).__init__(r'^\d{4}$',
max_length=None, min_length=None,
- error_message=gettext('Enter a zip code in the format XXXX.'),
+ error_message=ugettext('Enter a zip code in the format XXXX.'),
*args, **kwargs)
class CHPhoneNumberField(Field):
@@ -87,7 +87,7 @@ class CHIdentityCardNumberField(Field):
def clean(self, value):
super(CHIdentityCardNumberField, self).clean(value)
- error_msg = gettext('Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.')
+ error_msg = ugettext('Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.')
if value in EMPTY_VALUES:
return u''
diff --git a/django/contrib/localflavor/cl/forms.py b/django/contrib/localflavor/cl/forms.py
index 87c8093407..a737bcb5a7 100644
--- a/django/contrib/localflavor/cl/forms.py
+++ b/django/contrib/localflavor/cl/forms.py
@@ -4,7 +4,8 @@ Chile specific form helpers.
from django.newforms import ValidationError
from django.newforms.fields import RegexField, EMPTY_VALUES
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
+from django.utils.encoding import smart_unicode
class CLRutField(RegexField):
"""
@@ -18,12 +19,12 @@ class CLRutField(RegexField):
if 'strict' in kwargs:
del kwargs['strict']
super(CLRutField, self).__init__(r'^(\d{1,2}\.)?\d{3}\.\d{3}-[\dkK]$',
- error_message=gettext('Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'),
- *args, **kwargs)
+ error_message=ugettext('Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'),
+ *args, **kwargs)
else:
# In non-strict mode, accept RUTs that validate but do not exist in
# the real world.
- super(CLRutField, self).__init__(r'^[\d\.]{1,11}-?[\dkK]$', error_message=gettext(u'Enter valid a Chilean RUT'), *args, **kwargs)
+ super(CLRutField, self).__init__(r'^[\d\.]{1,11}-?[\dkK]$', error_message=ugettext('Enter valid a Chilean RUT'), *args, **kwargs)
def clean(self, value):
"""
@@ -49,14 +50,14 @@ class CLRutField(RegexField):
multi += 1
if multi == 8:
multi = 2
- return '0123456789K0'[11 - suma % 11]
+ return u'0123456789K0'[11 - suma % 11]
def _canonify(self, rut):
"""
Turns the RUT into one normalized format. Returns a (rut, verifier)
tuple.
"""
- rut = str(rut).replace(' ', '').replace('.', '').replace('-', '')
+ rut = smart_unicode(rut).replace(' ', '').replace('.', '').replace('-', '')
return rut[:-1], rut[-1]
def _format(self, code, verifier=None):
@@ -74,5 +75,5 @@ class CLRutField(RegexField):
else:
new_dot = pos - 3
code = code[:new_dot] + '.' + code[new_dot:]
- return '%s-%s' % (code, verifier)
+ return u'%s-%s' % (code, verifier)
diff --git a/django/contrib/localflavor/de/de_states.py b/django/contrib/localflavor/de/de_states.py
index c00fd2e293..2872a7871a 100644
--- a/django/contrib/localflavor/de/de_states.py
+++ b/django/contrib/localflavor/de/de_states.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ugettext_lazy as _
STATE_CHOICES = (
('BW', _('Baden-Wuerttemberg')),
diff --git a/django/contrib/localflavor/de/forms.py b/django/contrib/localflavor/de/forms.py
index 8e140c125f..1a0b8587a6 100644
--- a/django/contrib/localflavor/de/forms.py
+++ b/django/contrib/localflavor/de/forms.py
@@ -4,7 +4,7 @@ DE-specific Form helpers
from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
import re
id_re = re.compile(r"^(?P<residence>\d{10})(?P<origin>\w{1,3})[-\ ]?(?P<birthday>\d{7})[-\ ]?(?P<validity>\d{7})[-\ ]?(?P<checksum>\d{1})$")
@@ -13,15 +13,15 @@ class DEZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(DEZipCodeField, self).__init__(r'^\d{5}$',
max_length=None, min_length=None,
- error_message=gettext(u'Enter a zip code in the format XXXXX.'),
- *args, **kwargs)
+ error_message=ugettext('Enter a zip code in the format XXXXX.'),
+ *args, **kwargs)
class DEStateSelect(Select):
"""
A Select widget that uses a list of DE states as its choices.
"""
def __init__(self, attrs=None):
- from de_states import STATE_CHOICES # relative import
+ from de_states import STATE_CHOICES
super(DEStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
class DEIdentityCardNumberField(Field):
@@ -57,7 +57,7 @@ class DEIdentityCardNumberField(Field):
def clean(self, value):
super(DEIdentityCardNumberField, self).clean(value)
- error_msg = gettext(u'Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format.')
+ error_msg = ugettext('Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format.')
if value in EMPTY_VALUES:
return u''
match = re.match(id_re, value)
@@ -71,7 +71,7 @@ class DEIdentityCardNumberField(Field):
if residence == '0000000000' or birthday == '0000000' or validity == '0000000':
raise ValidationError(error_msg)
- all_digits = "%s%s%s%s" % (residence, birthday, validity, checksum)
+ all_digits = u"%s%s%s%s" % (residence, birthday, validity, checksum)
if not self.has_valid_checksum(residence) or not self.has_valid_checksum(birthday) or \
not self.has_valid_checksum(validity) or not self.has_valid_checksum(all_digits):
raise ValidationError(error_msg)
diff --git a/django/contrib/localflavor/fi/forms.py b/django/contrib/localflavor/fi/forms.py
index da5cb5f2a8..82e024f7b1 100644
--- a/django/contrib/localflavor/fi/forms.py
+++ b/django/contrib/localflavor/fi/forms.py
@@ -5,21 +5,21 @@ FI-specific Form helpers
import re
from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
class FIZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(FIZipCodeField, self).__init__(r'^\d{5}$',
max_length=None, min_length=None,
- error_message=gettext(u'Enter a zip code in the format XXXXX.'),
- *args, **kwargs)
+ error_message=ugettext('Enter a zip code in the format XXXXX.'),
+ *args, **kwargs)
class FIMunicipalitySelect(Select):
"""
A Select widget that uses a list of Finnish municipalities as its choices.
"""
def __init__(self, attrs=None):
- from fi_municipalities import MUNICIPALITY_CHOICES # relative import
+ from fi_municipalities import MUNICIPALITY_CHOICES
super(FIMunicipalitySelect, self).__init__(attrs, choices=MUNICIPALITY_CHOICES)
class FISocialSecurityNumber(Field):
@@ -37,9 +37,9 @@ class FISocialSecurityNumber(Field):
(?P<serial>(\d{3}))
(?P<checksum>[%s])$""" % checkmarks, value, re.VERBOSE | re.IGNORECASE)
if not result:
- raise ValidationError(gettext(u'Enter a valid Finnish social security number.'))
+ raise ValidationError(ugettext('Enter a valid Finnish social security number.'))
gd = result.groupdict()
checksum = int(gd['date'] + gd['serial'])
if checkmarks[checksum % len(checkmarks)] == gd['checksum'].upper():
return u'%s' % value.upper()
- raise ValidationError(gettext(u'Enter a valid Finnish social security number.'))
+ raise ValidationError(ugettext('Enter a valid Finnish social security number.'))
diff --git a/django/contrib/localflavor/fr/forms.py b/django/contrib/localflavor/fr/forms.py
index 550596b880..cc0a5db259 100644
--- a/django/contrib/localflavor/fr/forms.py
+++ b/django/contrib/localflavor/fr/forms.py
@@ -5,7 +5,7 @@ FR-specific Form helpers
from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
from django.utils.encoding import smart_unicode
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
import re
phone_digits_re = re.compile(r'^0\d(\s|\.)?(\d{2}(\s|\.)?){3}\d{2}$')
@@ -14,8 +14,8 @@ class FRZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(FRZipCodeField, self).__init__(r'^\d{5}$',
max_length=None, min_length=None,
- error_message=gettext(u'Enter a zip code in the format XXXXX.'),
- *args, **kwargs)
+ error_message=ugettext('Enter a zip code in the format XXXXX.'),
+ *args, **kwargs)
class FRPhoneNumberField(Field):
"""
@@ -39,6 +39,6 @@ class FRDepartmentSelect(Select):
A Select widget that uses a list of FR departments as its choices.
"""
def __init__(self, attrs=None):
- from fr_department import DEPARTMENT_ASCII_CHOICES # relative import
+ from fr_department import DEPARTMENT_ASCII_CHOICES
super(FRDepartmentSelect, self).__init__(attrs, choices=DEPARTMENT_ASCII_CHOICES)
diff --git a/django/contrib/localflavor/is_/forms.py b/django/contrib/localflavor/is_/forms.py
index 41727d8fa3..ef8799e29c 100644
--- a/django/contrib/localflavor/is_/forms.py
+++ b/django/contrib/localflavor/is_/forms.py
@@ -5,7 +5,8 @@ Iceland specific form helpers.
from django.newforms import ValidationError
from django.newforms.fields import RegexField, EMPTY_VALUES
from django.newforms.widgets import Select
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
+from django.utils.encoding import smart_unicode
class ISIdNumberField(RegexField):
"""
@@ -13,7 +14,7 @@ class ISIdNumberField(RegexField):
of Iceland has.
"""
def __init__(self, *args, **kwargs):
- error_msg = gettext(u'Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.')
+ error_msg = ugettext('Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.')
kwargs['min_length'],kwargs['max_length'] = 10,11
super(ISIdNumberField, self).__init__(r'^\d{6}(-| )?\d{4}$', error_message=error_msg, *args, **kwargs)
@@ -27,7 +28,7 @@ class ISIdNumberField(RegexField):
if self._validate(value):
return self._format(value)
else:
- raise ValidationError(gettext(u'The Icelandic identification number is not valid.'))
+ raise ValidationError(ugettext(u'The Icelandic identification number is not valid.'))
def _canonify(self, value):
"""
@@ -48,7 +49,7 @@ class ISIdNumberField(RegexField):
Takes in the value in canonical form and returns it in the common
display format.
"""
- return value[:6]+'-'+value[6:]
+ return smart_unicode(value[:6]+'-'+value[6:])
class ISPhoneNumberField(RegexField):
"""
diff --git a/django/contrib/localflavor/it/forms.py b/django/contrib/localflavor/it/forms.py
index bb1bb2e6a4..0b6eecec45 100644
--- a/django/contrib/localflavor/it/forms.py
+++ b/django/contrib/localflavor/it/forms.py
@@ -4,7 +4,7 @@ IT-specific Form helpers
from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
from django.utils.encoding import smart_unicode
from django.contrib.localflavor.it.util import ssn_check_digit, vat_number_check_digit
import re
@@ -13,15 +13,15 @@ class ITZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(ITZipCodeField, self).__init__(r'^\d{5}$',
max_length=None, min_length=None,
- error_message=gettext(u'Enter a valid zip code.'),
- *args, **kwargs)
+ error_message=ugettext('Enter a valid zip code.'),
+ *args, **kwargs)
class ITRegionSelect(Select):
"""
A Select widget that uses a list of IT regions as its choices.
"""
def __init__(self, attrs=None):
- from it_region import REGION_CHOICES # relative import
+ from it_region import REGION_CHOICES
super(ITRegionSelect, self).__init__(attrs, choices=REGION_CHOICES)
class ITProvinceSelect(Select):
@@ -29,7 +29,7 @@ class ITProvinceSelect(Select):
A Select widget that uses a list of IT regions as its choices.
"""
def __init__(self, attrs=None):
- from it_province import PROVINCE_CHOICES # relative import
+ from it_province import PROVINCE_CHOICES
super(ITProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES)
class ITSocialSecurityNumberField(RegexField):
@@ -38,7 +38,7 @@ class ITSocialSecurityNumberField(RegexField):
For reference see http://www.agenziaentrate.it/ and search for
'Informazioni sulla codificazione delle persone fisiche'.
"""
- err_msg = gettext(u'Enter a valid Social Security number.')
+ err_msg = ugettext(u'Enter a valid Social Security number.')
def __init__(self, *args, **kwargs):
super(ITSocialSecurityNumberField, self).__init__(r'^\w{3}\s*\w{3}\s*\w{5}\s*\w{5}$',
max_length=None, min_length=None, error_message=self.err_msg,
@@ -65,7 +65,7 @@ class ITVatNumberField(Field):
value = super(ITVatNumberField, self).clean(value)
if value == u'':
return value
- err_msg = gettext(u'Enter a valid VAT number.')
+ err_msg = ugettext(u'Enter a valid VAT number.')
try:
vat_number = int(value)
except ValueError:
diff --git a/django/contrib/localflavor/it/it_province.py b/django/contrib/localflavor/it/it_province.py
index 1867191b31..669ecd7f95 100644
--- a/django/contrib/localflavor/it/it_province.py
+++ b/django/contrib/localflavor/it/it_province.py
@@ -33,7 +33,7 @@ PROVINCE_CHOICES = (
('KR', 'Crotone'),
('CN', 'Cuneo'),
('EN', 'Enna'),
-# ('FM', 'Fermo'), # active starting from 2009
+# ('FM', 'Fermo'), # active starting from 2009
('FE', 'Ferrara'),
('FI', 'Firenze'),
('FG', 'Foggia'),
diff --git a/django/contrib/localflavor/it/util.py b/django/contrib/localflavor/it/util.py
index 49b607f160..c162ff7eff 100644
--- a/django/contrib/localflavor/it/util.py
+++ b/django/contrib/localflavor/it/util.py
@@ -1,23 +1,27 @@
+from django.utils.encoding import smart_str, smart_unicode
+
def ssn_check_digit(value):
"Calculate Italian social security number check digit."
ssn_even_chars = {
- '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
- 'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9,
- 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15, 'Q': 16, 'R': 17, 'S': 18,
- 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25
+ '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8,
+ '9': 9, 'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7,
+ 'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15,
+ 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23,
+ 'Y': 24, 'Z': 25
}
ssn_odd_chars = {
- '0': 1, '1': 0, '2': 5, '3': 7, '4': 9, '5': 13, '6': 15, '7': 17, '8': 19, '9': 21,
- 'A': 1, 'B': 0, 'C': 5, 'D': 7, 'E': 9, 'F': 13, 'G': 15, 'H': 17, 'I': 19, 'J': 21,
- 'K': 2, 'L': 4, 'M': 18, 'N': 20, 'O': 11, 'P': 3, 'Q': 6, 'R': 8, 'S': 12,
- 'T': 14, 'U': 16, 'V': 10, 'W': 22, 'X': 25, 'Y': 24, 'Z': 23
+ '0': 1, '1': 0, '2': 5, '3': 7, '4': 9, '5': 13, '6': 15, '7': 17, '8':
+ 19, '9': 21, 'A': 1, 'B': 0, 'C': 5, 'D': 7, 'E': 9, 'F': 13, 'G': 15,
+ 'H': 17, 'I': 19, 'J': 21, 'K': 2, 'L': 4, 'M': 18, 'N': 20, 'O': 11,
+ 'P': 3, 'Q': 6, 'R': 8, 'S': 12, 'T': 14, 'U': 16, 'V': 10, 'W': 22,
+ 'X': 25, 'Y': 24, 'Z': 23
}
# Chars from 'A' to 'Z'
ssn_check_digits = [chr(x) for x in range(65, 91)]
ssn = value.upper()
total = 0
- for i in range(0,15):
+ for i in range(0, 15):
try:
if i % 2 == 0:
total += ssn_odd_chars[ssn[i]]
@@ -30,11 +34,11 @@ def ssn_check_digit(value):
def vat_number_check_digit(vat_number):
"Calculate Italian VAT number check digit."
- normalized_vat_number = str(vat_number).zfill(10)
+ normalized_vat_number = smart_str(vat_number).zfill(10)
total = 0
for i in range(0, 10, 2):
total += int(normalized_vat_number[i])
for i in range(1, 11, 2):
quotient , remainder = divmod(int(normalized_vat_number[i]) * 2, 10)
total += quotient + remainder
- return str((10 - total % 10) % 10)
+ return smart_unicode((10 - total % 10) % 10)
diff --git a/django/contrib/localflavor/jp/forms.py b/django/contrib/localflavor/jp/forms.py
index 4d93a21227..682ffb8c31 100644
--- a/django/contrib/localflavor/jp/forms.py
+++ b/django/contrib/localflavor/jp/forms.py
@@ -4,7 +4,7 @@ JP-specific Form helpers
from django.core import validators
from django.newforms import ValidationError
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
from django.newforms.fields import RegexField, Select
import re
@@ -18,8 +18,8 @@ class JPPostalCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(JPPostalCodeField, self).__init__(r'^\d{3}-\d{4}$|^\d{7}$',
max_length=None, min_length=None,
- error_message=gettext(u'Enter a postal code in the format XXXXXXX or XXX-XXXX.'),
- *args, **kwargs)
+ error_message=ugettext('Enter a postal code in the format XXXXXXX or XXX-XXXX.'),
+ *args, **kwargs)
def clean(self, value):
"""
diff --git a/django/contrib/localflavor/jp/jp_prefectures.py b/django/contrib/localflavor/jp/jp_prefectures.py
index 72ac4f9474..f079fe6a3d 100644
--- a/django/contrib/localflavor/jp/jp_prefectures.py
+++ b/django/contrib/localflavor/jp/jp_prefectures.py
@@ -1,51 +1,51 @@
-from django.utils.translation import gettext_lazy as gettext_lazy
+from django.utils.translation import ugettext_lazy
JP_PREFECTURES = (
- ('hokkaido', gettext_lazy('Hokkaido'),),
- ('aomori', gettext_lazy('Aomori'),),
- ('iwate', gettext_lazy('Iwate'),),
- ('miyagi', gettext_lazy('Miyagi'),),
- ('akita', gettext_lazy('Akita'),),
- ('yamagata', gettext_lazy('Yamagata'),),
- ('fukushima', gettext_lazy('Fukushima'),),
- ('ibaraki', gettext_lazy('Ibaraki'),),
- ('tochigi', gettext_lazy('Tochigi'),),
- ('gunma', gettext_lazy('Gunma'),),
- ('saitama', gettext_lazy('Saitama'),),
- ('chiba', gettext_lazy('Chiba'),),
- ('tokyo', gettext_lazy('Tokyo'),),
- ('kanagawa', gettext_lazy('Kanagawa'),),
- ('yamanashi', gettext_lazy('Yamanashi'),),
- ('nagano', gettext_lazy('Nagano'),),
- ('niigata', gettext_lazy('Niigata'),),
- ('toyama', gettext_lazy('Toyama'),),
- ('ishikawa', gettext_lazy('Ishikawa'),),
- ('fukui', gettext_lazy('Fukui'),),
- ('gifu', gettext_lazy('Gifu'),),
- ('shizuoka', gettext_lazy('Shizuoka'),),
- ('aichi', gettext_lazy('Aichi'),),
- ('mie', gettext_lazy('Mie'),),
- ('shiga', gettext_lazy('Shiga'),),
- ('kyoto', gettext_lazy('Kyoto'),),
- ('osaka', gettext_lazy('Osaka'),),
- ('hyogo', gettext_lazy('Hyogo'),),
- ('nara', gettext_lazy('Nara'),),
- ('wakayama', gettext_lazy('Wakayama'),),
- ('tottori', gettext_lazy('Tottori'),),
- ('shimane', gettext_lazy('Shimane'),),
- ('okayama', gettext_lazy('Okayama'),),
- ('hiroshima', gettext_lazy('Hiroshima'),),
- ('yamaguchi', gettext_lazy('Yamaguchi'),),
- ('tokushima', gettext_lazy('Tokushima'),),
- ('kagawa', gettext_lazy('Kagawa'),),
- ('ehime', gettext_lazy('Ehime'),),
- ('kochi', gettext_lazy('Kochi'),),
- ('fukuoka', gettext_lazy('Fukuoka'),),
- ('saga', gettext_lazy('Saga'),),
- ('nagasaki', gettext_lazy('Nagasaki'),),
- ('kumamoto', gettext_lazy('Kumamoto'),),
- ('oita', gettext_lazy('Oita'),),
- ('miyazaki', gettext_lazy('Miyazaki'),),
- ('kagoshima', gettext_lazy('Kagoshima'),),
- ('okinawa', gettext_lazy('Okinawa'),),
+ ('hokkaido', ugettext_lazy('Hokkaido'),),
+ ('aomori', ugettext_lazy('Aomori'),),
+ ('iwate', ugettext_lazy('Iwate'),),
+ ('miyagi', ugettext_lazy('Miyagi'),),
+ ('akita', ugettext_lazy('Akita'),),
+ ('yamagata', ugettext_lazy('Yamagata'),),
+ ('fukushima', ugettext_lazy('Fukushima'),),
+ ('ibaraki', ugettext_lazy('Ibaraki'),),
+ ('tochigi', ugettext_lazy('Tochigi'),),
+ ('gunma', ugettext_lazy('Gunma'),),
+ ('saitama', ugettext_lazy('Saitama'),),
+ ('chiba', ugettext_lazy('Chiba'),),
+ ('tokyo', ugettext_lazy('Tokyo'),),
+ ('kanagawa', ugettext_lazy('Kanagawa'),),
+ ('yamanashi', ugettext_lazy('Yamanashi'),),
+ ('nagano', ugettext_lazy('Nagano'),),
+ ('niigata', ugettext_lazy('Niigata'),),
+ ('toyama', ugettext_lazy('Toyama'),),
+ ('ishikawa', ugettext_lazy('Ishikawa'),),
+ ('fukui', ugettext_lazy('Fukui'),),
+ ('gifu', ugettext_lazy('Gifu'),),
+ ('shizuoka', ugettext_lazy('Shizuoka'),),
+ ('aichi', ugettext_lazy('Aichi'),),
+ ('mie', ugettext_lazy('Mie'),),
+ ('shiga', ugettext_lazy('Shiga'),),
+ ('kyoto', ugettext_lazy('Kyoto'),),
+ ('osaka', ugettext_lazy('Osaka'),),
+ ('hyogo', ugettext_lazy('Hyogo'),),
+ ('nara', ugettext_lazy('Nara'),),
+ ('wakayama', ugettext_lazy('Wakayama'),),
+ ('tottori', ugettext_lazy('Tottori'),),
+ ('shimane', ugettext_lazy('Shimane'),),
+ ('okayama', ugettext_lazy('Okayama'),),
+ ('hiroshima', ugettext_lazy('Hiroshima'),),
+ ('yamaguchi', ugettext_lazy('Yamaguchi'),),
+ ('tokushima', ugettext_lazy('Tokushima'),),
+ ('kagawa', ugettext_lazy('Kagawa'),),
+ ('ehime', ugettext_lazy('Ehime'),),
+ ('kochi', ugettext_lazy('Kochi'),),
+ ('fukuoka', ugettext_lazy('Fukuoka'),),
+ ('saga', ugettext_lazy('Saga'),),
+ ('nagasaki', ugettext_lazy('Nagasaki'),),
+ ('kumamoto', ugettext_lazy('Kumamoto'),),
+ ('oita', ugettext_lazy('Oita'),),
+ ('miyazaki', ugettext_lazy('Miyazaki'),),
+ ('kagoshima', ugettext_lazy('Kagoshima'),),
+ ('okinawa', ugettext_lazy('Okinawa'),),
)
diff --git a/django/contrib/localflavor/no/forms.py b/django/contrib/localflavor/no/forms.py
index 22099005b9..a0d599324f 100644
--- a/django/contrib/localflavor/no/forms.py
+++ b/django/contrib/localflavor/no/forms.py
@@ -6,14 +6,14 @@ Norwegian-specific Form helpers
import re, datetime
from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
class NOZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(NOZipCodeField, self).__init__(r'^\d{4}$',
max_length=None, min_length=None,
- error_message=gettext(u'Enter a zip code in the format XXXX.'),
- *args, **kwargs)
+ error_message=ugettext('Enter a zip code in the format XXXX.'),
+ *args, **kwargs)
class NOMunicipalitySelect(Select):
"""
@@ -33,7 +33,7 @@ class NOSocialSecurityNumber(Field):
if value in EMPTY_VALUES:
return u''
- msg = gettext(u'Enter a valid Norwegian social security number.')
+ msg = ugettext(u'Enter a valid Norwegian social security number.')
if not re.match(r'^\d{11}$', value):
raise ValidationError(msg)
@@ -60,7 +60,7 @@ class NOSocialSecurityNumber(Field):
self.gender = 'F'
else:
self.gender = 'M'
-
+
digits = map(int, list(value))
weight_1 = [3, 7, 6, 1, 8, 9, 4, 5, 2, 1, 0]
weight_2 = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2, 1]
@@ -74,4 +74,4 @@ class NOSocialSecurityNumber(Field):
raise ValidationError(msg)
return value
-
+
diff --git a/django/contrib/localflavor/no/no_municipalities.py b/django/contrib/localflavor/no/no_municipalities.py
index d66fef514c..d6bacda275 100644
--- a/django/contrib/localflavor/no/no_municipalities.py
+++ b/django/contrib/localflavor/no/no_municipalities.py
@@ -1,4 +1,4 @@
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
"""
An alphabetical list of Norwegian municipalities (fylker) fro use as `choices`
in a formfield.
@@ -15,18 +15,18 @@ MUNICIPALITY_CHOICES = (
('hedmark', u'Hedmark'),
('hordaland', u'Hordaland'),
('janmayen', u'Jan Mayen'),
- ('moreogromsdal', u'Mre og Romsdal'),
- ('nordtrondelag', u'Nord-Trndelag'),
+ ('moreogromsdal', u'Møre og Romsdal'),
+ ('nordtrondelag', u'Nord-Trøndelag'),
('nordland', u'Nordland'),
('oppland', u'Oppland'),
('oslo', u'Oslo'),
('rogaland', u'Rogaland'),
('sognogfjordane', u'Sogn og Fjordane'),
('svalbard', u'Svalbard'),
- ('sortrondelag', u'Sr-Trndelag'),
+ ('sortrondelag', u'Sør-Trøndelag'),
('telemark', u'Telemark'),
('troms', u'Troms'),
('vestagder', u'Vest-Agder'),
('vestfold', u'Vestfold'),
- ('ostfold', u'stfold')
+ ('ostfold', u'Østfold')
)
diff --git a/django/contrib/localflavor/uk/forms.py b/django/contrib/localflavor/uk/forms.py
index ddd033e1e0..84d6c0e157 100644
--- a/django/contrib/localflavor/uk/forms.py
+++ b/django/contrib/localflavor/uk/forms.py
@@ -3,7 +3,7 @@ UK-specific Form helpers
"""
from django.newforms.fields import RegexField
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
class UKPostcodeField(RegexField):
"""
@@ -15,5 +15,5 @@ class UKPostcodeField(RegexField):
def __init__(self, *args, **kwargs):
super(UKPostcodeField, self).__init__(r'^(GIR 0AA|[A-PR-UWYZ]([0-9]{1,2}|([A-HIK-Y][0-9](|[0-9]|[ABEHMNPRVWXY]))|[0-9][A-HJKSTUW]) [0-9][ABD-HJLNP-UW-Z]{2})$',
max_length=None, min_length=None,
- error_message=gettext(u'Enter a postcode. A space is required between the two postcode parts.'),
+ error_message=ugettext(u'Enter a postcode. A space is required between the two postcode parts.'),
*args, **kwargs)
diff --git a/django/contrib/localflavor/us/forms.py b/django/contrib/localflavor/us/forms.py
index feda68291b..259a7f7058 100644
--- a/django/contrib/localflavor/us/forms.py
+++ b/django/contrib/localflavor/us/forms.py
@@ -5,7 +5,7 @@ USA-specific Form helpers
from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
from django.utils.encoding import smart_unicode
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
import re
phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$')
@@ -15,8 +15,8 @@ class USZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(USZipCodeField, self).__init__(r'^\d{5}(?:-\d{4})?$',
max_length=None, min_length=None,
- error_message=gettext(u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'),
- *args, **kwargs)
+ error_message=ugettext('Enter a zip code in the format XXXXX or XXXXX-XXXX.'),
+ *args, **kwargs)
class USPhoneNumberField(Field):
def clean(self, value):
@@ -38,17 +38,17 @@ class USSocialSecurityNumberField(Field):
* Conforms to the XXX-XX-XXXX format.
* No group consists entirely of zeroes.
* The leading group is not "666" (block "666" will never be allocated).
- * The number is not in the promotional block 987-65-4320 through 987-65-4329,
- which are permanently invalid.
+ * The number is not in the promotional block 987-65-4320 through
+ 987-65-4329, which are permanently invalid.
* The number is not one known to be invalid due to otherwise widespread
- promotional use or distribution (e.g., the Woolworth's number or the 1962
- promotional number).
+ promotional use or distribution (e.g., the Woolworth's number or the
+ 1962 promotional number).
"""
def clean(self, value):
super(USSocialSecurityNumberField, self).clean(value)
if value in EMPTY_VALUES:
return u''
- msg = gettext(u'Enter a valid U.S. Social Security number in XXX-XX-XXXX format.')
+ msg = ugettext('Enter a valid U.S. Social Security number in XXX-XX-XXXX format.')
match = re.match(ssn_re, value)
if not match:
raise ValidationError(msg)
@@ -75,7 +75,7 @@ class USStateField(Field):
abbreviation for the given state.
"""
def clean(self, value):
- from us_states import STATES_NORMALIZED # relative import
+ from us_states import STATES_NORMALIZED
super(USStateField, self).clean(value)
if value in EMPTY_VALUES:
return u''
@@ -95,5 +95,5 @@ class USStateSelect(Select):
A Select widget that uses a list of U.S. states/territories as its choices.
"""
def __init__(self, attrs=None):
- from us_states import STATE_CHOICES # relative import
+ from us_states import STATE_CHOICES
super(USStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py
index 4bb135cc32..5d1f0ff1fb 100644
--- a/django/contrib/markup/templatetags/markup.py
+++ b/django/contrib/markup/templatetags/markup.py
@@ -16,6 +16,7 @@ silently fail and return the un-marked-up text.
from django import template
from django.conf import settings
+from django.utils.encoding import smart_str, force_unicode
register = template.Library()
@@ -25,9 +26,9 @@ def textile(value):
except ImportError:
if settings.DEBUG:
raise template.TemplateSyntaxError, "Error in {% textile %} filter: The Python textile library isn't installed."
- return value
+ return force_unicode(value)
else:
- return textile.textile(value, encoding=settings.DEFAULT_CHARSET, output=settings.DEFAULT_CHARSET)
+ return force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8'))
def markdown(value):
try:
@@ -35,9 +36,9 @@ def markdown(value):
except ImportError:
if settings.DEBUG:
raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed."
- return value
+ return force_unicode(value)
else:
- return markdown.markdown(value)
+ return force_unicode(markdown.markdown(smart_str(value)))
def restructuredtext(value):
try:
@@ -45,11 +46,11 @@ def restructuredtext(value):
except ImportError:
if settings.DEBUG:
raise template.TemplateSyntaxError, "Error in {% restructuredtext %} filter: The Python docutils library isn't installed."
- return value
+ return force_unicode(value)
else:
docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {})
- parts = publish_parts(source=value, writer_name="html4css1", settings_overrides=docutils_settings)
- return parts["fragment"]
+ parts = publish_parts(source=smart_str(value), writer_name="html4css1", settings_overrides=docutils_settings)
+ return force_unicode(parts["fragment"])
register.filter(textile)
register.filter(markdown)
diff --git a/django/contrib/redirects/models.py b/django/contrib/redirects/models.py
index 60205e29f3..6a3f5c9ff5 100644
--- a/django/contrib/redirects/models.py
+++ b/django/contrib/redirects/models.py
@@ -1,6 +1,6 @@
from django.db import models
from django.contrib.sites.models import Site
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ugettext_lazy as _
class Redirect(models.Model):
site = models.ForeignKey(Site, radio_admin=models.VERTICAL)
@@ -20,5 +20,5 @@ class Redirect(models.Model):
list_filter = ('site',)
search_fields = ('old_path', 'new_path')
- def __str__(self):
- return "%s ---> %s" % (self.old_path, self.new_path)
+ def __unicode__(self):
+ return u"%s ---> %s" % (self.old_path, self.new_path)
diff --git a/django/contrib/sessions/models.py b/django/contrib/sessions/models.py
index 521a2abee9..d2df17284c 100644
--- a/django/contrib/sessions/models.py
+++ b/django/contrib/sessions/models.py
@@ -1,7 +1,7 @@
import base64, md5, random, sys, datetime, os, time
import cPickle as pickle
from django.db import models
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ugettext_lazy as _
from django.conf import settings
class SessionManager(models.Manager):
diff --git a/django/contrib/sitemaps/views.py b/django/contrib/sitemaps/views.py
index d615c8e661..86ef1e3526 100644
--- a/django/contrib/sitemaps/views.py
+++ b/django/contrib/sitemaps/views.py
@@ -2,6 +2,7 @@ from django.http import HttpResponse, Http404
from django.template import loader
from django.contrib.sites.models import Site
from django.core import urlresolvers
+from django.utils.encoding import smart_str
def index(request, sitemaps):
current_site = Site.objects.get_current()
@@ -26,5 +27,5 @@ def sitemap(request, sitemaps, section=None):
urls.extend(site().get_urls())
else:
urls.extend(site.get_urls())
- xml = loader.render_to_string('sitemap.xml', {'urlset': urls})
+ xml = smart_str(loader.render_to_string('sitemap.xml', {'urlset': urls}))
return HttpResponse(xml, mimetype='application/xml')
diff --git a/django/contrib/sites/models.py b/django/contrib/sites/models.py
index df93c543d2..276c58474c 100644
--- a/django/contrib/sites/models.py
+++ b/django/contrib/sites/models.py
@@ -1,5 +1,5 @@
from django.db import models
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ugettext_lazy as _
class SiteManager(models.Manager):
def get_current(self):
@@ -19,5 +19,5 @@ class Site(models.Model):
list_display = ('domain', 'name')
search_fields = ('domain', 'name')
- def __str__(self):
+ def __unicode__(self):
return self.domain
diff --git a/django/contrib/syndication/feeds.py b/django/contrib/syndication/feeds.py
index af00bdc3e9..428767ae48 100644
--- a/django/contrib/syndication/feeds.py
+++ b/django/contrib/syndication/feeds.py
@@ -2,10 +2,13 @@ from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.template import Context, loader, Template, TemplateDoesNotExist
from django.contrib.sites.models import Site
from django.utils import feedgenerator
+from django.utils.encoding import smart_unicode
from django.conf import settings
def add_domain(domain, url):
if not url.startswith('http://'):
+ # 'url' must already be ASCII and URL-quoted, so no need for encoding
+ # conversions here.
url = u'http://%s%s' % (domain, url)
return url
@@ -97,9 +100,9 @@ class Feed(object):
enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
if enc_url:
enc = feedgenerator.Enclosure(
- url = enc_url.decode('utf-8'),
- length = str(self.__get_dynamic_attr('item_enclosure_length', item)).decode('utf-8'),
- mime_type = self.__get_dynamic_attr('item_enclosure_mime_type', item).decode('utf-8'),
+ url = smart_unicode(enc_url),
+ length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
+ mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
)
author_name = self.__get_dynamic_attr('item_author_name', item)
if author_name is not None:
@@ -108,9 +111,9 @@ class Feed(object):
else:
author_email = author_link = None
feed.add_item(
- title = title_tmp.render(Context({'obj': item, 'site': current_site})).decode('utf-8'),
+ title = title_tmp.render(Context({'obj': item, 'site': current_site})),
link = link,
- description = description_tmp.render(Context({'obj': item, 'site': current_site})).decode('utf-8'),
+ description = description_tmp.render(Context({'obj': item, 'site': current_site})),
unique_id = link,
enclosure = enc,
pubdate = self.__get_dynamic_attr('item_pubdate', item),
diff --git a/django/contrib/webdesign/lorem_ipsum.py b/django/contrib/webdesign/lorem_ipsum.py
index 6226bbef1b..4ad175d033 100644
--- a/django/contrib/webdesign/lorem_ipsum.py
+++ b/django/contrib/webdesign/lorem_ipsum.py
@@ -5,8 +5,40 @@ Utility functions for generating "lorem ipsum" Latin text.
import random
COMMON_P = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
-WORDS = ('exercitationem', 'perferendis', 'perspiciatis', 'laborum', 'eveniet', 'sunt', 'iure', 'nam', 'nobis', 'eum', 'cum', 'officiis', 'excepturi', 'odio', 'consectetur', 'quasi', 'aut', 'quisquam', 'vel', 'eligendi', 'itaque', 'non', 'odit', 'tempore', 'quaerat', 'dignissimos', 'facilis', 'neque', 'nihil', 'expedita', 'vitae', 'vero', 'ipsum', 'nisi', 'animi', 'cumque', 'pariatur', 'velit', 'modi', 'natus', 'iusto', 'eaque', 'sequi', 'illo', 'sed', 'ex', 'et', 'voluptatibus', 'tempora', 'veritatis', 'ratione', 'assumenda', 'incidunt', 'nostrum', 'placeat', 'aliquid', 'fuga', 'provident', 'praesentium', 'rem', 'necessitatibus', 'suscipit', 'adipisci', 'quidem', 'possimus', 'voluptas', 'debitis', 'sint', 'accusantium', 'unde', 'sapiente', 'voluptate', 'qui', 'aspernatur', 'laudantium', 'soluta', 'amet', 'quo', 'aliquam', 'saepe', 'culpa', 'libero', 'ipsa', 'dicta', 'reiciendis', 'nesciunt', 'doloribus', 'autem', 'impedit', 'minima', 'maiores', 'repudiandae', 'ipsam', 'obcaecati', 'ullam', 'enim', 'totam', 'delectus', 'ducimus', 'quis', 'voluptates', 'dolores', 'molestiae', 'harum', 'dolorem', 'quia', 'voluptatem', 'molestias', 'magni', 'distinctio', 'omnis', 'illum', 'dolorum', 'voluptatum', 'ea', 'quas', 'quam', 'corporis', 'quae', 'blanditiis', 'atque', 'deserunt', 'laboriosam', 'earum', 'consequuntur', 'hic', 'cupiditate', 'quibusdam', 'accusamus', 'ut', 'rerum', 'error', 'minus', 'eius', 'ab', 'ad', 'nemo', 'fugit', 'officia', 'at', 'in', 'id', 'quos', 'reprehenderit', 'numquam', 'iste', 'fugiat', 'sit', 'inventore', 'beatae', 'repellendus', 'magnam', 'recusandae', 'quod', 'explicabo', 'doloremque', 'aperiam', 'consequatur', 'asperiores', 'commodi', 'optio', 'dolor', 'labore', 'temporibus', 'repellat', 'veniam', 'architecto', 'est', 'esse', 'mollitia', 'nulla', 'a', 'similique', 'eos', 'alias', 'dolore', 'tenetur', 'deleniti', 'porro', 'facere', 'maxime', 'corrupti')
-COMMON_WORDS = ('lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipisicing', 'elit', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua')
+
+WORDS = ('exercitationem', 'perferendis', 'perspiciatis', 'laborum', 'eveniet',
+ 'sunt', 'iure', 'nam', 'nobis', 'eum', 'cum', 'officiis', 'excepturi',
+ 'odio', 'consectetur', 'quasi', 'aut', 'quisquam', 'vel', 'eligendi',
+ 'itaque', 'non', 'odit', 'tempore', 'quaerat', 'dignissimos',
+ 'facilis', 'neque', 'nihil', 'expedita', 'vitae', 'vero', 'ipsum',
+ 'nisi', 'animi', 'cumque', 'pariatur', 'velit', 'modi', 'natus',
+ 'iusto', 'eaque', 'sequi', 'illo', 'sed', 'ex', 'et', 'voluptatibus',
+ 'tempora', 'veritatis', 'ratione', 'assumenda', 'incidunt', 'nostrum',
+ 'placeat', 'aliquid', 'fuga', 'provident', 'praesentium', 'rem',
+ 'necessitatibus', 'suscipit', 'adipisci', 'quidem', 'possimus',
+ 'voluptas', 'debitis', 'sint', 'accusantium', 'unde', 'sapiente',
+ 'voluptate', 'qui', 'aspernatur', 'laudantium', 'soluta', 'amet',
+ 'quo', 'aliquam', 'saepe', 'culpa', 'libero', 'ipsa', 'dicta',
+ 'reiciendis', 'nesciunt', 'doloribus', 'autem', 'impedit', 'minima',
+ 'maiores', 'repudiandae', 'ipsam', 'obcaecati', 'ullam', 'enim',
+ 'totam', 'delectus', 'ducimus', 'quis', 'voluptates', 'dolores',
+ 'molestiae', 'harum', 'dolorem', 'quia', 'voluptatem', 'molestias',
+ 'magni', 'distinctio', 'omnis', 'illum', 'dolorum', 'voluptatum', 'ea',
+ 'quas', 'quam', 'corporis', 'quae', 'blanditiis', 'atque', 'deserunt',
+ 'laboriosam', 'earum', 'consequuntur', 'hic', 'cupiditate',
+ 'quibusdam', 'accusamus', 'ut', 'rerum', 'error', 'minus', 'eius',
+ 'ab', 'ad', 'nemo', 'fugit', 'officia', 'at', 'in', 'id', 'quos',
+ 'reprehenderit', 'numquam', 'iste', 'fugiat', 'sit', 'inventore',
+ 'beatae', 'repellendus', 'magnam', 'recusandae', 'quod', 'explicabo',
+ 'doloremque', 'aperiam', 'consequatur', 'asperiores', 'commodi',
+ 'optio', 'dolor', 'labore', 'temporibus', 'repellat', 'veniam',
+ 'architecto', 'est', 'esse', 'mollitia', 'nulla', 'a', 'similique',
+ 'eos', 'alias', 'dolore', 'tenetur', 'deleniti', 'porro', 'facere',
+ 'maxime', 'corrupti')
+
+COMMON_WORDS = ('lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur',
+ 'adipisicing', 'elit', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt',
+ 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua')
def sentence():
"""
@@ -17,10 +49,10 @@ def sentence():
"""
# Determine the number of comma-separated sections and number of words in
# each section for this sentence.
- sections = [' '.join(random.sample(WORDS, random.randint(3, 12))) for i in range(random.randint(1, 5))]
- s = ', '.join(sections)
+ sections = [u' '.join(random.sample(WORDS, random.randint(3, 12))) for i in range(random.randint(1, 5))]
+ s = u', '.join(sections)
# Convert to sentence case and add end punctuation.
- return '%s%s%s' % (s[0].upper(), s[1:], random.choice('?.'))
+ return u'%s%s%s' % (s[0].upper(), s[1:], random.choice('?.'))
def paragraph():
"""
@@ -28,7 +60,7 @@ def paragraph():
The paragraph consists of between 1 and 4 sentences, inclusive.
"""
- return ' '.join([sentence() for i in range(random.randint(1, 4))])
+ return u' '.join([sentence() for i in range(random.randint(1, 4))])
def paragraphs(count, common=True):
"""
@@ -66,4 +98,4 @@ def words(count, common=True):
word_list += random.sample(WORDS, c)
else:
word_list = word_list[:count]
- return ' '.join(word_list)
+ return u' '.join(word_list)
diff --git a/django/contrib/webdesign/templatetags/webdesign.py b/django/contrib/webdesign/templatetags/webdesign.py
index e5117093f8..2370659f1d 100644
--- a/django/contrib/webdesign/templatetags/webdesign.py
+++ b/django/contrib/webdesign/templatetags/webdesign.py
@@ -18,7 +18,7 @@ class LoremNode(template.Node):
paras = paragraphs(count, common=self.common)
if self.method == 'p':
paras = ['<p>%s</p>' % p for p in paras]
- return '\n\n'.join(paras)
+ return u'\n\n'.join(paras)
#@register.tag
def lorem(parser, token):
diff --git a/django/contrib/webdesign/tests.py b/django/contrib/webdesign/tests.py
index d20ebf110a..eeb1956a01 100644
--- a/django/contrib/webdesign/tests.py
+++ b/django/contrib/webdesign/tests.py
@@ -2,7 +2,7 @@
r"""
>>> words(7)
-'lorem ipsum dolor sit amet consectetur adipisicing'
+u'lorem ipsum dolor sit amet consectetur adipisicing'
>>> paragraphs(1)
['Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.']
@@ -14,4 +14,4 @@ import datetime
if __name__ == '__main__':
import doctest
- doctest.testmod() \ No newline at end of file
+ doctest.testmod()
diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py
index 6370cab47c..e26f234fe5 100644
--- a/django/core/handlers/modpython.py
+++ b/django/core/handlers/modpython.py
@@ -49,7 +49,7 @@ class ModPythonRequest(http.HttpRequest):
if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):
self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
else:
- self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
+ self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
def _get_request(self):
if not hasattr(self, '_request'):
@@ -58,7 +58,7 @@ class ModPythonRequest(http.HttpRequest):
def _get_get(self):
if not hasattr(self, '_get'):
- self._get = http.QueryDict(self._req.args)
+ self._get = http.QueryDict(self._req.args, encoding=self._encoding)
return self._get
def _set_get(self, get):
@@ -160,7 +160,7 @@ class ModPythonHandler(BaseHandler):
req.content_type = response['Content-Type']
for key, value in response.headers.items():
if key != 'Content-Type':
- req.headers_out[key] = value
+ req.headers_out[str(key)] = str(value)
for c in response.cookies.values():
req.headers_out.add('Set-Cookie', c.output(header=''))
req.status = response.status_code
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
index 4320b69627..49938e25dc 100644
--- a/django/core/handlers/wsgi.py
+++ b/django/core/handlers/wsgi.py
@@ -113,9 +113,9 @@ class WSGIRequest(http.HttpRequest):
header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '')
self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
else:
- self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
+ self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
else:
- self._post, self._files = http.QueryDict(''), datastructures.MultiValueDict()
+ self._post, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict()
def _get_request(self):
if not hasattr(self, '_request'):
@@ -125,7 +125,7 @@ class WSGIRequest(http.HttpRequest):
def _get_get(self):
if not hasattr(self, '_get'):
# The WSGI spec says 'QUERY_STRING' may be absent.
- self._get = http.QueryDict(self.environ.get('QUERY_STRING', ''))
+ self._get = http.QueryDict(self.environ.get('QUERY_STRING', ''), encoding=self._encoding)
return self._get
def _set_get(self, get):
@@ -200,8 +200,8 @@ class WSGIHandler(BaseHandler):
except KeyError:
status_text = 'UNKNOWN STATUS CODE'
status = '%s %s' % (response.status_code, status_text)
- response_headers = response.headers.items()
+ response_headers = [(str(k), str(v)) for k, v in response.headers.items()]
for c in response.cookies.values():
- response_headers.append(('Set-Cookie', c.output(header='')))
+ response_headers.append(('Set-Cookie', str(c.output(header=''))))
start_response(status, response_headers)
return response
diff --git a/django/core/mail.py b/django/core/mail.py
index 2f252df724..96a32ed62d 100644
--- a/django/core/mail.py
+++ b/django/core/mail.py
@@ -3,12 +3,13 @@ Tools for sending email.
"""
from django.conf import settings
+from django.utils.encoding import smart_str, force_unicode
from email import Charset, Encoders
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.Header import Header
-from email.Utils import formatdate
+from email.Utils import formatdate, parseaddr, formataddr
import mimetypes
import os
import smtplib
@@ -67,8 +68,18 @@ class SafeMIMEText(MIMEText):
"Forbids multi-line headers, to prevent header injection."
if '\n' in val or '\r' in val:
raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name)
- if name == "Subject":
- val = Header(val, settings.DEFAULT_CHARSET)
+ try:
+ val = str(force_unicode(val))
+ except UnicodeEncodeError:
+ if name.lower() in ('to', 'from', 'cc'):
+ result = []
+ for item in val.split(', '):
+ nm, addr = parseaddr(item)
+ nm = str(Header(nm, settings.DEFAULT_CHARSET))
+ result.append(formataddr((nm, str(addr))))
+ val = ', '.join(result)
+ else:
+ val = Header(force_unicode(val), settings.DEFAULT_CHARSET)
MIMEText.__setitem__(self, name, val)
class SafeMIMEMultipart(MIMEMultipart):
@@ -76,8 +87,18 @@ class SafeMIMEMultipart(MIMEMultipart):
"Forbids multi-line headers, to prevent header injection."
if '\n' in val or '\r' in val:
raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name)
- if name == "Subject":
- val = Header(val, settings.DEFAULT_CHARSET)
+ try:
+ val = str(force_unicode(val))
+ except UnicodeEncodeError:
+ if name.lower() in ('to', 'from', 'cc'):
+ result = []
+ for item in val.split(', '):
+ nm, addr = parseaddr(item)
+ nm = str(Header(nm, settings.DEFAULT_CHARSET))
+ result.append(formataddr((nm, str(addr))))
+ val = ', '.join(result)
+ else:
+ val = Header(force_unicode(val), settings.DEFAULT_CHARSET)
MIMEMultipart.__setitem__(self, name, val)
class SMTPConnection(object):
@@ -176,6 +197,14 @@ class EmailMessage(object):
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
connection=None, attachments=None, headers=None):
+ """
+ Initialise a single email message (which can be sent to multiple
+ recipients).
+
+ All strings used to create the message can be unicode strings (or UTF-8
+ bytestrings). The SafeMIMEText class will handle any necessary encoding
+ conversions.
+ """
self.to = to or []
self.bcc = bcc or []
self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
@@ -192,7 +221,7 @@ class EmailMessage(object):
def message(self):
encoding = self.encoding or settings.DEFAULT_CHARSET
- msg = SafeMIMEText(self.body, self.content_subtype, encoding)
+ msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET), self.content_subtype, encoding)
if self.attachments:
body_msg = msg
msg = SafeMIMEMultipart(_subtype=self.multipart_subtype)
diff --git a/django/core/management.py b/django/core/management.py
index e617bd2136..3713c3d443 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -427,11 +427,11 @@ def get_custom_sql_for_model(model):
for sql_file in sql_files:
if os.path.exists(sql_file):
fp = open(sql_file, 'U')
- for statement in statements.split(fp.read()):
+ for statement in statements.split(fp.read().decode(settings.FILE_CHARSET)):
# Remove any comments from the file
- statement = re.sub(r"--.*[\n\Z]", "", statement)
+ statement = re.sub(ur"--.*[\n\Z]", "", statement)
if statement.strip():
- output.append(statement + ";")
+ output.append(statement + u";")
fp.close()
return output
diff --git a/django/core/serializers/__init__.py b/django/core/serializers/__init__.py
index 494393f3cf..049edf7521 100644
--- a/django/core/serializers/__init__.py
+++ b/django/core/serializers/__init__.py
@@ -6,7 +6,7 @@ Usage::
>>> from django.core import serializers
>>> json = serializers.serialize("json", some_query_set)
>>> objects = list(serializers.deserialize("json", json))
-
+
To add your own serializers, use the SERIALIZATION_MODULES setting::
SERIALIZATION_MODULES = {
@@ -30,19 +30,19 @@ try:
import yaml
BUILTIN_SERIALIZERS["yaml"] = "django.core.serializers.pyyaml"
except ImportError:
- pass
+ pass
_serializers = {}
-
+
def register_serializer(format, serializer_module):
"""Register a new serializer by passing in a module name."""
module = __import__(serializer_module, {}, {}, [''])
_serializers[format] = module
-
+
def unregister_serializer(format):
"""Unregister a given serializer"""
del _serializers[format]
-
+
def get_serializer(format):
if not _serializers:
_load_serializers()
@@ -52,12 +52,12 @@ def get_serializer_formats():
if not _serializers:
_load_serializers()
return _serializers.keys()
-
+
def get_deserializer(format):
if not _serializers:
_load_serializers()
return _serializers[format].Deserializer
-
+
def serialize(format, queryset, **options):
"""
Serialize a queryset (or any iterator that returns database objects) using
@@ -87,4 +87,4 @@ def _load_serializers():
register_serializer(format, BUILTIN_SERIALIZERS[format])
if hasattr(settings, "SERIALIZATION_MODULES"):
for format in settings.SERIALIZATION_MODULES:
- register_serializer(format, settings.SERIALIZATION_MODULES[format]) \ No newline at end of file
+ register_serializer(format, settings.SERIALIZATION_MODULES[format])
diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py
index 86d0037c17..1ef7bee472 100644
--- a/django/core/serializers/base.py
+++ b/django/core/serializers/base.py
@@ -7,6 +7,7 @@ try:
except ImportError:
from StringIO import StringIO
from django.db import models
+from django.utils.encoding import smart_str, smart_unicode
class SerializationError(Exception):
"""Something bad happened during serialization."""
@@ -59,7 +60,7 @@ class Serializer(object):
value = getattr(obj, "get_%s_url" % field.name, lambda: None)()
else:
value = field.flatten_data(follow=None, obj=obj).get(field.name, "")
- return str(value)
+ return smart_unicode(value)
def start_serialization(self):
"""
@@ -154,7 +155,7 @@ class DeserializedObject(object):
self.m2m_data = m2m_data
def __repr__(self):
- return "<DeserializedObject: %s>" % str(self.object)
+ return "<DeserializedObject: %s>" % smart_str(self.object)
def save(self, save_m2m=True):
self.object.save()
diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py
index 5fbb3163f7..f61a2fa4a2 100644
--- a/django/core/serializers/python.py
+++ b/django/core/serializers/python.py
@@ -7,33 +7,34 @@ other serializers.
from django.conf import settings
from django.core.serializers import base
from django.db import models
+from django.utils.encoding import smart_unicode
class Serializer(base.Serializer):
"""
Serializes a QuerySet to basic Python objects.
"""
-
+
def start_serialization(self):
self._current = None
self.objects = []
-
+
def end_serialization(self):
pass
-
+
def start_object(self, obj):
self._current = {}
-
+
def end_object(self, obj):
self.objects.append({
- "model" : str(obj._meta),
- "pk" : str(obj._get_pk_val()),
+ "model" : smart_unicode(obj._meta),
+ "pk" : smart_unicode(obj._get_pk_val()),
"fields" : self._current
})
self._current = None
-
+
def handle_field(self, obj, field):
self._current[field.name] = getattr(obj, field.name)
-
+
def handle_fk_field(self, obj, field):
related = getattr(obj, field.name)
if related is not None:
@@ -44,17 +45,17 @@ class Serializer(base.Serializer):
# Related to remote object via other field
related = getattr(related, field.rel.field_name)
self._current[field.name] = related
-
+
def handle_m2m_field(self, obj, field):
self._current[field.name] = [related._get_pk_val() for related in getattr(obj, field.name).iterator()]
-
+
def getvalue(self):
return self.objects
def Deserializer(object_list, **options):
"""
Deserialize simple Python objects back into Django ORM instances.
-
+
It's expected that you pass the Python objects themselves (instead of a
stream or a string) to the constructor
"""
@@ -64,36 +65,30 @@ def Deserializer(object_list, **options):
Model = _get_model(d["model"])
data = {Model._meta.pk.attname : Model._meta.pk.to_python(d["pk"])}
m2m_data = {}
-
+
# Handle each field
for (field_name, field_value) in d["fields"].iteritems():
- if isinstance(field_value, unicode):
- field_value = field_value.encode(options.get("encoding", settings.DEFAULT_CHARSET))
-
+ if isinstance(field_value, str):
+ field_value = smart_unicode(field_value, options.get("encoding", settings.DEFAULT_CHARSET), strings_only=True)
+
field = Model._meta.get_field(field_name)
-
+
# Handle M2M relations
if field.rel and isinstance(field.rel, models.ManyToManyRel):
- pks = []
m2m_convert = field.rel.to._meta.pk.to_python
- for pk in field_value:
- if isinstance(pk, unicode):
- pks.append(m2m_convert(pk.encode(options.get("encoding", settings.DEFAULT_CHARSET))))
- else:
- pks.append(m2m_convert(pk))
- m2m_data[field.name] = pks
-
+ m2m_data[field.name] = [m2m_convert(smart_unicode(pk)) for pk in field_value]
+
# Handle FK fields
elif field.rel and isinstance(field.rel, models.ManyToOneRel):
if field_value:
data[field.attname] = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value)
else:
data[field.attname] = None
-
+
# Handle all other fields
else:
data[field.name] = field.to_python(field_value)
-
+
yield base.DeserializedObject(Model(**data), m2m_data)
def _get_model(model_identifier):
@@ -105,5 +100,5 @@ def _get_model(model_identifier):
except TypeError:
Model = None
if Model is None:
- raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier)
+ raise base.DeserializationError(u"Invalid model identifier: '%s'" % model_identifier)
return Model
diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py
index d3444280c5..92159dbbe3 100644
--- a/django/core/serializers/pyyaml.py
+++ b/django/core/serializers/pyyaml.py
@@ -21,7 +21,7 @@ class Serializer(PythonSerializer):
self.options.pop('stream', None)
self.options.pop('fields', None)
yaml.dump(self.objects, self.stream, **self.options)
-
+
def getvalue(self):
return self.stream.getvalue()
@@ -35,4 +35,4 @@ def Deserializer(stream_or_string, **options):
stream = stream_or_string
for obj in PythonDeserializer(yaml.load(stream)):
yield obj
-
+
diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py
index 3e4a6f3e79..50bce929b3 100644
--- a/django/core/serializers/xml_serializer.py
+++ b/django/core/serializers/xml_serializer.py
@@ -6,13 +6,14 @@ from django.conf import settings
from django.core.serializers import base
from django.db import models
from django.utils.xmlutils import SimplerXMLGenerator
+from django.utils.encoding import smart_unicode
from xml.dom import pulldom
class Serializer(base.Serializer):
"""
Serializes a QuerySet to XML.
"""
-
+
def indent(self, level):
if self.options.get('indent', None) is not None:
self.xml.ignorableWhitespace('\n' + ' ' * self.options.get('indent', None) * level)
@@ -24,7 +25,7 @@ class Serializer(base.Serializer):
self.xml = SimplerXMLGenerator(self.stream, self.options.get("encoding", settings.DEFAULT_CHARSET))
self.xml.startDocument()
self.xml.startElement("django-objects", {"version" : "1.0"})
-
+
def end_serialization(self):
"""
End serialization -- end the document.
@@ -32,27 +33,27 @@ class Serializer(base.Serializer):
self.indent(0)
self.xml.endElement("django-objects")
self.xml.endDocument()
-
+
def start_object(self, obj):
"""
Called as each object is handled.
"""
if not hasattr(obj, "_meta"):
raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj))
-
+
self.indent(1)
self.xml.startElement("object", {
- "pk" : str(obj._get_pk_val()),
- "model" : str(obj._meta),
+ "pk" : smart_unicode(obj._get_pk_val()),
+ "model" : smart_unicode(obj._meta),
})
-
+
def end_object(self, obj):
"""
Called after handling all fields for an object.
"""
self.indent(1)
self.xml.endElement("object")
-
+
def handle_field(self, obj, field):
"""
Called to handle each field on an object (except for ForeignKeys and
@@ -63,17 +64,17 @@ class Serializer(base.Serializer):
"name" : field.name,
"type" : field.get_internal_type()
})
-
+
# Get a "string version" of the object's data (this is handled by the
- # serializer base class).
+ # serializer base class).
if getattr(obj, field.name) is not None:
value = self.get_string_value(obj, field)
- self.xml.characters(str(value))
+ self.xml.characters(smart_unicode(value))
else:
self.xml.addQuickElement("None")
self.xml.endElement("field")
-
+
def handle_fk_field(self, obj, field):
"""
Called to handle a ForeignKey (we need to treat them slightly
@@ -88,11 +89,11 @@ class Serializer(base.Serializer):
else:
# Related to remote object via other field
related = getattr(related, field.rel.field_name)
- self.xml.characters(str(related))
+ self.xml.characters(smart_unicode(related))
else:
self.xml.addQuickElement("None")
self.xml.endElement("field")
-
+
def handle_m2m_field(self, obj, field):
"""
Called to handle a ManyToManyField. Related objects are only
@@ -101,9 +102,9 @@ class Serializer(base.Serializer):
"""
self._start_relational_field(field)
for relobj in getattr(obj, field.name).iterator():
- self.xml.addQuickElement("object", attrs={"pk" : str(relobj._get_pk_val())})
+ self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
self.xml.endElement("field")
-
+
def _start_relational_field(self, field):
"""
Helper to output the <field> element for relational fields
@@ -112,33 +113,33 @@ class Serializer(base.Serializer):
self.xml.startElement("field", {
"name" : field.name,
"rel" : field.rel.__class__.__name__,
- "to" : str(field.rel.to._meta),
+ "to" : smart_unicode(field.rel.to._meta),
})
-
+
class Deserializer(base.Deserializer):
"""
Deserialize XML.
"""
-
+
def __init__(self, stream_or_string, **options):
super(Deserializer, self).__init__(stream_or_string, **options)
- self.encoding = self.options.get("encoding", settings.DEFAULT_CHARSET)
- self.event_stream = pulldom.parse(self.stream)
-
+ self.event_stream = pulldom.parse(self.stream)
+
def next(self):
for event, node in self.event_stream:
if event == "START_ELEMENT" and node.nodeName == "object":
self.event_stream.expandNode(node)
return self._handle_object(node)
raise StopIteration
-
+
def _handle_object(self, node):
"""
Convert an <object> node to a DeserializedObject.
"""
- # Look up the model using the model loading mechanism. If this fails, bail.
+ # Look up the model using the model loading mechanism. If this fails,
+ # bail.
Model = self._get_model_from_node(node, "model")
-
+
# Start building a data dictionary from the object. If the node is
# missing the pk attribute, bail.
pk = node.getAttribute("pk")
@@ -146,11 +147,11 @@ class Deserializer(base.Deserializer):
raise base.DeserializationError("<object> node is missing the 'pk' attribute")
data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)}
-
+
# Also start building a dict of m2m data (this is saved as
# {m2m_accessor_attribute : [list_of_related_objects]})
m2m_data = {}
-
+
# Deseralize each field.
for field_node in node.getElementsByTagName("field"):
# If the field is missing the name attribute, bail (are you
@@ -158,12 +159,12 @@ class Deserializer(base.Deserializer):
field_name = field_node.getAttribute("name")
if not field_name:
raise base.DeserializationError("<field> node is missing the 'name' attribute")
-
+
# Get the field from the Model. This will raise a
# FieldDoesNotExist if, well, the field doesn't exist, which will
# be propagated correctly.
field = Model._meta.get_field(field_name)
-
+
# As is usually the case, relation fields get the special treatment.
if field.rel and isinstance(field.rel, models.ManyToManyRel):
m2m_data[field.name] = self._handle_m2m_field_node(field_node, field)
@@ -173,12 +174,12 @@ class Deserializer(base.Deserializer):
if len(field_node.childNodes) == 1 and field_node.childNodes[0].nodeName == 'None':
value = None
else:
- value = field.to_python(getInnerText(field_node).strip().encode(self.encoding))
+ value = field.to_python(getInnerText(field_node).strip())
data[field.name] = value
-
+
# Return a DeserializedObject so that the m2m data has a place to live.
return base.DeserializedObject(Model(**data), m2m_data)
-
+
def _handle_fk_field_node(self, node, field):
"""
Handle a <field> node for a ForeignKey
@@ -188,16 +189,16 @@ class Deserializer(base.Deserializer):
return None
else:
return field.rel.to._meta.get_field(field.rel.field_name).to_python(
- getInnerText(node).strip().encode(self.encoding))
-
+ getInnerText(node).strip())
+
def _handle_m2m_field_node(self, node, field):
"""
- Handle a <field> node for a ManyToManyField
+ Handle a <field> node for a ManyToManyField.
"""
return [field.rel.to._meta.pk.to_python(
- c.getAttribute("pk").encode(self.encoding))
+ c.getAttribute("pk"))
for c in node.getElementsByTagName("object")]
-
+
def _get_model_from_node(self, node, attr):
"""
Helper to look up a model from a <object model=...> or a <field
@@ -217,8 +218,8 @@ class Deserializer(base.Deserializer):
"<%s> node has invalid model identifier: '%s'" % \
(node.nodeName, model_identifier))
return Model
-
-
+
+
def getInnerText(node):
"""
Get all the inner text of a DOM node (recursively).
@@ -232,4 +233,5 @@ def getInnerText(node):
inner_text.extend(getInnerText(child))
else:
pass
- return "".join(inner_text) \ No newline at end of file
+ return u"".join(inner_text)
+
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index f4c2dc4677..000a56a726 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -9,6 +9,7 @@ a string) and returns a tuple in this format:
from django.http import Http404
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
+from django.utils.encoding import iri_to_uri
from django.utils.functional import memoize
import re
@@ -37,14 +38,20 @@ def get_callable(lookup_view, can_fail=False):
If can_fail is True, lookup_view might be a URL pattern label, so errors
during the import fail and the string is returned.
"""
- if not callable(lookup_view):
- mod_name, func_name = get_mod_func(lookup_view)
- try:
- if func_name != '':
- lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
- except (ImportError, AttributeError):
- if not can_fail:
- raise
+ try:
+ # Bail out early if lookup_view is not ASCII. This can't be a function.
+ lookup_view = lookup_view.encode('ascii')
+
+ if not callable(lookup_view):
+ mod_name, func_name = get_mod_func(lookup_view)
+ try:
+ if func_name != '':
+ lookup_view = getattr(__import__(mod_name, {}, {}, ['']), func_name)
+ except (ImportError, AttributeError):
+ if not can_fail:
+ raise
+ except UnicodeEncodeError:
+ pass
return lookup_view
get_callable = memoize(get_callable, _callable_cache)
@@ -265,7 +272,7 @@ class RegexURLResolver(object):
except (ImportError, AttributeError):
raise NoReverseMatch
if lookup_view in self.reverse_dict:
- return ''.join([reverse_helper(part.regex, *args, **kwargs) for part in self.reverse_dict[lookup_view]])
+ return u''.join([reverse_helper(part.regex, *args, **kwargs) for part in self.reverse_dict[lookup_view]])
raise NoReverseMatch
def reverse_helper(self, lookup_view, *args, **kwargs):
@@ -279,5 +286,5 @@ def resolve(path, urlconf=None):
def reverse(viewname, urlconf=None, args=None, kwargs=None):
args = args or []
kwargs = kwargs or {}
- return '/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs)
+ return iri_to_uri(u'/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs))
diff --git a/django/core/validators.py b/django/core/validators.py
index 293f0e1a8c..4e39157de8 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -10,8 +10,9 @@ form field is required.
import urllib2
from django.conf import settings
-from django.utils.translation import gettext, gettext_lazy, ngettext
+from django.utils.translation import ugettext as _, ugettext_lazy, ungettext
from django.utils.functional import Promise, lazy
+from django.utils.encoding import force_unicode
import re
_datere = r'\d{4}-\d{1,2}-\d{1,2}'
@@ -32,16 +33,17 @@ phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNO
slug_re = re.compile(r'^[-\w]+$')
url_re = re.compile(r'^https?://\S+$')
-lazy_inter = lazy(lambda a,b: str(a) % b, str)
+lazy_inter = lazy(lambda a,b: force_unicode(a) % b, unicode)
class ValidationError(Exception):
def __init__(self, message):
"ValidationError can be passed a string or a list."
if isinstance(message, list):
- self.messages = message
+ self.messages = [force_unicode(msg) for msg in message]
else:
assert isinstance(message, (basestring, Promise)), ("%s should be a string" % repr(message))
- self.messages = [message]
+ self.messages = [force_unicode(message)]
+
def __str__(self):
# This is needed because, without a __str__(), printing an exception
# instance would result in this:
@@ -53,39 +55,40 @@ class CriticalValidationError(Exception):
def __init__(self, message):
"ValidationError can be passed a string or a list."
if isinstance(message, list):
- self.messages = message
+ self.messages = [force_unicode(msg) for msg in message]
else:
assert isinstance(message, (basestring, Promise)), ("'%s' should be a string" % message)
- self.messages = [message]
+ self.messages = [force_unicode(message)]
+
def __str__(self):
return str(self.messages)
def isAlphaNumeric(field_data, all_data):
if not alnum_re.search(field_data):
- raise ValidationError, gettext("This value must contain only letters, numbers and underscores.")
+ raise ValidationError, _("This value must contain only letters, numbers and underscores.")
def isAlphaNumericURL(field_data, all_data):
if not alnumurl_re.search(field_data):
- raise ValidationError, gettext("This value must contain only letters, numbers, underscores, dashes or slashes.")
+ raise ValidationError, _("This value must contain only letters, numbers, underscores, dashes or slashes.")
def isSlug(field_data, all_data):
if not slug_re.search(field_data):
- raise ValidationError, gettext("This value must contain only letters, numbers, underscores or hyphens.")
+ raise ValidationError, _("This value must contain only letters, numbers, underscores or hyphens.")
def isLowerCase(field_data, all_data):
if field_data.lower() != field_data:
- raise ValidationError, gettext("Uppercase letters are not allowed here.")
+ raise ValidationError, _("Uppercase letters are not allowed here.")
def isUpperCase(field_data, all_data):
if field_data.upper() != field_data:
- raise ValidationError, gettext("Lowercase letters are not allowed here.")
+ raise ValidationError, _("Lowercase letters are not allowed here.")
def isCommaSeparatedIntegerList(field_data, all_data):
for supposed_int in field_data.split(','):
try:
int(supposed_int)
except ValueError:
- raise ValidationError, gettext("Enter only digits separated by commas.")
+ raise ValidationError, _("Enter only digits separated by commas.")
def isCommaSeparatedEmailList(field_data, all_data):
"""
@@ -97,32 +100,32 @@ def isCommaSeparatedEmailList(field_data, all_data):
try:
isValidEmail(supposed_email.strip(), '')
except ValidationError:
- raise ValidationError, gettext("Enter valid e-mail addresses separated by commas.")
+ raise ValidationError, _("Enter valid e-mail addresses separated by commas.")
def isValidIPAddress4(field_data, all_data):
if not ip4_re.search(field_data):
- raise ValidationError, gettext("Please enter a valid IP address.")
+ raise ValidationError, _("Please enter a valid IP address.")
def isNotEmpty(field_data, all_data):
if field_data.strip() == '':
- raise ValidationError, gettext("Empty values are not allowed here.")
+ raise ValidationError, _("Empty values are not allowed here.")
def isOnlyDigits(field_data, all_data):
if not field_data.isdigit():
- raise ValidationError, gettext("Non-numeric characters aren't allowed here.")
+ raise ValidationError, _("Non-numeric characters aren't allowed here.")
def isNotOnlyDigits(field_data, all_data):
if field_data.isdigit():
- raise ValidationError, gettext("This value can't be comprised solely of digits.")
+ raise ValidationError, _("This value can't be comprised solely of digits.")
def isInteger(field_data, all_data):
# This differs from isOnlyDigits because this accepts the negative sign
if not integer_re.search(field_data):
- raise ValidationError, gettext("Enter a whole number.")
+ raise ValidationError, _("Enter a whole number.")
def isOnlyLetters(field_data, all_data):
if not field_data.isalpha():
- raise ValidationError, gettext("Only alphabetical characters are allowed here.")
+ raise ValidationError, _("Only alphabetical characters are allowed here.")
def _isValidDate(date_string):
"""
@@ -137,30 +140,30 @@ def _isValidDate(date_string):
# This check is needed because strftime is used when saving the date
# value to the database, and strftime requires that the year be >=1900.
if year < 1900:
- raise ValidationError, gettext('Year must be 1900 or later.')
+ raise ValidationError, _('Year must be 1900 or later.')
try:
date(year, month, day)
except ValueError, e:
- msg = gettext('Invalid date: %s') % gettext(str(e))
+ msg = _('Invalid date: %s') % _(str(e))
raise ValidationError, msg
def isValidANSIDate(field_data, all_data):
if not ansi_date_re.search(field_data):
- raise ValidationError, gettext('Enter a valid date in YYYY-MM-DD format.')
+ raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
_isValidDate(field_data)
def isValidANSITime(field_data, all_data):
if not ansi_time_re.search(field_data):
- raise ValidationError, gettext('Enter a valid time in HH:MM format.')
+ raise ValidationError, _('Enter a valid time in HH:MM format.')
def isValidANSIDatetime(field_data, all_data):
if not ansi_datetime_re.search(field_data):
- raise ValidationError, gettext('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
+ raise ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
_isValidDate(field_data.split()[0])
def isValidEmail(field_data, all_data):
if not email_re.search(field_data):
- raise ValidationError, gettext('Enter a valid e-mail address.')
+ raise ValidationError, _('Enter a valid e-mail address.')
def isValidImage(field_data, all_data):
"""
@@ -172,22 +175,22 @@ def isValidImage(field_data, all_data):
try:
content = field_data['content']
except TypeError:
- raise ValidationError, gettext("No file was submitted. Check the encoding type on the form.")
+ raise ValidationError, _("No file was submitted. Check the encoding type on the form.")
try:
Image.open(StringIO(content))
except IOError: # Python Imaging Library doesn't recognize it as an image
- raise ValidationError, gettext("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
+ raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
def isValidImageURL(field_data, all_data):
uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
try:
uc(field_data, all_data)
except URLMimeTypeCheck.InvalidContentType:
- raise ValidationError, gettext("The URL %s does not point to a valid image.") % field_data
+ raise ValidationError, _("The URL %s does not point to a valid image.") % field_data
def isValidPhone(field_data, all_data):
if not phone_re.search(field_data):
- raise ValidationError, gettext('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data
+ raise ValidationError, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data
def isValidQuicktimeVideoURL(field_data, all_data):
"Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)"
@@ -195,11 +198,11 @@ def isValidQuicktimeVideoURL(field_data, all_data):
try:
uc(field_data, all_data)
except URLMimeTypeCheck.InvalidContentType:
- raise ValidationError, gettext("The URL %s does not point to a valid QuickTime video.") % field_data
+ raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data
def isValidURL(field_data, all_data):
if not url_re.search(field_data):
- raise ValidationError, gettext("A valid URL is required.")
+ raise ValidationError, _("A valid URL is required.")
def isValidHTML(field_data, all_data):
import urllib, urllib2
@@ -213,14 +216,14 @@ def isValidHTML(field_data, all_data):
return
from xml.dom.minidom import parseString
error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')]
- raise ValidationError, gettext("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
+ raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
def isWellFormedXml(field_data, all_data):
from xml.dom.minidom import parseString
try:
parseString(field_data)
except Exception, e: # Naked except because we're not sure what will be thrown
- raise ValidationError, gettext("Badly formed XML: %s") % str(e)
+ raise ValidationError, _("Badly formed XML: %s") % str(e)
def isWellFormedXmlFragment(field_data, all_data):
isWellFormedXml('<root>%s</root>' % field_data, all_data)
@@ -250,7 +253,7 @@ def isValidUSState(field_data, all_data):
"Checks that the given string is a valid two-letter U.S. state abbreviation"
states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY']
if field_data.upper() not in states:
- raise ValidationError, gettext("Enter a valid U.S. state abbreviation.")
+ raise ValidationError, _("Enter a valid U.S. state abbreviation.")
def hasNoProfanities(field_data, all_data):
"""
@@ -264,14 +267,14 @@ def hasNoProfanities(field_data, all_data):
if words_seen:
from django.utils.text import get_text_list
plural = len(words_seen) > 1
- raise ValidationError, ngettext("Watch your mouth! The word %s is not allowed here.",
+ raise ValidationError, ungettext("Watch your mouth! The word %s is not allowed here.",
"Watch your mouth! The words %s are not allowed here.", plural) % \
get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], 'and')
class AlwaysMatchesOtherField(object):
def __init__(self, other_field_name, error_message=None):
self.other = other_field_name
- self.error_message = error_message or lazy_inter(gettext_lazy("This field must match the '%s' field."), self.other)
+ self.error_message = error_message or lazy_inter(ugettext_lazy("This field must match the '%s' field."), self.other)
self.always_test = True
def __call__(self, field_data, all_data):
@@ -290,7 +293,7 @@ class ValidateIfOtherFieldEquals(object):
v(field_data, all_data)
class RequiredIfOtherFieldNotGiven(object):
- def __init__(self, other_field_name, error_message=gettext_lazy("Please enter something for at least one field.")):
+ def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter something for at least one field.")):
self.other, self.error_message = other_field_name, error_message
self.always_test = True
@@ -299,7 +302,7 @@ class RequiredIfOtherFieldNotGiven(object):
raise ValidationError, self.error_message
class RequiredIfOtherFieldsGiven(object):
- def __init__(self, other_field_names, error_message=gettext_lazy("Please enter both fields or leave them both empty.")):
+ def __init__(self, other_field_names, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
self.other, self.error_message = other_field_names, error_message
self.always_test = True
@@ -310,7 +313,7 @@ class RequiredIfOtherFieldsGiven(object):
class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven):
"Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list."
- def __init__(self, other_field_name, error_message=gettext_lazy("Please enter both fields or leave them both empty.")):
+ def __init__(self, other_field_name, error_message=ugettext_lazy("Please enter both fields or leave them both empty.")):
RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message)
class RequiredIfOtherFieldEquals(object):
@@ -318,7 +321,7 @@ class RequiredIfOtherFieldEquals(object):
self.other_field = other_field
self.other_value = other_value
other_label = other_label or other_value
- self.error_message = error_message or lazy_inter(gettext_lazy("This field must be given if %(field)s is %(value)s"), {
+ self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is %(value)s"), {
'field': other_field, 'value': other_label})
self.always_test = True
@@ -331,7 +334,7 @@ class RequiredIfOtherFieldDoesNotEqual(object):
self.other_field = other_field
self.other_value = other_value
other_label = other_label or other_value
- self.error_message = error_message or lazy_inter(gettext_lazy("This field must be given if %(field)s is not %(value)s"), {
+ self.error_message = error_message or lazy_inter(ugettext_lazy("This field must be given if %(field)s is not %(value)s"), {
'field': other_field, 'value': other_label})
self.always_test = True
@@ -350,7 +353,7 @@ class IsLessThanOtherField(object):
class UniqueAmongstFieldsWithPrefix(object):
def __init__(self, field_name, prefix, error_message):
self.field_name, self.prefix = field_name, prefix
- self.error_message = error_message or gettext_lazy("Duplicate values are not allowed.")
+ self.error_message = error_message or ugettext_lazy("Duplicate values are not allowed.")
def __call__(self, field_data, all_data):
for field_name, value in all_data.items():
@@ -365,11 +368,11 @@ class NumberIsInRange(object):
self.lower, self.upper = lower, upper
if not error_message:
if lower and upper:
- self.error_message = gettext("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper}
+ self.error_message = _("This value must be between %(lower)s and %(upper)s.") % {'lower': lower, 'upper': upper}
elif lower:
- self.error_message = gettext("This value must be at least %s.") % lower
+ self.error_message = _("This value must be at least %s.") % lower
elif upper:
- self.error_message = gettext("This value must be no more than %s.") % upper
+ self.error_message = _("This value must be no more than %s.") % upper
else:
self.error_message = error_message
@@ -405,7 +408,7 @@ class IsAPowerOf(object):
from math import log
val = log(int(field_data)) / log(self.power_of)
if val != int(val):
- raise ValidationError, gettext("This value must be a power of %s.") % self.power_of
+ raise ValidationError, _("This value must be a power of %s.") % self.power_of
class IsValidDecimal(object):
def __init__(self, max_digits, decimal_places):
@@ -414,19 +417,19 @@ class IsValidDecimal(object):
def __call__(self, field_data, all_data):
match = decimal_re.search(str(field_data))
if not match:
- raise ValidationError, gettext("Please enter a valid decimal number.")
+ raise ValidationError, _("Please enter a valid decimal number.")
digits = len(match.group('digits') or '')
decimals = len(match.group('decimals') or '')
if digits + decimals > self.max_digits:
- raise ValidationError, ngettext("Please enter a valid decimal number with at most %s total digit.",
+ raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.",
"Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
if digits > (self.max_digits - self.decimal_places):
- raise ValidationError, ngettext( "Please enter a valid decimal number with a whole part of at most %s digit.",
+ raise ValidationError, ungettext( "Please enter a valid decimal number with a whole part of at most %s digit.",
"Please enter a valid decimal number with a whole part of at most %s digits.", str(self.max_digits-self.decimal_places)) % str(self.max_digits-self.decimal_places)
if decimals > self.decimal_places:
- raise ValidationError, ngettext("Please enter a valid decimal number with at most %s decimal place.",
+ raise ValidationError, ungettext("Please enter a valid decimal number with at most %s decimal place.",
"Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places
def isValidFloat(field_data, all_data):
@@ -434,7 +437,7 @@ def isValidFloat(field_data, all_data):
try:
float(data)
except ValueError:
- raise ValidationError, gettext("Please enter a valid floating point number.")
+ raise ValidationError, ugettext("Please enter a valid floating point number.")
class HasAllowableSize(object):
"""
@@ -443,14 +446,14 @@ class HasAllowableSize(object):
"""
def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None):
self.min_size, self.max_size = min_size, max_size
- self.min_error_message = min_error_message or lazy_inter(gettext_lazy("Make sure your uploaded file is at least %s bytes big."), min_size)
- self.max_error_message = max_error_message or lazy_inter(gettext_lazy("Make sure your uploaded file is at most %s bytes big."), max_size)
+ self.min_error_message = min_error_message or lazy_inter(ugettext_lazy("Make sure your uploaded file is at least %s bytes big."), min_size)
+ self.max_error_message = max_error_message or lazy_inter(ugettext_lazy("Make sure your uploaded file is at most %s bytes big."), max_size)
def __call__(self, field_data, all_data):
try:
content = field_data['content']
except TypeError:
- raise ValidationError, gettext_lazy("No file was submitted. Check the encoding type on the form.")
+ raise ValidationError, ugettext_lazy("No file was submitted. Check the encoding type on the form.")
if self.min_size is not None and len(content) < self.min_size:
raise ValidationError, self.min_error_message
if self.max_size is not None and len(content) > self.max_size:
@@ -461,7 +464,7 @@ class MatchesRegularExpression(object):
Checks that the field matches the given regular-expression. The regex
should be in string format, not already compiled.
"""
- def __init__(self, regexp, error_message=gettext_lazy("The format for this field is wrong.")):
+ def __init__(self, regexp, error_message=ugettext_lazy("The format for this field is wrong.")):
self.regexp = re.compile(regexp)
self.error_message = error_message
@@ -476,7 +479,7 @@ class AnyValidator(object):
as a validation error. The message is rather unspecific, so it's best to
specify one on instantiation.
"""
- def __init__(self, validator_list=None, error_message=gettext_lazy("This field is invalid.")):
+ def __init__(self, validator_list=None, error_message=ugettext_lazy("This field is invalid.")):
if validator_list is None: validator_list = []
self.validator_list = validator_list
self.error_message = error_message
@@ -512,10 +515,10 @@ class URLMimeTypeCheck(object):
try:
info = urllib2.urlopen(field_data).info()
except (urllib2.HTTPError, urllib2.URLError):
- raise URLMimeTypeCheck.CouldNotRetrieve, gettext("Could not retrieve anything from %s.") % field_data
+ raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data
content_type = info['content-type']
if content_type not in self.mime_type_list:
- raise URLMimeTypeCheck.InvalidContentType, gettext("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
+ raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
'url': field_data, 'contenttype': content_type}
class RelaxNGCompact(object):
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index 4c64134118..25ae2fa84f 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -84,7 +84,7 @@ class DatabaseWrapper(local):
kwargs = {
'conv': django_conversions,
'charset': 'utf8',
- 'use_unicode': False,
+ 'use_unicode': True,
}
if settings.DATABASE_USER:
kwargs['user'] = settings.DATABASE_USER
diff --git a/django/db/backends/mysql_old/base.py b/django/db/backends/mysql_old/base.py
index ca9d2c8b50..0a5c127e81 100644
--- a/django/db/backends/mysql_old/base.py
+++ b/django/db/backends/mysql_old/base.py
@@ -91,6 +91,7 @@ class DatabaseWrapper(local):
'db': settings.DATABASE_NAME,
'passwd': settings.DATABASE_PASSWORD,
'conv': django_conversions,
+ 'use_unicode': True,
}
if settings.DATABASE_HOST.startswith('/'):
kwargs['unix_socket'] = settings.DATABASE_HOST
@@ -103,6 +104,7 @@ class DatabaseWrapper(local):
cursor = self.connection.cursor()
if self.connection.get_server_info() >= '4.1':
cursor.execute("SET NAMES 'utf8'")
+ cursor.execute("SET CHARACTER SET 'utf8'")
else:
cursor = self.connection.cursor()
if settings.DEBUG:
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 48b3c27355..f442e3ecdd 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -6,13 +6,18 @@ Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
from django.conf import settings
from django.db.backends import util
+from django.utils.datastructures import SortedDict
+from django.utils.encoding import smart_str, force_unicode
+import datetime
+import os
+
+# Oracle takes client-side character set encoding from the environment.
+os.environ['NLS_LANG'] = '.UTF8'
try:
import cx_Oracle as Database
except ImportError, e:
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured, "Error loading cx_Oracle module: %s" % e
-import datetime
-from django.utils.datastructures import SortedDict
DatabaseError = Database.Error
@@ -45,9 +50,9 @@ class DatabaseWrapper(local):
conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
self.connection = Database.connect(conn_string, **self.options)
cursor = FormatStylePlaceholderCursor(self.connection)
- # default arraysize of 1 is highly sub-optimal
+ # Default arraysize of 1 is highly sub-optimal.
cursor.arraysize = 100
- # set oracle date to ansi date format
+ # Set oracle date to ansi date format.
cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'")
cursor.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
if settings.DEBUG:
@@ -78,23 +83,22 @@ uses_case_insensitive_names = True
class FormatStylePlaceholderCursor(Database.Cursor):
"""
- Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var" style.
- This fixes it -- but note that if you want to use a literal "%s" in a query,
- you'll need to use "%%s".
+ Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var"
+ style. This fixes it -- but note that if you want to use a literal "%s" in
+ a query, you'll need to use "%%s".
+
+ We also do automatic conversion between Unicode on the Python side and
+ UTF-8 -- for talking to Oracle -- in here.
"""
+ charset = 'utf-8'
+
def _rewrite_args(self, query, params=None):
if params is None:
params = []
else:
- # cx_Oracle can't handle unicode parameters, so cast to str for now
- for i, param in enumerate(params):
- if type(param) == unicode:
- try:
- params[i] = param.encode('utf-8')
- except UnicodeError:
- params[i] = str(param)
+ params = self._format_params(params)
args = [(':arg%d' % i) for i in range(len(params))]
- query = query % tuple(args)
+ query = smart_str(query, self.charset) % tuple(args)
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
# it does want a trailing ';' but not a trailing '/'. However, these
# characters must be included in the original query in case the query
@@ -103,6 +107,16 @@ class FormatStylePlaceholderCursor(Database.Cursor):
query = query[:-1]
return query, params
+ def _format_params(self, params):
+ if isinstance(params, dict):
+ result = {}
+ charset = self.charset
+ for key, value in params.items():
+ result[smart_str(key, charset)] = smart_str(value, charset)
+ return result
+ else:
+ return tuple([smart_str(p, self.charset, True) for p in params])
+
def execute(self, query, params=None):
query, params = self._rewrite_args(query, params)
return Database.Cursor.execute(self, query, params)
@@ -111,6 +125,26 @@ class FormatStylePlaceholderCursor(Database.Cursor):
query, params = self._rewrite_args(query, params)
return Database.Cursor.executemany(self, query, params)
+ def fetchone(self):
+ return to_unicode(Database.Cursor.fetchone(self))
+
+ def fetchmany(self, size=None):
+ if size is None:
+ size = self.arraysize
+ return tuple([tuple([to_unicode(e) for e in r]) for r in Database.Cursor.fetchmany(self, size)])
+
+ def fetchall(self):
+ return tuple([tuple([to_unicode(e) for e in r]) for r in Database.Cursor.fetchall(self)])
+
+def to_unicode(s):
+ """
+ Convert strings to Unicode objects (and return all other data types
+ unchanged).
+ """
+ if isinstance(s, basestring):
+ return force_unicode(s)
+ return s
+
def quote_name(name):
# SQL92 requires delimited (quoted) names to be case-sensitive. When
# not quoted, Oracle has case-insensitive behavior for identifiers, but
diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py
index 6b1a30d55d..f7903bce4f 100644
--- a/django/db/backends/oracle/creation.py
+++ b/django/db/backends/oracle/creation.py
@@ -8,15 +8,15 @@ from django.core import management
DATA_TYPES = {
'AutoField': 'NUMBER(11)',
'BooleanField': 'NUMBER(1) CHECK (%(column)s IN (0,1))',
- 'CharField': 'VARCHAR2(%(maxlength)s)',
+ 'CharField': 'NVARCHAR2(%(maxlength)s)',
'CommaSeparatedIntegerField': 'VARCHAR2(%(maxlength)s)',
'DateField': 'DATE',
'DateTimeField': 'TIMESTAMP',
'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)',
- 'FileField': 'VARCHAR2(100)',
- 'FilePathField': 'VARCHAR2(100)',
+ 'FileField': 'NVARCHAR2(100)',
+ 'FilePathField': 'NVARCHAR2(100)',
'FloatField': 'DOUBLE PRECISION',
- 'ImageField': 'VARCHAR2(100)',
+ 'ImageField': 'NVARCHAR2(100)',
'IntegerField': 'NUMBER(11)',
'IPAddressField': 'VARCHAR2(15)',
'ManyToManyField': None,
@@ -25,7 +25,7 @@ DATA_TYPES = {
'PhoneNumberField': 'VARCHAR2(20)',
'PositiveIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)',
'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)',
- 'SlugField': 'VARCHAR2(50)',
+ 'SlugField': 'NVARCHAR2(50)',
'SmallIntegerField': 'NUMBER(11)',
'TextField': 'NCLOB',
'TimeField': 'TIMESTAMP',
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
index 611852e0dc..d90f0cc225 100644
--- a/django/db/backends/postgresql/base.py
+++ b/django/db/backends/postgresql/base.py
@@ -4,6 +4,7 @@ PostgreSQL database backend for Django.
Requires psycopg 1: http://initd.org/projects/psycopg1
"""
+from django.utils.encoding import smart_str, smart_unicode
from django.db.backends import util
try:
import psycopg as Database
@@ -21,11 +22,6 @@ except ImportError:
# Import copy of _thread_local.py from Python 2.4
from django.utils._threading_local import local
-def smart_basestring(s, charset):
- if isinstance(s, unicode):
- return s.encode(charset)
- return s
-
class UnicodeCursorWrapper(object):
"""
A thin wrapper around psycopg cursors that allows them to accept Unicode
@@ -33,18 +29,31 @@ class UnicodeCursorWrapper(object):
This is necessary because psycopg doesn't apply any DB quoting to
parameters that are Unicode strings. If a param is Unicode, this will
- convert it to a bytestring using DEFAULT_CHARSET before passing it to
- psycopg.
+ convert it to a bytestring using database client's encoding before passing
+ it to psycopg.
+
+ All results retrieved from the database are converted into Unicode strings
+ before being returned to the caller.
"""
def __init__(self, cursor, charset):
self.cursor = cursor
self.charset = charset
+ def format_params(self, params):
+ if isinstance(params, dict):
+ result = {}
+ charset = self.charset
+ for key, value in params.items():
+ result[smart_str(key, charset)] = smart_str(value, charset)
+ return result
+ else:
+ return tuple([smart_str(p, self.charset, True) for p in params])
+
def execute(self, sql, params=()):
- return self.cursor.execute(sql, [smart_basestring(p, self.charset) for p in params])
+ return self.cursor.execute(smart_str(sql, self.charset), self.format_params(params))
def executemany(self, sql, param_list):
- new_param_list = [tuple([smart_basestring(p, self.charset) for p in params]) for params in param_list]
+ new_param_list = [self.format_params(params) for params in param_list]
return self.cursor.executemany(sql, new_param_list)
def __getattr__(self, attr):
@@ -83,7 +92,8 @@ class DatabaseWrapper(local):
cursor = self.connection.cursor()
if set_tz:
cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
- cursor = UnicodeCursorWrapper(cursor, settings.DEFAULT_CHARSET)
+ cursor.execute("SET client_encoding to 'UNICODE'")
+ cursor = UnicodeCursorWrapper(cursor, 'utf-8')
global postgres_version
if not postgres_version:
cursor.execute("SELECT version()")
@@ -186,16 +196,17 @@ def get_sql_flush(style, tables, sequences):
"""
if tables:
if postgres_version[0] >= 8 and postgres_version[1] >= 1:
- # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* in order to be able to
- # truncate tables referenced by a foreign key in any other table. The result is a
- # single SQL TRUNCATE statement.
+ # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to*
+ # in order to be able to truncate tables referenced by a foreign
+ # key in any other table. The result is a single SQL TRUNCATE
+ # statement.
sql = ['%s %s;' % \
(style.SQL_KEYWORD('TRUNCATE'),
style.SQL_FIELD(', '.join([quote_name(table) for table in tables]))
)]
else:
- # Older versions of Postgres can't do TRUNCATE in a single call, so they must use
- # a simple delete.
+ # Older versions of Postgres can't do TRUNCATE in a single call, so
+ # they must use a simple delete.
sql = ['%s %s %s;' % \
(style.SQL_KEYWORD('DELETE'),
style.SQL_KEYWORD('FROM'),
@@ -263,6 +274,14 @@ def get_sql_sequence_reset(style, model_list):
style.SQL_TABLE(f.m2m_db_table())))
return output
+def typecast_string(s):
+ """
+ Cast all returned strings to unicode strings.
+ """
+ if not s:
+ return s
+ return smart_unicode(s)
+
# Register these custom typecasts, because Django expects dates/times to be
# in Python's native (standard-library) datetime/time format, whereas psycopg
# use mx.DateTime by default.
@@ -274,6 +293,7 @@ Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time
Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp))
Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean))
Database.register_type(Database.new_type((1700,), "NUMERIC", util.typecast_decimal))
+Database.register_type(Database.new_type(Database.types[1043].values, 'STRING', typecast_string))
OPERATOR_MAPPING = {
'exact': '= %s',
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
index 17d36a8613..c0ecbf80e9 100644
--- a/django/db/backends/postgresql_psycopg2/base.py
+++ b/django/db/backends/postgresql_psycopg2/base.py
@@ -7,6 +7,7 @@ Requires psycopg 2: http://initd.org/projects/psycopg2
from django.db.backends import util
try:
import psycopg2 as Database
+ import psycopg2.extensions
except ImportError, e:
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured, "Error loading psycopg2 module: %s" % e
@@ -21,6 +22,8 @@ except ImportError:
# Import copy of _thread_local.py from Python 2.4
from django.utils._threading_local import local
+psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
+
postgres_version = None
class DatabaseWrapper(local):
@@ -48,6 +51,7 @@ class DatabaseWrapper(local):
conn_string += " port=%s" % settings.DATABASE_PORT
self.connection = Database.connect(conn_string, **self.options)
self.connection.set_isolation_level(1) # make transactions transparent to all cursors
+ self.connection.set_client_encoding('UTF8')
cursor = self.connection.cursor()
cursor.tzinfo_factory = None
if set_tz:
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index c4ecf5f578..a0b1341b53 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -34,14 +34,6 @@ Database.register_converter("TIMESTAMP", util.typecast_timestamp)
Database.register_converter("decimal", util.typecast_decimal)
Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal)
-def utf8rowFactory(cursor, row):
- def utf8(s):
- if type(s) == unicode:
- return s.encode("utf-8")
- else:
- return s
- return [utf8(r) for r in row]
-
try:
# Only exists in Python 2.4+
from threading import local
@@ -69,7 +61,6 @@ class DatabaseWrapper(local):
self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
self.connection.create_function("regexp", 2, _sqlite_regexp)
cursor = self.connection.cursor(factory=SQLiteCursorWrapper)
- cursor.row_factory = utf8rowFactory
if settings.DEBUG:
return util.CursorDebugWrapper(cursor, self)
else:
@@ -85,8 +76,9 @@ class DatabaseWrapper(local):
def close(self):
from django.conf import settings
- # If database is in memory, closing the connection destroys the database.
- # To prevent accidental data loss, ignore close requests on an in-memory db.
+ # If database is in memory, closing the connection destroys the
+ # database. To prevent accidental data loss, ignore close requests on
+ # an in-memory db.
if self.connection is not None and settings.DATABASE_NAME != ":memory:":
self.connection.close()
self.connection = None
@@ -181,10 +173,10 @@ def get_autoinc_sql(table):
return None
def get_sql_flush(style, tables, sequences):
- """Return a list of SQL statements required to remove all data from
+ """
+ Return a list of SQL statements required to remove all data from
all tables in the database (without actually removing the tables
themselves) and put the database in an empty 'initial' state
-
"""
# NB: The generated SQL below is specific to SQLite
# Note: The DELETE FROM... SQL generated below works for SQLite databases
@@ -241,3 +233,4 @@ OPERATOR_MAPPING = {
'istartswith': "LIKE %s ESCAPE '\\'",
'iendswith': "LIKE %s ESCAPE '\\'",
}
+
diff --git a/django/db/backends/util.py b/django/db/backends/util.py
index 58f2d1e990..4de7c517d6 100644
--- a/django/db/backends/util.py
+++ b/django/db/backends/util.py
@@ -1,6 +1,7 @@
import datetime
import md5
from time import time
+from django.utils.encoding import smart_unicode, force_unicode
try:
import decimal
@@ -18,12 +19,8 @@ class CursorDebugWrapper(object):
return self.cursor.execute(sql, params)
finally:
stop = time()
- # If params was a list, convert it to a tuple, because string
- # formatting with '%' only works with tuples or dicts.
- if not isinstance(params, (tuple, dict)):
- params = tuple(params)
self.db.queries.append({
- 'sql': sql % params,
+ 'sql': smart_unicode(sql) % convert_args(params),
'time': "%.3f" % (stop - start),
})
@@ -34,7 +31,7 @@ class CursorDebugWrapper(object):
finally:
stop = time()
self.db.queries.append({
- 'sql': 'MANY: ' + sql + ' ' + str(tuple(param_list)),
+ 'sql': 'MANY: ' + sql + ' ' + smart_unicode(tuple(param_list)),
'time': "%.3f" % (stop - start),
})
@@ -44,6 +41,16 @@ class CursorDebugWrapper(object):
else:
return getattr(self.cursor, attr)
+def convert_args(args):
+ """
+ Convert sequence or dictionary to contain unicode values.
+ """
+ to_unicode = lambda s: force_unicode(s, strings_only=True)
+ if isinstance(args, (list, tuple)):
+ return tuple([to_unicode(val) for val in args])
+ else:
+ return dict([(to_unicode(k), to_unicode(v)) for k, v in args.items()])
+
###############################################
# Converters from database (string) to Python #
###############################################
diff --git a/django/db/models/base.py b/django/db/models/base.py
index b1c4a43628..d430d03a31 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -12,6 +12,7 @@ from django.db.models.loading import register_models, get_model
from django.dispatch import dispatcher
from django.utils.datastructures import SortedDict
from django.utils.functional import curry
+from django.utils.encoding import smart_str, force_unicode
from django.conf import settings
from itertools import izip
import types
@@ -83,9 +84,11 @@ class Model(object):
return getattr(self, self._meta.pk.attname)
def __repr__(self):
- return '<%s: %s>' % (self.__class__.__name__, self)
+ return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))
def __str__(self):
+ if hasattr(self, '__unicode__'):
+ return force_unicode(self).encode('utf-8')
return '%s object' % self.__class__.__name__
def __eq__(self, other):
@@ -318,14 +321,14 @@ class Model(object):
def _get_FIELD_display(self, field):
value = getattr(self, field.attname)
- return dict(field.choices).get(value, value)
+ return force_unicode(dict(field.choices).get(value, value), strings_only=True)
def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
op = is_next and '>' or '<'
where = '(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
(backend.quote_name(field.column), op, backend.quote_name(field.column),
backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column), op)
- param = str(getattr(self, field.attname))
+ param = smart_str(getattr(self, field.attname))
q = self.__class__._default_manager.filter(**kwargs).order_by((not is_next and '-' or '') + field.name, (not is_next and '-' or '') + self._meta.pk.name)
q._where.append(where)
q._params.extend([param, param, getattr(self, self._meta.pk.attname)])
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 14154fd6f7..7ef532eab6 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -8,7 +8,8 @@ from django.core.exceptions import ObjectDoesNotExist
from django.utils.functional import curry
from django.utils.itercompat import tee
from django.utils.text import capfirst
-from django.utils.translation import gettext, gettext_lazy
+from django.utils.translation import ugettext_lazy, ugettext as _
+from django.utils.encoding import smart_unicode
import datetime, os, time
try:
import decimal
@@ -26,7 +27,7 @@ BLANK_CHOICE_DASH = [("", "---------")]
BLANK_CHOICE_NONE = [("", "None")]
# prepares a value for use in a LIKE query
-prep_for_like_query = lambda x: str(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
+prep_for_like_query = lambda x: smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
# returns the <ul> class for a given radio_admin value
get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
@@ -43,7 +44,7 @@ def manipulator_validator_unique(f, opts, self, field_data, all_data):
return
if getattr(self, 'original_object', None) and self.original_object._get_pk_val() == old_obj._get_pk_val():
return
- raise validators.ValidationError, gettext("%(optname)s with this %(fieldname)s already exists.") % {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name}
+ raise validators.ValidationError, _("%(optname)s with this %(fieldname)s already exists.") % {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name}
# A guide to Field parameters:
#
@@ -123,7 +124,7 @@ class Field(object):
Subclasses should implement validate(), not validate_full().
"""
if not self.blank and not field_data:
- return [gettext_lazy('This field is required.')]
+ return [_('This field is required.')]
try:
self.validate(field_data, all_data)
except validators.ValidationError, e:
@@ -280,7 +281,7 @@ class Field(object):
core_field_names.extend(f.get_manipulator_field_names(name_prefix))
# Now, if there are any, add the validator to this FormField.
if core_field_names:
- params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, gettext_lazy("This field is required.")))
+ params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, ugettext_lazy("This field is required.")))
# Finally, add the field_names.
field_names = self.get_manipulator_field_names(name_prefix)
@@ -308,9 +309,9 @@ class Field(object):
return first_choice + list(self.choices)
rel_model = self.rel.to
if hasattr(self.rel, 'get_related_field'):
- lst = [(getattr(x, self.rel.get_related_field().attname), str(x)) for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)]
+ lst = [(getattr(x, self.rel.get_related_field().attname), smart_unicode(x)) for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)]
else:
- lst = [(x._get_pk_val(), str(x)) for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)]
+ lst = [(x._get_pk_val(), smart_unicode(x)) for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)]
return first_choice + lst
def get_choices_default(self):
@@ -375,7 +376,7 @@ class AutoField(Field):
try:
return int(value)
except (TypeError, ValueError):
- raise validators.ValidationError, gettext("This value must be an integer.")
+ raise validators.ValidationError, _("This value must be an integer.")
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
if not rel:
@@ -410,7 +411,7 @@ class BooleanField(Field):
if value in (True, False): return value
if value in ('t', 'True', '1'): return True
if value in ('f', 'False', '0'): return False
- raise validators.ValidationError, gettext("This value must be either True or False.")
+ raise validators.ValidationError, _("This value must be either True or False.")
def get_manipulator_field_objs(self):
return [oldforms.CheckboxField]
@@ -431,8 +432,8 @@ class CharField(Field):
if self.null:
return value
else:
- raise validators.ValidationError, gettext_lazy("This field cannot be null.")
- return str(value)
+ raise validators.ValidationError, ugettext_lazy("This field cannot be null.")
+ return smart_unicode(value)
def formfield(self, **kwargs):
defaults = {'max_length': self.maxlength}
@@ -465,15 +466,15 @@ class DateField(Field):
try:
return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3])
except ValueError:
- raise validators.ValidationError, gettext('Enter a valid date in YYYY-MM-DD format.')
+ raise validators.ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
def get_db_prep_lookup(self, lookup_type, value):
if lookup_type == 'range':
- value = [str(v) for v in value]
+ value = [smart_unicode(v) for v in value]
elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte') and hasattr(value, 'strftime'):
value = value.strftime('%Y-%m-%d')
else:
- value = str(value)
+ value = smart_unicode(value)
return Field.get_db_prep_lookup(self, lookup_type, value)
def pre_save(self, model_instance, add):
@@ -534,7 +535,7 @@ class DateTimeField(DateField):
try:
return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3])
except ValueError:
- raise validators.ValidationError, gettext('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
+ raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
def get_db_prep_save(self, value):
# Casts dates into string format for entry into database.
@@ -543,14 +544,14 @@ class DateTimeField(DateField):
# doesn't support microseconds.
if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
value = value.replace(microsecond=0)
- value = str(value)
+ value = smart_unicode(value)
return Field.get_db_prep_save(self, value)
def get_db_prep_lookup(self, lookup_type, value):
if lookup_type == 'range':
- value = [str(v) for v in value]
+ value = [smart_unicode(v) for v in value]
else:
- value = str(value)
+ value = smart_unicode(value)
return Field.get_db_prep_lookup(self, lookup_type, value)
def get_manipulator_field_objs(self):
@@ -594,7 +595,7 @@ class DecimalField(Field):
try:
return decimal.Decimal(value)
except decimal.InvalidOperation:
- raise validators.ValidationError, gettext("This value must be a decimal number.")
+ raise validators.ValidationError, ugettext("This value must be a decimal number.")
def _format(self, value):
if isinstance(value, basestring):
@@ -615,7 +616,7 @@ class DecimalField(Field):
if value < 0:
num_chars += 1
- return "%.*f" % (self.decimal_places, value)
+ return u"%.*f" % (self.decimal_places, value)
def get_db_prep_save(self, value):
if value is not None:
@@ -677,7 +678,7 @@ class FileField(Field):
self.always_test = True
def __call__(self, field_data, all_data):
if not all_data.get(self.other_file_field_name, False):
- c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, gettext_lazy("This field is required."))
+ c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, ugettext_lazy("This field is required."))
c(field_data, all_data)
# First, get the core fields, if any.
core_field_names = []
@@ -688,7 +689,7 @@ class FileField(Field):
if core_field_names:
field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name))
else:
- v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, gettext_lazy("This field is required."))
+ v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, ugettext_lazy("This field is required."))
v.always_test = True
field_list[0].validator_list.append(v)
field_list[0].is_required = field_list[1].is_required = False
@@ -822,7 +823,7 @@ class NullBooleanField(Field):
if value in ('None'): return None
if value in ('t', 'True', '1'): return True
if value in ('f', 'False', '0'): return False
- raise validators.ValidationError, gettext("This value must be either None, True or False.")
+ raise validators.ValidationError, _("This value must be either None, True or False.")
def get_manipulator_field_objs(self):
return [oldforms.NullBooleanField]
@@ -887,9 +888,9 @@ class TimeField(Field):
def prep(value):
if isinstance(value, datetime.time):
value = datetime.datetime.combine(datetime.date(1900, 1, 1), value)
- return str(value)
+ return smart_unicode(value)
else:
- prep = str
+ prep = smart_unicode
if lookup_type == 'range':
value = [prep(v) for v in value]
else:
@@ -919,7 +920,7 @@ class TimeField(Field):
elif isinstance(value, basestring):
value = datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
else:
- value = str(value)
+ value = smart_unicode(value)
return Field.get_db_prep_save(self, value)
def get_manipulator_field_objs(self):
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 7286959302..152af10545 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -3,8 +3,9 @@ from django.db.models import signals, get_model
from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class
from django.db.models.related import RelatedObject
from django.utils.text import capfirst
-from django.utils.translation import gettext_lazy, string_concat, ngettext
+from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _
from django.utils.functional import curry
+from django.utils.encoding import smart_unicode
from django.core import validators
from django import oldforms
from django import newforms as forms
@@ -637,9 +638,9 @@ class ManyToManyField(RelatedField, Field):
Field.__init__(self, **kwargs)
if self.rel.raw_id_admin:
- msg = gettext_lazy('Separate multiple IDs with commas.')
+ msg = ugettext_lazy('Separate multiple IDs with commas.')
else:
- msg = gettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.')
+ msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.')
self.help_text = string_concat(self.help_text, ' ', msg)
def get_manipulator_field_objs(self):
@@ -686,7 +687,7 @@ class ManyToManyField(RelatedField, Field):
objects = mod._default_manager.in_bulk(pks)
if len(objects) != len(pks):
badkeys = [k for k in pks if k not in objects]
- raise validators.ValidationError, ngettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
+ raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
"Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
'self': self.verbose_name,
'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
@@ -697,7 +698,7 @@ class ManyToManyField(RelatedField, Field):
if obj:
instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()]
if self.rel.raw_id_admin:
- new_data[self.name] = ",".join([str(id) for id in instance_ids])
+ new_data[self.name] = u",".join([smart_unicode(id) for id in instance_ids])
else:
new_data[self.name] = instance_ids
else:
diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py
index c624cb698b..c3e5292198 100644
--- a/django/db/models/manipulators.py
+++ b/django/db/models/manipulators.py
@@ -7,6 +7,8 @@ from django.db.models import signals
from django.utils.functional import curry
from django.utils.datastructures import DotExpandedDict
from django.utils.text import capfirst
+from django.utils.encoding import smart_str
+from django.utils.translation import ugettext as _
import types
def add_manipulators(sender):
@@ -111,7 +113,7 @@ class AutomaticManipulator(oldforms.Manipulator):
if self.change:
self.fields_added, self.fields_changed, self.fields_deleted = [], [], []
for f in self.opts.fields:
- if not f.primary_key and str(getattr(self.original_object, f.attname)) != str(getattr(new_object, f.attname)):
+ if not f.primary_key and smart_str(getattr(self.original_object, f.attname)) != smart_str(getattr(new_object, f.attname)):
self.fields_changed.append(f.verbose_name)
# Save many-to-many objects. Example: Set sites for a poll.
@@ -211,7 +213,7 @@ class AutomaticManipulator(oldforms.Manipulator):
self.fields_added.append('%s "%s"' % (related.opts.verbose_name, new_rel_obj))
else:
for f in related.opts.fields:
- if not f.primary_key and f != related.field and str(getattr(old_rel_obj, f.attname)) != str(getattr(new_rel_obj, f.attname)):
+ if not f.primary_key and f != related.field and smart_str(getattr(old_rel_obj, f.attname)) != smart_str(getattr(new_rel_obj, f.attname)):
self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
# Save many-to-many objects.
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 93627f7b72..7cccb611cf 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -5,6 +5,8 @@ from django.db.models.fields import AutoField, FieldDoesNotExist
from django.db.models.loading import get_models
from django.db.models.query import orderlist2sql
from django.db.models import Manager
+from django.utils.translation import activate, deactivate_all, get_language, string_concat
+from django.utils.encoding import force_unicode, smart_str
from bisect import bisect
import re
@@ -42,6 +44,7 @@ class Options(object):
self.object_name = cls.__name__
self.module_name = self.object_name.lower()
self.verbose_name = get_verbose_name(self.object_name)
+
# Next, apply any overridden values from 'class Meta'.
if self.meta:
meta_attrs = self.meta.__dict__
@@ -51,12 +54,12 @@ class Options(object):
setattr(self, attr_name, meta_attrs.pop(attr_name, getattr(self, attr_name)))
# verbose_name_plural is a special case because it uses a 's'
# by default.
- setattr(self, 'verbose_name_plural', meta_attrs.pop('verbose_name_plural', self.verbose_name + 's'))
+ setattr(self, 'verbose_name_plural', meta_attrs.pop('verbose_name_plural', string_concat(self.verbose_name, 's')))
# Any leftover attributes must be invalid.
if meta_attrs != {}:
raise TypeError, "'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())
else:
- self.verbose_name_plural = self.verbose_name + 's'
+ self.verbose_name_plural = string_concat(self.verbose_name, 's')
del self.meta
def _prepare(self, model):
@@ -95,7 +98,20 @@ class Options(object):
return '<Options for %s>' % self.object_name
def __str__(self):
- return "%s.%s" % (self.app_label, self.module_name)
+ return "%s.%s" % (smart_str(self.app_label), smart_str(self.module_name))
+
+ def verbose_name_raw(self):
+ """
+ There are a few places where the untranslated verbose name is needed
+ (so that we get the same value regardless of currently active
+ locale).
+ """
+ lang = get_language()
+ deactivate_all()
+ raw = force_unicode(self.verbose_name)
+ activate(lang)
+ return raw
+ verbose_name_raw = property(verbose_name_raw)
def get_field(self, name, many_to_many=True):
"Returns the requested field by name. Raises FieldDoesNotExist on error."
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 92bc9d78ed..5771f3d7d9 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -4,6 +4,7 @@ from django.db.models.fields import DateField, FieldDoesNotExist
from django.db.models import signals, loading
from django.dispatch import dispatcher
from django.utils.datastructures import SortedDict
+from django.utils.encoding import smart_unicode
from django.contrib.contenttypes import generic
import datetime
import operator
@@ -52,7 +53,7 @@ def handle_legacy_orderlist(order_list):
return order_list
else:
import warnings
- new_order_list = [LEGACY_ORDERING_MAPPING[j.upper()].replace('_', str(i)) for i, j in order_list]
+ new_order_list = [LEGACY_ORDERING_MAPPING[j.upper()].replace('_', smart_unicode(i)) for i, j in order_list]
warnings.warn("%r ordering syntax is deprecated. Use %r instead." % (order_list, new_order_list), DeprecationWarning)
return new_order_list
diff --git a/django/http/__init__.py b/django/http/__init__.py
index 1b2abe5049..7061644a4e 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -1,8 +1,9 @@
import os
from Cookie import SimpleCookie
from pprint import pformat
-from urllib import urlencode, quote
+from urllib import urlencode
from django.utils.datastructures import MultiValueDict
+from django.utils.encoding import smart_str, iri_to_uri, force_unicode
RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
@@ -17,6 +18,10 @@ class Http404(Exception):
class HttpRequest(object):
"A basic HTTP request"
+
+ # The encoding used in GET/POST dicts. None means use default setting.
+ _encoding = None
+
def __init__(self):
self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
self.path = ''
@@ -42,14 +47,31 @@ class HttpRequest(object):
def is_secure(self):
return os.environ.get("HTTPS") == "on"
+ def _set_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
+ next access (so that it is decoded correctly).
+ """
+ self._encoding = val
+ if hasattr(self, '_get'):
+ del self._get
+ if hasattr(self, '_post'):
+ del self._post
+
+ def _get_encoding(self):
+ return self._encoding
+
+ encoding = property(_get_encoding, _set_encoding)
+
def parse_file_upload(header_dict, post_data):
- "Returns a tuple of (POST MultiValueDict, FILES MultiValueDict)"
+ "Returns a tuple of (POST QueryDict, FILES MultiValueDict)"
import email, email.Message
from cgi import parse_header
raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
raw_message += '\r\n\r\n' + post_data
msg = email.message_from_string(raw_message)
- POST = MultiValueDict()
+ POST = QueryDict('', mutable=True)
FILES = MultiValueDict()
for submessage in msg.get_payload():
if submessage and isinstance(submessage, email.Message.Message):
@@ -74,13 +96,25 @@ def parse_file_upload(header_dict, post_data):
return POST, FILES
class QueryDict(MultiValueDict):
- """A specialized MultiValueDict that takes a query string when initialized.
- This is immutable unless you create a copy of it."""
- def __init__(self, query_string, mutable=False):
+ """
+ A specialized MultiValueDict that takes a query string when initialized.
+ This is immutable unless you create a copy of it.
+
+ Values retrieved from this class are converted from the given encoding
+ (DEFAULT_CHARSET by default) to unicode.
+ """
+ def __init__(self, query_string, mutable=False, encoding=None):
MultiValueDict.__init__(self)
+ if not encoding:
+ # *Important*: do not import settings any earlier because of note
+ # in core.handlers.modpython.
+ from django.conf import settings
+ self.encoding = settings.DEFAULT_CHARSET
+ else:
+ self.encoding = encoding
self._mutable = True
for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True
- self.appendlist(key, value)
+ self.appendlist(force_unicode(key, errors='replace'), force_unicode(value, errors='replace'))
self._mutable = mutable
def _assert_mutable(self):
@@ -89,6 +123,8 @@ class QueryDict(MultiValueDict):
def __setitem__(self, key, value):
self._assert_mutable()
+ key = str_to_unicode(key, self.encoding)
+ value = str_to_unicode(value, self.encoding)
MultiValueDict.__setitem__(self, key, value)
def __delitem__(self, key):
@@ -111,15 +147,27 @@ class QueryDict(MultiValueDict):
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_]
MultiValueDict.setlist(self, key, list_)
+ def setlistdefault(self, key, default_list=()):
+ self._assert_mutable()
+ if key not in self:
+ self.setlist(key, default_list)
+ return MultiValueDict.getlist(self, key)
+
def appendlist(self, key, value):
self._assert_mutable()
+ key = str_to_unicode(key, self.encoding)
+ value = str_to_unicode(value, self.encoding)
MultiValueDict.appendlist(self, key, value)
def update(self, other_dict):
self._assert_mutable()
- MultiValueDict.update(self, other_dict)
+ f = lambda s: str_to_unicode(s, self.encoding)
+ d = dict([(f(k), f(v)) for k, v in other_dict.items()])
+ MultiValueDict.update(self, d)
def pop(self, key, *args):
self._assert_mutable()
@@ -133,9 +181,11 @@ class QueryDict(MultiValueDict):
self._assert_mutable()
MultiValueDict.clear(self)
- def setdefault(self, *args):
+ def setdefault(self, key, default=None):
self._assert_mutable()
- return MultiValueDict.setdefault(self, *args)
+ key = str_to_unicode(key, self.encoding)
+ default = str_to_unicode(default, self.encoding)
+ return MultiValueDict.setdefault(self, key, default)
def copy(self):
"Returns a mutable copy of this object."
@@ -144,7 +194,8 @@ class QueryDict(MultiValueDict):
def urlencode(self):
output = []
for k, list_ in self.lists():
- output.extend([urlencode({k: v}) for v in list_])
+ k = smart_str(k, self.encoding)
+ output.extend([urlencode({k: smart_str(v, self.encoding)}) for v in list_])
return '&'.join(output)
def parse_cookie(cookie):
@@ -221,9 +272,7 @@ class HttpResponse(object):
self.cookies[key]['max-age'] = 0
def _get_content(self):
- content = ''.join(self._container)
- if isinstance(content, unicode):
- content = content.encode(self._charset)
+ content = smart_str(''.join(self._container), self._charset)
return content
def _set_content(self, value):
@@ -266,14 +315,14 @@ class HttpResponseRedirect(HttpResponse):
def __init__(self, redirect_to):
HttpResponse.__init__(self)
- self['Location'] = quote(redirect_to, safe=RESERVED_CHARS)
+ self['Location'] = iri_to_uri(redirect_to)
class HttpResponsePermanentRedirect(HttpResponse):
status_code = 301
def __init__(self, redirect_to):
HttpResponse.__init__(self)
- self['Location'] = quote(redirect_to, safe=RESERVED_CHARS)
+ self['Location'] = iri_to_uri(redirect_to)
class HttpResponseNotModified(HttpResponse):
status_code = 304
@@ -312,3 +361,20 @@ def get_host(request):
if not host:
host = request.META.get('HTTP_HOST', '')
return host
+
+# It's neither necessary nor appropriate to use
+# django.utils.encoding.smart_unicode for parsing URLs and form inputs. Thus,
+# this slightly more restricted function.
+def str_to_unicode(s, encoding):
+ """
+ Convert basestring objects to unicode, using the given encoding. Illegaly
+ encoded input characters are replaced with Unicode "unknown" codepoint
+ (\ufffd).
+
+ Returns any non-basestring objects without change.
+ """
+ if isinstance(s, str):
+ return unicode(s, encoding, 'replace')
+ else:
+ return s
+
diff --git a/django/newforms/fields.py b/django/newforms/fields.py
index cec2ae8317..b6abea6400 100644
--- a/django/newforms/fields.py
+++ b/django/newforms/fields.py
@@ -6,7 +6,7 @@ import datetime
import re
import time
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
from django.utils.encoding import smart_unicode
from util import ErrorList, ValidationError
@@ -84,7 +84,7 @@ class Field(object):
Raises ValidationError for any errors.
"""
if self.required and value in EMPTY_VALUES:
- raise ValidationError(gettext(u'This field is required.'))
+ raise ValidationError(ugettext(u'This field is required.'))
return value
def widget_attrs(self, widget):
@@ -107,9 +107,9 @@ class CharField(Field):
return u''
value = smart_unicode(value)
if self.max_length is not None and len(value) > self.max_length:
- raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
+ raise ValidationError(ugettext(u'Ensure this value has at most %d characters.') % self.max_length)
if self.min_length is not None and len(value) < self.min_length:
- raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length)
+ raise ValidationError(ugettext(u'Ensure this value has at least %d characters.') % self.min_length)
return value
def widget_attrs(self, widget):
@@ -132,11 +132,11 @@ class IntegerField(Field):
try:
value = int(value)
except (ValueError, TypeError):
- raise ValidationError(gettext(u'Enter a whole number.'))
+ raise ValidationError(ugettext(u'Enter a whole number.'))
if self.max_value is not None and value > self.max_value:
- raise ValidationError(gettext(u'Ensure this value is less than or equal to %s.') % self.max_value)
+ raise ValidationError(ugettext(u'Ensure this value is less than or equal to %s.') % self.max_value)
if self.min_value is not None and value < self.min_value:
- raise ValidationError(gettext(u'Ensure this value is greater than or equal to %s.') % self.min_value)
+ raise ValidationError(ugettext(u'Ensure this value is greater than or equal to %s.') % self.min_value)
return value
class FloatField(Field):
@@ -155,11 +155,11 @@ class FloatField(Field):
try:
value = float(value)
except (ValueError, TypeError):
- raise ValidationError(gettext('Enter a number.'))
+ raise ValidationError(ugettext('Enter a number.'))
if self.max_value is not None and value > self.max_value:
- raise ValidationError(gettext('Ensure this value is less than or equal to %s.') % self.max_value)
+ raise ValidationError(ugettext('Ensure this value is less than or equal to %s.') % self.max_value)
if self.min_value is not None and value < self.min_value:
- raise ValidationError(gettext('Ensure this value is greater than or equal to %s.') % self.min_value)
+ raise ValidationError(ugettext('Ensure this value is greater than or equal to %s.') % self.min_value)
return value
decimal_re = re.compile(r'^-?(?P<digits>\d+)(\.(?P<decimals>\d+))?$')
@@ -183,21 +183,21 @@ class DecimalField(Field):
value = value.strip()
match = decimal_re.search(value)
if not match:
- raise ValidationError(gettext('Enter a number.'))
+ raise ValidationError(ugettext('Enter a number.'))
else:
value = Decimal(value)
digits = len(match.group('digits') or '')
decimals = len(match.group('decimals') or '')
if self.max_value is not None and value > self.max_value:
- raise ValidationError(gettext('Ensure this value is less than or equal to %s.') % self.max_value)
+ raise ValidationError(ugettext('Ensure this value is less than or equal to %s.') % self.max_value)
if self.min_value is not None and value < self.min_value:
- raise ValidationError(gettext('Ensure this value is greater than or equal to %s.') % self.min_value)
+ raise ValidationError(ugettext('Ensure this value is greater than or equal to %s.') % self.min_value)
if self.max_digits is not None and (digits + decimals) > self.max_digits:
- raise ValidationError(gettext('Ensure that there are no more than %s digits in total.') % self.max_digits)
+ raise ValidationError(ugettext('Ensure that there are no more than %s digits in total.') % self.max_digits)
if self.decimal_places is not None and decimals > self.decimal_places:
- raise ValidationError(gettext('Ensure that there are no more than %s decimal places.') % self.decimal_places)
+ raise ValidationError(ugettext('Ensure that there are no more than %s decimal places.') % self.decimal_places)
if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
- raise ValidationError(gettext('Ensure that there are no more than %s digits before the decimal point.') % (self.max_digits - self.decimal_places))
+ raise ValidationError(ugettext('Ensure that there are no more than %s digits before the decimal point.') % (self.max_digits - self.decimal_places))
return value
DEFAULT_DATE_INPUT_FORMATS = (
@@ -230,7 +230,7 @@ class DateField(Field):
return datetime.date(*time.strptime(value, format)[:3])
except ValueError:
continue
- raise ValidationError(gettext(u'Enter a valid date.'))
+ raise ValidationError(ugettext(u'Enter a valid date.'))
DEFAULT_TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
@@ -257,7 +257,7 @@ class TimeField(Field):
return datetime.time(*time.strptime(value, format)[3:6])
except ValueError:
continue
- raise ValidationError(gettext(u'Enter a valid time.'))
+ raise ValidationError(ugettext(u'Enter a valid time.'))
DEFAULT_DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
@@ -293,7 +293,7 @@ class DateTimeField(Field):
return datetime.datetime(*time.strptime(value, format)[:6])
except ValueError:
continue
- raise ValidationError(gettext(u'Enter a valid date/time.'))
+ raise ValidationError(ugettext(u'Enter a valid date/time.'))
class RegexField(Field):
def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
@@ -307,7 +307,7 @@ class RegexField(Field):
regex = re.compile(regex)
self.regex = regex
self.max_length, self.min_length = max_length, min_length
- self.error_message = error_message or gettext(u'Enter a valid value.')
+ self.error_message = error_message or ugettext(u'Enter a valid value.')
def clean(self, value):
"""
@@ -321,9 +321,9 @@ class RegexField(Field):
if value == u'':
return value
if self.max_length is not None and len(value) > self.max_length:
- raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
+ raise ValidationError(ugettext(u'Ensure this value has at most %d characters.') % self.max_length)
if self.min_length is not None and len(value) < self.min_length:
- raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length)
+ raise ValidationError(ugettext(u'Ensure this value has at least %d characters.') % self.min_length)
if not self.regex.search(value):
raise ValidationError(self.error_message)
return value
@@ -336,7 +336,7 @@ email_re = re.compile(
class EmailField(RegexField):
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
RegexField.__init__(self, email_re, max_length, min_length,
- gettext(u'Enter a valid e-mail address.'), *args, **kwargs)
+ ugettext(u'Enter a valid e-mail address.'), *args, **kwargs)
url_re = re.compile(
r'^https?://' # http:// or https://
@@ -354,7 +354,7 @@ except ImportError:
class URLField(RegexField):
def __init__(self, max_length=None, min_length=None, verify_exists=False,
validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs):
- super(URLField, self).__init__(url_re, max_length, min_length, gettext(u'Enter a valid URL.'), *args, **kwargs)
+ super(URLField, self).__init__(url_re, max_length, min_length, ugettext(u'Enter a valid URL.'), *args, **kwargs)
self.verify_exists = verify_exists
self.user_agent = validator_user_agent
@@ -376,9 +376,9 @@ class URLField(RegexField):
req = urllib2.Request(value, None, headers)
u = urllib2.urlopen(req)
except ValueError:
- raise ValidationError(gettext(u'Enter a valid URL.'))
+ raise ValidationError(ugettext(u'Enter a valid URL.'))
except: # urllib2.URLError, httplib.InvalidURL, etc.
- raise ValidationError(gettext(u'This URL appears to be a broken link.'))
+ raise ValidationError(ugettext(u'This URL appears to be a broken link.'))
return value
class BooleanField(Field):
@@ -427,9 +427,9 @@ class ChoiceField(Field):
value = smart_unicode(value)
if value == u'':
return value
- valid_values = set([str(k) for k, v in self.choices])
+ valid_values = set([smart_unicode(k) for k, v in self.choices])
if value not in valid_values:
- raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.'))
+ raise ValidationError(ugettext(u'Select a valid choice. That choice is not one of the available choices.'))
return value
class MultipleChoiceField(ChoiceField):
@@ -441,11 +441,11 @@ class MultipleChoiceField(ChoiceField):
Validates that the input is a list or tuple.
"""
if self.required and not value:
- raise ValidationError(gettext(u'This field is required.'))
+ raise ValidationError(ugettext(u'This field is required.'))
elif not self.required and not value:
return []
if not isinstance(value, (list, tuple)):
- raise ValidationError(gettext(u'Enter a list of values.'))
+ raise ValidationError(ugettext(u'Enter a list of values.'))
new_value = []
for val in value:
val = smart_unicode(val)
@@ -454,7 +454,7 @@ class MultipleChoiceField(ChoiceField):
valid_values = set([smart_unicode(k) for k, v in self.choices])
for val in new_value:
if val not in valid_values:
- raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val)
+ raise ValidationError(ugettext(u'Select a valid choice. %s is not one of the available choices.') % val)
return new_value
class ComboField(Field):
@@ -520,18 +520,18 @@ class MultiValueField(Field):
if not value or isinstance(value, (list, tuple)):
if not value or not [v for v in value if v not in EMPTY_VALUES]:
if self.required:
- raise ValidationError(gettext(u'This field is required.'))
+ raise ValidationError(ugettext(u'This field is required.'))
else:
return self.compress([])
else:
- raise ValidationError(gettext(u'Enter a list of values.'))
+ raise ValidationError(ugettext(u'Enter a list of values.'))
for i, field in enumerate(self.fields):
try:
field_value = value[i]
except IndexError:
field_value = None
if self.required and field_value in EMPTY_VALUES:
- raise ValidationError(gettext(u'This field is required.'))
+ raise ValidationError(ugettext(u'This field is required.'))
try:
clean_data.append(field.clean(field_value))
except ValidationError, e:
@@ -564,8 +564,8 @@ class SplitDateTimeField(MultiValueField):
# Raise a validation error if time or date is empty
# (possible if SplitDateTimeField has required=False).
if data_list[0] in EMPTY_VALUES:
- raise ValidationError(gettext(u'Enter a valid date.'))
+ raise ValidationError(ugettext(u'Enter a valid date.'))
if data_list[1] in EMPTY_VALUES:
- raise ValidationError(gettext(u'Enter a valid time.'))
+ raise ValidationError(ugettext(u'Enter a valid time.'))
return datetime.datetime.combine(*data_list)
return None
diff --git a/django/newforms/forms.py b/django/newforms/forms.py
index 1ea1c73d9a..50e6f42108 100644
--- a/django/newforms/forms.py
+++ b/django/newforms/forms.py
@@ -6,7 +6,7 @@ import copy
from django.utils.datastructures import SortedDict
from django.utils.html import escape
-from django.utils.encoding import StrAndUnicode
+from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
from fields import Field
from widgets import TextInput, Textarea
@@ -119,13 +119,13 @@ class BaseForm(StrAndUnicode):
bf_errors = ErrorList([escape(error) for error in bf.errors]) # Escape and cache in local variable.
if bf.is_hidden:
if bf_errors:
- top_errors.extend(['(Hidden field %s) %s' % (name, e) for e in bf_errors])
+ top_errors.extend(['(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
hidden_fields.append(unicode(bf))
else:
if errors_on_separate_row and bf_errors:
- output.append(error_row % bf_errors)
+ output.append(error_row % force_unicode(bf_errors))
if bf.label:
- label = escape(bf.label)
+ label = escape(force_unicode(bf.label))
# Only add a colon if the label does not end in punctuation.
if label[-1] not in ':?.!':
label += ':'
@@ -133,10 +133,10 @@ class BaseForm(StrAndUnicode):
else:
label = ''
if field.help_text:
- help_text = help_text_html % field.help_text
+ help_text = help_text_html % force_unicode(field.help_text)
else:
help_text = u''
- output.append(normal_row % {'errors': bf_errors, 'label': label, 'field': unicode(bf), 'help_text': help_text})
+ output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
if top_errors:
output.insert(0, error_row % top_errors)
if hidden_fields: # Insert any hidden fields in the last row.
@@ -314,8 +314,8 @@ class BoundField(StrAndUnicode):
associated Form has specified auto_id. Returns an empty string otherwise.
"""
auto_id = self.form.auto_id
- if auto_id and '%s' in str(auto_id):
- return str(auto_id) % self.html_name
+ if auto_id and '%s' in smart_unicode(auto_id):
+ return smart_unicode(auto_id) % self.html_name
elif auto_id:
return self.html_name
return ''
diff --git a/django/newforms/models.py b/django/newforms/models.py
index d51b06c78c..56a08bc58e 100644
--- a/django/newforms/models.py
+++ b/django/newforms/models.py
@@ -3,7 +3,9 @@ Helper functions for creating Form classes from Django models
and database field objects.
"""
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
+from django.utils.encoding import smart_unicode
+
from util import ValidationError
from forms import BaseForm, SortedDictFromList
@@ -122,7 +124,7 @@ class QuerySetIterator(object):
if self.empty_label is not None:
yield (u"", self.empty_label)
for obj in self.queryset:
- yield (obj._get_pk_val(), str(obj))
+ yield (obj._get_pk_val(), smart_unicode(obj))
# Clear the QuerySet cache if required.
if not self.cache_choices:
self.queryset._result_cache = None
@@ -169,7 +171,7 @@ class ModelChoiceField(ChoiceField):
try:
value = self.queryset.model._default_manager.get(pk=value)
except self.queryset.model.DoesNotExist:
- raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.'))
+ raise ValidationError(ugettext(u'Select a valid choice. That choice is not one of the available choices.'))
return value
class ModelMultipleChoiceField(ModelChoiceField):
@@ -182,17 +184,17 @@ class ModelMultipleChoiceField(ModelChoiceField):
def clean(self, value):
if self.required and not value:
- raise ValidationError(gettext(u'This field is required.'))
+ raise ValidationError(ugettext(u'This field is required.'))
elif not self.required and not value:
return []
if not isinstance(value, (list, tuple)):
- raise ValidationError(gettext(u'Enter a list of values.'))
+ raise ValidationError(ugettext(u'Enter a list of values.'))
final_values = []
for val in value:
try:
obj = self.queryset.model._default_manager.get(pk=val)
except self.queryset.model.DoesNotExist:
- raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val)
+ raise ValidationError(ugettext(u'Select a valid choice. %s is not one of the available choices.') % val)
else:
final_values.append(obj)
return final_values
diff --git a/django/newforms/util.py b/django/newforms/util.py
index 891585cba2..2bafc8d9c7 100644
--- a/django/newforms/util.py
+++ b/django/newforms/util.py
@@ -1,5 +1,5 @@
from django.utils.html import escape
-from django.utils.encoding import smart_unicode
+from django.utils.encoding import smart_unicode, StrAndUnicode
def flatatt(attrs):
"""
@@ -10,36 +10,36 @@ def flatatt(attrs):
"""
return u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
-class ErrorDict(dict):
+class ErrorDict(dict, StrAndUnicode):
"""
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 __str__(self):
+ def __unicode__(self):
return self.as_ul()
def as_ul(self):
if not self: return u''
- return u'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s%s</li>' % (k, v) for k, v in self.items()])
+ return u'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s%s</li>' % (k, smart_unicode(v)) for k, v in self.items()])
def as_text(self):
- return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u' * %s' % i for i in v])) for k, v in self.items()])
+ return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u' * %s' % smart_unicode(i) for i in v])) for k, v in self.items()])
-class ErrorList(list):
+class ErrorList(list, StrAndUnicode):
"""
A collection of errors that knows how to display itself in various formats.
"""
- def __str__(self):
+ def __unicode__(self):
return self.as_ul()
def as_ul(self):
if not self: return u''
- return u'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s</li>' % e for e in self])
+ return u'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s</li>' % smart_unicode(e) for e in self])
def as_text(self):
if not self: return u''
- return u'\n'.join([u'* %s' % e for e in self])
+ return u'\n'.join([u'* %s' % smart_unicode(e) for e in self])
class ValidationError(Exception):
def __init__(self, message):
diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py
index ecd6dcc9d3..b90e6df9cd 100644
--- a/django/newforms/widgets.py
+++ b/django/newforms/widgets.py
@@ -10,8 +10,8 @@ except NameError:
from itertools import chain
from django.utils.datastructures import MultiValueDict
from django.utils.html import escape
-from django.utils.translation import gettext
-from django.utils.encoding import StrAndUnicode, smart_unicode
+from django.utils.translation import ugettext
+from django.utils.encoding import StrAndUnicode, force_unicode
from util import flatatt
__all__ = (
@@ -77,7 +77,7 @@ class Input(Widget):
def render(self, name, value, attrs=None):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
- if value != '': final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
+ if value != '': final_attrs['value'] = force_unicode(value) # Only add the 'value' attribute if a value is non-empty.
return u'<input%s />' % flatatt(final_attrs)
class TextInput(Input):
@@ -111,7 +111,7 @@ class MultipleHiddenInput(HiddenInput):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
- return u'\n'.join([(u'<input%s />' % flatatt(dict(value=smart_unicode(v), **final_attrs))) for v in value])
+ return u'\n'.join([(u'<input%s />' % flatatt(dict(value=force_unicode(v), **final_attrs))) for v in value])
def value_from_datadict(self, data, name):
if isinstance(data, MultiValueDict):
@@ -130,7 +130,7 @@ class Textarea(Widget):
def render(self, name, value, attrs=None):
if value is None: value = ''
- value = smart_unicode(value)
+ value = force_unicode(value)
final_attrs = self.build_attrs(attrs, name=name)
return u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(value))
@@ -150,7 +150,7 @@ class CheckboxInput(Widget):
if result:
final_attrs['checked'] = 'checked'
if value not in ('', True, False, None):
- final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
+ final_attrs['value'] = force_unicode(value) # Only add the 'value' attribute if a value is non-empty.
return u'<input%s />' % flatatt(final_attrs)
class Select(Widget):
@@ -165,11 +165,11 @@ class Select(Widget):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, name=name)
output = [u'<select%s>' % flatatt(final_attrs)]
- str_value = smart_unicode(value) # Normalize to string.
+ str_value = force_unicode(value) # Normalize to string.
for option_value, option_label in chain(self.choices, choices):
- option_value = smart_unicode(option_value)
+ option_value = force_unicode(option_value)
selected_html = (option_value == str_value) and u' selected="selected"' or ''
- output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(smart_unicode(option_label))))
+ output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(force_unicode(option_label))))
output.append(u'</select>')
return u'\n'.join(output)
@@ -178,7 +178,7 @@ class NullBooleanSelect(Select):
A Select Widget intended to be used with NullBooleanField.
"""
def __init__(self, attrs=None):
- choices = ((u'1', gettext('Unknown')), (u'2', gettext('Yes')), (u'3', gettext('No')))
+ choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No')))
super(NullBooleanSelect, self).__init__(attrs, choices)
def render(self, name, value, attrs=None, choices=()):
@@ -202,11 +202,11 @@ class SelectMultiple(Widget):
if value is None: value = []
final_attrs = self.build_attrs(attrs, name=name)
output = [u'<select multiple="multiple"%s>' % flatatt(final_attrs)]
- str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
+ str_values = set([force_unicode(v) for v in value]) # Normalize to strings.
for option_value, option_label in chain(self.choices, choices):
- option_value = smart_unicode(option_value)
+ option_value = force_unicode(option_value)
selected_html = (option_value in str_values) and ' selected="selected"' or ''
- output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(smart_unicode(option_label))))
+ output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(force_unicode(option_label))))
output.append(u'</select>')
return u'\n'.join(output)
@@ -220,8 +220,8 @@ class RadioInput(StrAndUnicode):
def __init__(self, name, value, attrs, choice, index):
self.name, self.value = name, value
self.attrs = attrs
- self.choice_value = smart_unicode(choice[0])
- self.choice_label = smart_unicode(choice[1])
+ self.choice_value = force_unicode(choice[0])
+ self.choice_label = force_unicode(choice[1])
self.index = index
def __unicode__(self):
@@ -254,13 +254,13 @@ class RadioFieldRenderer(StrAndUnicode):
def __unicode__(self):
"Outputs a <ul> for this set of radio fields."
- return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])
+ return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self])
class RadioSelect(Select):
def render(self, name, value, attrs=None, choices=()):
"Returns a RadioFieldRenderer instance rather than a Unicode string."
if value is None: value = ''
- str_value = smart_unicode(value) # Normalize to string.
+ str_value = force_unicode(value) # Normalize to string.
final_attrs = self.build_attrs(attrs)
return RadioFieldRenderer(name, str_value, final_attrs, list(chain(self.choices, choices)))
@@ -280,16 +280,16 @@ class CheckboxSelectMultiple(SelectMultiple):
has_id = attrs and 'id' in attrs
final_attrs = self.build_attrs(attrs, name=name)
output = [u'<ul>']
- str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
+ str_values = set([force_unicode(v) for v in value]) # Normalize to strings.
for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
# If an ID attribute was given, add a numeric index as a suffix,
# so that the checkboxes don't all have the same ID attribute.
if has_id:
final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
- option_value = smart_unicode(option_value)
+ option_value = force_unicode(option_value)
rendered_cb = cb.render(name, option_value)
- output.append(u'<li><label>%s %s</label></li>' % (rendered_cb, escape(smart_unicode(option_label))))
+ output.append(u'<li><label>%s %s</label></li>' % (rendered_cb, escape(force_unicode(option_label))))
output.append(u'</ul>')
return u'\n'.join(output)
diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
index 5814eef7ff..d0f9aca62a 100644
--- a/django/oldforms/__init__.py
+++ b/django/oldforms/__init__.py
@@ -2,7 +2,8 @@ from django.core import validators
from django.core.exceptions import PermissionDenied
from django.utils.html import escape
from django.conf import settings
-from django.utils.translation import gettext, ngettext
+from django.utils.translation import ugettext, ungettext
+from django.utils.encoding import smart_unicode, force_unicode, smart_str
FORM_FIELD_ID_PREFIX = 'id_'
@@ -66,7 +67,7 @@ class Manipulator(object):
errors.setdefault(field.field_name, []).extend(e.messages)
# if field.is_required and not new_data.get(field.field_name, False):
-# errors.setdefault(field.field_name, []).append(gettext_lazy('This field is required.'))
+# errors.setdefault(field.field_name, []).append(ugettext_lazy('This field is required.'))
# continue
# try:
# validator_list = field.validator_list
@@ -166,7 +167,11 @@ class FormFieldWrapper(object):
def __str__(self):
"Renders the field"
- return str(self.formfield.render(self.data))
+ return unicode(self).encode('utf-8')
+
+ def __unicode__(self):
+ "Renders the field"
+ return force_unicode(self.formfield.render(self.data))
def __repr__(self):
return '<FormFieldWrapper for "%s">' % self.formfield.field_name
@@ -196,7 +201,10 @@ class FormFieldCollection(FormFieldWrapper):
self.formfield_dict = formfield_dict
def __str__(self):
- return str(self.formfield_dict)
+ return unicode(self).encode('utf-8')
+
+ def __str__(self):
+ return unicode(self.formfield_dict)
def __getitem__(self, template_key):
"Look up field by template key; raise KeyError on failure"
@@ -294,8 +302,12 @@ class FormField(object):
Subclasses should also implement a render(data) method, which is responsible
for rending the form field in XHTML.
"""
+
def __str__(self):
- return self.render('')
+ return unicode(self).encode('utf-8')
+
+ def __unicode__(self):
+ return self.render(u'')
def __repr__(self):
return 'FormField "%s"' % self.field_name
@@ -354,7 +366,7 @@ class FormField(object):
def get_validation_errors(self, new_data):
errors = {}
if self.is_required and not new_data.get(self.field_name, False):
- errors.setdefault(self.field_name, []).append(gettext('This field is required.'))
+ errors.setdefault(self.field_name, []).append(ugettext('This field is required.'))
return errors
try:
for validator in self.validator_list:
@@ -388,24 +400,22 @@ class TextField(FormField):
self.member_name = member_name
def isValidLength(self, data, form):
- if data and self.maxlength and len(data.decode(settings.DEFAULT_CHARSET)) > self.maxlength:
- raise validators.ValidationError, ngettext("Ensure your text is less than %s character.",
+ if data and self.maxlength and len(smart_unicode(data)) > self.maxlength:
+ raise validators.ValidationError, ungettext("Ensure your text is less than %s character.",
"Ensure your text is less than %s characters.", self.maxlength) % self.maxlength
def hasNoNewlines(self, data, form):
if data and '\n' in data:
- raise validators.ValidationError, gettext("Line breaks are not allowed here.")
+ raise validators.ValidationError, ugettext("Line breaks are not allowed here.")
def render(self, data):
if data is None:
- data = ''
- maxlength = ''
+ data = u''
+ maxlength = u''
if self.maxlength:
- maxlength = 'maxlength="%s" ' % self.maxlength
- if isinstance(data, unicode):
- data = data.encode(settings.DEFAULT_CHARSET)
- return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
- (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
+ maxlength = u'maxlength="%s" ' % self.maxlength
+ return u'<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
+ (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and u' required' or '',
self.field_name, self.length, escape(data), maxlength)
def html2python(data):
@@ -428,10 +438,8 @@ class LargeTextField(TextField):
def render(self, data):
if data is None:
data = ''
- if isinstance(data, unicode):
- data = data.encode(settings.DEFAULT_CHARSET)
- return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
- (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
+ return u'<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
+ (self.get_id(), self.__class__.__name__, self.is_required and u' required' or u'',
self.field_name, self.rows, self.cols, escape(data))
class HiddenField(FormField):
@@ -441,7 +449,7 @@ class HiddenField(FormField):
self.validator_list = validator_list[:]
def render(self, data):
- return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
+ return u'<input type="hidden" id="%s" name="%s" value="%s" />' % \
(self.get_id(), self.field_name, escape(data))
class CheckboxField(FormField):
@@ -456,7 +464,7 @@ class CheckboxField(FormField):
checked_html = ''
if data or (data is '' and self.checked_by_default):
checked_html = ' checked="checked"'
- return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
+ return u'<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
(self.get_id(), self.__class__.__name__,
self.field_name, checked_html)
@@ -471,6 +479,7 @@ class SelectField(FormField):
def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None):
if validator_list is None: validator_list = []
if choices is None: choices = []
+ choices = [(k, smart_unicode(v, strings_only=True)) for k, v in choices]
self.field_name = field_name
# choices is a list of (value, human-readable key) tuples because order matters
self.choices, self.size, self.is_required = choices, size, is_required
@@ -479,23 +488,23 @@ class SelectField(FormField):
self.member_name = member_name
def render(self, data):
- output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
+ output = [u'<select id="%s" class="v%s%s" name="%s" size="%s">' % \
(self.get_id(), self.__class__.__name__,
- self.is_required and ' required' or '', self.field_name, self.size)]
- str_data = str(data) # normalize to string
+ self.is_required and u' required' or u'', self.field_name, self.size)]
+ str_data = smart_unicode(data) # normalize to string
for value, display_name in self.choices:
- selected_html = ''
- if str(value) == str_data:
- selected_html = ' selected="selected"'
- output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name)))
- output.append(' </select>')
- return '\n'.join(output)
+ selected_html = u''
+ if smart_unicode(value) == str_data:
+ selected_html = u' selected="selected"'
+ output.append(u' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name)))
+ output.append(u' </select>')
+ return u'\n'.join(output)
def isValidChoice(self, data, form):
- str_data = str(data)
- str_choices = [str(item[0]) for item in self.choices]
+ str_data = smart_unicode(data)
+ str_choices = [smart_str(item[0]) for item in self.choices]
if str_data not in str_choices:
- raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data': str_data, 'choices': str_choices}
+ raise validators.ValidationError, ugettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data': str_data, 'choices': str_choices}
class NullSelectField(SelectField):
"This SelectField converts blank fields to None"
@@ -509,6 +518,7 @@ class RadioSelectField(FormField):
def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None):
if validator_list is None: validator_list = []
if choices is None: choices = []
+ choices = [(k, smart_unicode(v)) for k, v in choices]
self.field_name = field_name
# choices is a list of (value, human-readable key) tuples because order matters
self.choices, self.is_required = choices, is_required
@@ -520,7 +530,7 @@ class RadioSelectField(FormField):
def render(self, data):
"""
Returns a special object, RadioFieldRenderer, that is iterable *and*
- has a default str() rendered output.
+ has a default unicode() rendered output.
This allows for flexible use in templates. You can just use the default
rendering:
@@ -537,44 +547,44 @@ class RadioSelectField(FormField):
class RadioFieldRenderer:
def __init__(self, datalist, ul_class):
self.datalist, self.ul_class = datalist, ul_class
- def __str__(self):
- "Default str() output for this radio field -- a <ul>"
- output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
- output.extend(['<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
- output.append('</ul>')
- return ''.join(output)
+ def __unicode__(self):
+ "Default unicode() output for this radio field -- a <ul>"
+ output = [u'<ul%s>' % (self.ul_class and u' class="%s"' % self.ul_class or u'')]
+ output.extend([u'<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
+ output.append(u'</ul>')
+ return u''.join(output)
def __iter__(self):
for d in self.datalist:
yield d
def __len__(self):
return len(self.datalist)
datalist = []
- str_data = str(data) # normalize to string
+ str_data = smart_unicode(data) # normalize to string
for i, (value, display_name) in enumerate(self.choices):
selected_html = ''
- if str(value) == str_data:
- selected_html = ' checked="checked"'
+ if smart_unicode(value) == str_data:
+ selected_html = u' checked="checked"'
datalist.append({
'value': value,
'name': display_name,
- 'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
- (self.get_id() + '_' + str(i), self.field_name, value, selected_html),
- 'label': '<label for="%s">%s</label>' % \
- (self.get_id() + '_' + str(i), display_name),
+ 'field': u'<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
+ (self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html),
+ 'label': u'<label for="%s">%s</label>' % \
+ (self.get_id() + u'_' + unicode(i), display_name),
})
return RadioFieldRenderer(datalist, self.ul_class)
def isValidChoice(self, data, form):
- str_data = str(data)
- str_choices = [str(item[0]) for item in self.choices]
+ str_data = smart_unicode(data)
+ str_choices = [smart_unicode(item[0]) for item in self.choices]
if str_data not in str_choices:
- raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':str_data, 'choices':str_choices}
+ raise validators.ValidationError, ugettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':str_data, 'choices':str_choices}
class NullBooleanField(SelectField):
"This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None"
def __init__(self, field_name, is_required=False, validator_list=None):
if validator_list is None: validator_list = []
- SelectField.__init__(self, field_name, choices=[('1', _('Unknown')), ('2', _('Yes')), ('3', _('No'))],
+ SelectField.__init__(self, field_name, choices=[('1', ugettext('Unknown')), ('2', ugettext('Yes')), ('3', ugettext('No'))],
is_required=is_required, validator_list=validator_list)
def render(self, data):
@@ -590,24 +600,24 @@ class NullBooleanField(SelectField):
class SelectMultipleField(SelectField):
requires_data_list = True
def render(self, data):
- output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \
- (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
+ output = [u'<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \
+ (self.get_id(), self.__class__.__name__, self.is_required and u' required' or u'',
self.field_name, self.size)]
- str_data_list = map(str, data) # normalize to strings
+ str_data_list = map(smart_unicode, data) # normalize to strings
for value, choice in self.choices:
- selected_html = ''
- if str(value) in str_data_list:
- selected_html = ' selected="selected"'
- output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice)))
- output.append(' </select>')
- return '\n'.join(output)
+ selected_html = u''
+ if smart_unicode(value) in str_data_list:
+ selected_html = u' selected="selected"'
+ output.append(u' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice)))
+ output.append(u' </select>')
+ return u'\n'.join(output)
def isValidChoice(self, field_data, all_data):
# data is something like ['1', '2', '3']
- str_choices = [str(item[0]) for item in self.choices]
- for val in map(str, field_data):
+ str_choices = [smart_unicode(item[0]) for item in self.choices]
+ for val in map(smart_unicode, field_data):
if val not in str_choices:
- raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':val, 'choices':str_choices}
+ raise validators.ValidationError, ugettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':val, 'choices':str_choices}
def html2python(data):
if data is None:
@@ -642,18 +652,18 @@ class CheckboxSelectMultipleField(SelectMultipleField):
new_data.setlist(self.field_name, data_list)
def render(self, data):
- output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
- str_data_list = map(str, data) # normalize to strings
+ output = [u'<ul%s>' % (self.ul_class and u' class="%s"' % self.ul_class or u'')]
+ str_data_list = map(smart_unicode, data) # normalize to strings
for value, choice in self.choices:
- checked_html = ''
- if str(value) in str_data_list:
- checked_html = ' checked="checked"'
- field_name = '%s%s' % (self.field_name, value)
- output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s value="on" /> <label for="%s">%s</label></li>' % \
+ checked_html = u''
+ if smart_unicode(value) in str_data_list:
+ checked_html = u' checked="checked"'
+ field_name = u'%s%s' % (self.field_name, value)
+ output.append(u'<li><input type="checkbox" id="%s" class="v%s" name="%s"%s value="on" /> <label for="%s">%s</label></li>' % \
(self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html,
self.get_id() + escape(value), choice))
- output.append('</ul>')
- return '\n'.join(output)
+ output.append(u'</ul>')
+ return u'\n'.join(output)
####################
# FILE UPLOADS #
@@ -669,12 +679,12 @@ class FileUploadField(FormField):
try:
content = field_data['content']
except TypeError:
- raise validators.CriticalValidationError, gettext("No file was submitted. Check the encoding type on the form.")
+ raise validators.CriticalValidationError, ugettext("No file was submitted. Check the encoding type on the form.")
if not content:
- raise validators.CriticalValidationError, gettext("The submitted file is empty.")
+ raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
def render(self, data):
- return '<input type="file" id="%s" class="v%s" name="%s" />' % \
+ return u'<input type="file" id="%s" class="v%s" name="%s" />' % \
(self.get_id(), self.__class__.__name__, self.field_name)
def html2python(data):
@@ -727,7 +737,7 @@ class SmallIntegerField(IntegerField):
def isSmallInteger(self, field_data, all_data):
if not -32768 <= int(field_data) <= 32767:
- raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.")
+ raise validators.CriticalValidationError, ugettext("Enter a whole number between -32,768 and 32,767.")
class PositiveIntegerField(IntegerField):
def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None):
@@ -737,7 +747,7 @@ class PositiveIntegerField(IntegerField):
def isPositive(self, field_data, all_data):
if int(field_data) < 0:
- raise validators.CriticalValidationError, gettext("Enter a positive number.")
+ raise validators.CriticalValidationError, ugettext("Enter a positive number.")
class PositiveSmallIntegerField(IntegerField):
def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None):
@@ -747,7 +757,7 @@ class PositiveSmallIntegerField(IntegerField):
def isPositiveSmall(self, field_data, all_data):
if not 0 <= int(field_data) <= 32767:
- raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.")
+ raise validators.CriticalValidationError, ugettext("Enter a whole number between 0 and 32,767.")
class FloatField(TextField):
def __init__(self, field_name, is_required=False, validator_list=None):
@@ -1005,9 +1015,9 @@ class CommaSeparatedIntegerField(TextField):
def render(self, data):
if data is None:
- data = ''
+ data = u''
elif isinstance(data, (list, tuple)):
- data = ','.join(data)
+ data = u','.join(data)
return super(CommaSeparatedIntegerField, self).render(data)
class RawIdAdminField(CommaSeparatedIntegerField):
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 4cda9bc8d0..b58e9f049c 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -58,8 +58,10 @@ import re
from inspect import getargspec
from django.conf import settings
from django.template.context import Context, RequestContext, ContextPopException
-from django.utils.functional import curry
+from django.utils.functional import curry, Promise
from django.utils.text import smart_split
+from django.utils.encoding import smart_unicode, force_unicode
+from django.utils.translation import ugettext as _
__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
@@ -122,6 +124,9 @@ class TemplateSyntaxError(Exception):
class TemplateDoesNotExist(Exception):
pass
+class TemplateEncodingError(Exception):
+ pass
+
class VariableDoesNotExist(Exception):
def __init__(self, msg, params=()):
@@ -155,6 +160,10 @@ class StringOrigin(Origin):
class Template(object):
def __init__(self, template_string, origin=None, name='<Unknown Template>'):
"Compilation stage"
+ try:
+ template_string = smart_unicode(template_string)
+ except UnicodeDecodeError:
+ raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
if settings.TEMPLATE_DEBUG and origin == None:
origin = StringOrigin(template_string)
# Could do some crazy stack-frame stuff to record where this string
@@ -693,6 +702,13 @@ def resolve_variable(path, context):
else:
raise
del bits[0]
+ if isinstance(current, (basestring, Promise)):
+ try:
+ current = force_unicode(current)
+ except UnicodeDecodeError:
+ # Failing to convert to unicode can happen sometimes (e.g. debug
+ # tracebacks). So we allow it in this particular instance.
+ pass
return current
class Node(object):
@@ -720,7 +736,7 @@ class NodeList(list):
bits.append(self.render_node(node, context))
else:
bits.append(node)
- return ''.join(bits)
+ return ''.join([force_unicode(b) for b in bits])
def get_nodes_by_type(self, nodetype):
"Return a list of all nodes of the given type"
@@ -730,7 +746,7 @@ class NodeList(list):
return nodes
def render_node(self, node, context):
- return(node.render(context))
+ return node.render(context)
class DebugNodeList(NodeList):
def render_node(self, node, context):
@@ -765,32 +781,17 @@ class VariableNode(Node):
def __repr__(self):
return "<Variable Node: %s>" % self.filter_expression
- def encode_output(self, output):
- # Check type so that we don't run str() on a Unicode object
- if not isinstance(output, basestring):
- try:
- return str(output)
- except UnicodeEncodeError:
- # If __str__() returns a Unicode object, convert it to bytestring.
- return unicode(output).encode(settings.DEFAULT_CHARSET)
- elif isinstance(output, unicode):
- return output.encode(settings.DEFAULT_CHARSET)
- else:
- return output
-
def render(self, context):
- output = self.filter_expression.resolve(context)
- return self.encode_output(output)
+ return self.filter_expression.resolve(context)
class DebugVariableNode(VariableNode):
def render(self, context):
try:
- output = self.filter_expression.resolve(context)
+ return self.filter_expression.resolve(context)
except TemplateSyntaxError, e:
if not hasattr(e, 'source'):
e.source = self.source
raise
- return self.encode_output(output)
def generic_tag_compiler(params, defaults, name, node_class, parser, token):
"Returns a template.Node subclass."
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index bbaceba24a..6aadbf2381 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -2,7 +2,8 @@
from django.template import resolve_variable, Library
from django.conf import settings
-from django.utils.translation import gettext, ngettext
+from django.utils.translation import ugettext, ungettext
+from django.utils.encoding import force_unicode, smart_str, iri_to_uri
import re
import random as random_module
@@ -12,29 +13,18 @@ register = Library()
# STRING DECORATOR #
#######################
-def smart_string(obj):
- # FUTURE: Unicode strings should probably be normalized to a specific
- # encoding and non-unicode strings should be converted to unicode too.
-# if isinstance(obj, unicode):
-# obj = obj.encode(settings.DEFAULT_CHARSET)
-# else:
-# obj = unicode(obj, settings.DEFAULT_CHARSET)
- # FUTURE: Replace dumb string logic below with cool unicode logic above.
- if not isinstance(obj, basestring):
- obj = str(obj)
- return obj
-
def stringfilter(func):
"""
- Decorator for filters which should only receive strings. The object passed
- as the first positional argument will be converted to a string.
+ Decorator for filters which should only receive unicode objects. The object
+ passed as the first positional argument will be converted to a unicode
+ object.
"""
def _dec(*args, **kwargs):
if args:
args = list(args)
- args[0] = smart_string(args[0])
+ args[0] = force_unicode(args[0])
return func(*args, **kwargs)
-
+
# Include a reference to the real function (used to check original
# arguments by the template parser).
_dec._decorated_function = getattr(func, '_decorated_function', func)
@@ -54,7 +44,7 @@ def capfirst(value):
"Capitalizes the first character of the value"
return value and value[0].upper() + value[1:]
capfirst = stringfilter(capfirst)
-
+
def fix_ampersands(value):
"Replaces ampersands with ``&amp;`` entities"
from django.utils.html import fix_ampersands
@@ -83,27 +73,32 @@ def floatformat(text, arg=-1):
try:
f = float(text)
except ValueError:
- return ''
+ return u''
try:
d = int(arg)
except ValueError:
- return smart_string(f)
+ return force_unicode(f)
m = f - int(f)
if not m and d < 0:
- return '%d' % int(f)
+ return u'%d' % int(f)
else:
- formatstr = '%%.%df' % abs(d)
+ formatstr = u'%%.%df' % abs(d)
return formatstr % f
+def iriencode(value):
+ "Escapes an IRI value for use in a URL"
+ return force_unicode(iri_to_uri(value))
+iriencode = stringfilter(iriencode)
+
def linenumbers(value):
"Displays text with line numbers"
from django.utils.html import escape
- lines = value.split('\n')
+ lines = value.split(u'\n')
# Find the maximum width of the line count, for use with zero padding string format command
- width = str(len(str(len(lines))))
+ width = unicode(len(unicode(len(lines))))
for i, line in enumerate(lines):
- lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
- return '\n'.join(lines)
+ lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))
+ return u'\n'.join(lines)
linenumbers = stringfilter(linenumbers)
def lower(value):
@@ -120,8 +115,13 @@ def make_list(value):
make_list = stringfilter(make_list)
def slugify(value):
- "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
- value = re.sub('[^\w\s-]', '', value).strip().lower()
+ """
+ Normalizes string, converts to lowercase, removes non-alpha chars and
+ converts spaces to hyphens.
+ """
+ import unicodedata
+ value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
+ value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
return re.sub('[-\s]+', '-', value)
slugify = stringfilter(slugify)
@@ -135,9 +135,9 @@ def stringformat(value, arg):
of Python string formatting
"""
try:
- return ("%" + str(arg)) % value
+ return (u"%" + unicode(arg)) % value
except (ValueError, TypeError):
- return ""
+ return u""
def title(value):
"Converts a string into titlecase"
@@ -155,8 +155,6 @@ def truncatewords(value, arg):
length = int(arg)
except ValueError: # invalid literal for int()
return value # Fail silently.
- if not isinstance(value, basestring):
- value = str(value)
return truncate_words(value, length)
truncatewords = stringfilter(truncatewords)
@@ -171,8 +169,6 @@ def truncatewords_html(value, arg):
length = int(arg)
except ValueError: # invalid literal for int()
return value # Fail silently.
- if not isinstance(value, basestring):
- value = str(value)
return truncate_html_words(value, length)
truncatewords_html = stringfilter(truncatewords_html)
@@ -183,10 +179,8 @@ upper = stringfilter(upper)
def urlencode(value):
"Escapes a value for use in a URL"
- import urllib
- if not isinstance(value, basestring):
- value = str(value)
- return urllib.quote(value)
+ from django.utils.http import urlquote
+ return urlquote(value)
urlencode = stringfilter(urlencode)
def urlize(value):
@@ -246,7 +240,7 @@ center = stringfilter(center)
def cut(value, arg):
"Removes all values of arg from the given string"
- return value.replace(arg, '')
+ return value.replace(arg, u'')
cut = stringfilter(cut)
###################
@@ -273,11 +267,11 @@ linebreaksbr = stringfilter(linebreaksbr)
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)
- endtag_re = re.compile('</%s>' % tags_re)
- value = starttag_re.sub('', value)
- value = endtag_re.sub('', value)
+ tags_re = u'(%s)' % u'|'.join(tags)
+ starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
+ endtag_re = re.compile(u'</%s>' % tags_re)
+ value = starttag_re.sub(u'', value)
+ value = endtag_re.sub(u'', value)
return value
removetags = stringfilter(removetags)
@@ -296,7 +290,7 @@ def dictsort(value, arg):
Takes a list of dicts, returns that list sorted by the property given in
the argument.
"""
- decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
+ decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for item in value]
decorated.sort()
return [item[1] for item in decorated]
@@ -305,7 +299,7 @@ def dictsortreversed(value, arg):
Takes a list of dicts, returns that list sorted in reverse order by the
property given in the argument.
"""
- decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
+ decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for item in value]
decorated.sort()
decorated.reverse()
return [item[1] for item in decorated]
@@ -315,12 +309,12 @@ def first(value):
try:
return value[0]
except IndexError:
- return ''
+ return u''
def join(value, arg):
"Joins a list with a string, like Python's ``str.join(list)``"
try:
- return arg.join(map(smart_string, value))
+ return arg.join(map(force_unicode, value))
except AttributeError: # fail silently but nicely
return value
@@ -346,7 +340,7 @@ def slice_(value, arg):
"""
try:
bits = []
- for x in arg.split(':'):
+ for x in arg.split(u':'):
if len(x) == 0:
bits.append(None)
else:
@@ -378,12 +372,12 @@ def unordered_list(value):
</li>
"""
def _helper(value, tabs):
- indent = '\t' * tabs
+ indent = u'\t' * tabs
if value[1]:
- return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
- '\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
+ return u'%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, force_unicode(value[0]), indent,
+ u'\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
else:
- return '%s<li>%s</li>' % (indent, value[0])
+ return u'%s<li>%s</li>' % (indent, force_unicode(value[0]))
return _helper(value, 1)
###################
@@ -421,7 +415,7 @@ def date(value, arg=None):
"Formats a date according to the given format"
from django.utils.dateformat import format
if not value:
- return ''
+ return u''
if arg is None:
arg = settings.DATE_FORMAT
return format(value, arg)
@@ -429,8 +423,8 @@ def date(value, arg=None):
def time(value, arg=None):
"Formats a time according to the given format"
from django.utils.dateformat import time_format
- if value in (None, ''):
- return ''
+ if value in (None, u''):
+ return u''
if arg is None:
arg = settings.TIME_FORMAT
return time_format(value, arg)
@@ -439,7 +433,7 @@ def timesince(value, arg=None):
'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
from django.utils.timesince import timesince
if not value:
- return ''
+ return u''
if arg:
return timesince(arg, value)
return timesince(value)
@@ -449,7 +443,7 @@ def timeuntil(value, arg=None):
from django.utils.timesince import timesince
from datetime import datetime
if not value:
- return ''
+ return u''
if arg:
return timesince(arg, value)
return timesince(datetime.now(), value)
@@ -488,8 +482,8 @@ def yesno(value, arg=None):
========== ====================== ==================================
"""
if arg is None:
- arg = gettext('yes,no,maybe')
- bits = arg.split(',')
+ arg = ugettext('yes,no,maybe')
+ bits = arg.split(u',')
if len(bits) < 2:
return value # Invalid arg.
try:
@@ -514,28 +508,28 @@ def filesizeformat(bytes):
try:
bytes = float(bytes)
except TypeError:
- return "0 bytes"
-
+ return u"0 bytes"
+
if bytes < 1024:
- return ngettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
+ return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
if bytes < 1024 * 1024:
- return gettext("%.1f KB") % (bytes / 1024)
+ return ugettext("%.1f KB") % (bytes / 1024)
if bytes < 1024 * 1024 * 1024:
- return gettext("%.1f MB") % (bytes / (1024 * 1024))
- return gettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
+ return ugettext("%.1f MB") % (bytes / (1024 * 1024))
+ return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
-def pluralize(value, arg='s'):
+def pluralize(value, arg=u's'):
"""
Returns a plural suffix if the value is not 1, for '1 vote' vs. '2 votes'
By default, 's' is used as a suffix; if an argument is provided, that string
is used instead. If the provided argument contains a comma, the text before
the comma is used for the singular case.
"""
- if not ',' in arg:
- arg = ',' + arg
- bits = arg.split(',')
+ if not u',' in arg:
+ arg = u',' + arg
+ bits = arg.split(u',')
if len(bits) > 2:
- return ''
+ return u''
singular_suffix, plural_suffix = bits[:2]
try:
@@ -562,7 +556,7 @@ def pprint(value):
try:
return pformat(value)
except Exception, e:
- return "Error in formatting:%s" % e
+ return u"Error in formatting:%s" % force_unicode(e)
# Syntax: register.filter(name of filter, callback)
register.filter(add)
@@ -582,6 +576,7 @@ register.filter(first)
register.filter(fix_ampersands)
register.filter(floatformat)
register.filter(get_digit)
+register.filter(iriencode)
register.filter(join)
register.filter(length)
register.filter(length_is)
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 19beb29141..7b3a261a5b 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -4,6 +4,7 @@ from django.template import Node, NodeList, Template, Context, resolve_variable
from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
from django.template import get_library, Library, InvalidTemplateLibrary
from django.conf import settings
+from django.utils.encoding import smart_str, smart_unicode
from django.utils.itercompat import groupby
import sys
import re
@@ -64,8 +65,8 @@ class FirstOfNode(Node):
except VariableDoesNotExist:
continue
if value:
- return str(value)
- return ''
+ return smart_unicode(value)
+ return u''
class ForNode(Node):
def __init__(self, loopvars, sequence, reversed, nodelist_loop):
@@ -337,7 +338,7 @@ class URLNode(Node):
def render(self, context):
from django.core.urlresolvers import reverse, NoReverseMatch
args = [arg.resolve(context) for arg in self.args]
- kwargs = dict([(k, v.resolve(context)) for k, v in self.kwargs.items()])
+ kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) for k, v in self.kwargs.items()])
try:
return reverse(self.view_name, args=args, kwargs=kwargs)
except NoReverseMatch:
diff --git a/django/template/loaders/app_directories.py b/django/template/loaders/app_directories.py
index c4e91df929..c520689644 100644
--- a/django/template/loaders/app_directories.py
+++ b/django/template/loaders/app_directories.py
@@ -34,7 +34,7 @@ def get_template_sources(template_name, template_dirs=None):
def load_template_source(template_name, template_dirs=None):
for filepath in get_template_sources(template_name, template_dirs):
try:
- return (open(filepath).read(), filepath)
+ return (open(filepath).read().decode(settings.FILE_CHARSET), filepath)
except IOError:
pass
raise TemplateDoesNotExist, template_name
diff --git a/django/template/loaders/eggs.py b/django/template/loaders/eggs.py
index 6184aeaccf..0c68153917 100644
--- a/django/template/loaders/eggs.py
+++ b/django/template/loaders/eggs.py
@@ -18,7 +18,7 @@ def load_template_source(template_name, template_dirs=None):
pkg_name = 'templates/' + template_name
for app in settings.INSTALLED_APPS:
try:
- return (resource_string(app, pkg_name), 'egg:%s:%s ' % (app, pkg_name))
+ return (resource_string(app, pkg_name), 'egg:%s:%s ' % (app, pkg_name)).decode(settings.FILE_CHARSET)
except:
pass
raise TemplateDoesNotExist, template_name
diff --git a/django/template/loaders/filesystem.py b/django/template/loaders/filesystem.py
index d01f54c5fe..3ba4625cda 100644
--- a/django/template/loaders/filesystem.py
+++ b/django/template/loaders/filesystem.py
@@ -14,7 +14,7 @@ def load_template_source(template_name, template_dirs=None):
tried = []
for filepath in get_template_sources(template_name, template_dirs):
try:
- return (open(filepath).read(), filepath)
+ return (open(filepath).read().decode(settings.FILE_CHARSET), filepath)
except IOError:
tried.append(filepath)
if tried:
diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py
index bf6497f9aa..1e85c6b5d1 100644
--- a/django/templatetags/i18n.py
+++ b/django/templatetags/i18n.py
@@ -11,7 +11,7 @@ class GetAvailableLanguagesNode(Node):
def render(self, context):
from django.conf import settings
- context[self.variable] = [(k, translation.gettext(v)) for k, v in settings.LANGUAGES]
+ context[self.variable] = [(k, translation.ugettext(v)) for k, v in settings.LANGUAGES]
return ''
class GetCurrentLanguageNode(Node):
@@ -40,7 +40,7 @@ class TranslateNode(Node):
if self.noop:
return value
else:
- return translation.gettext(value)
+ return translation.ugettext(value)
class BlockTranslateNode(Node):
def __init__(self, extra_context, singular, plural=None, countervar=None, counter=None):
@@ -68,9 +68,9 @@ class BlockTranslateNode(Node):
count = self.counter.resolve(context)
context[self.countervar] = count
plural = self.render_token_list(self.plural)
- result = translation.ngettext(singular, plural, count) % context
+ result = translation.ungettext(singular, plural, count) % context
else:
- result = translation.gettext(singular) % context
+ result = translation.ugettext(singular) % context
context.pop()
return result
diff --git a/django/test/client.py b/django/test/client.py
index e4fd54c23b..ed72ad8f77 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -10,9 +10,11 @@ from django.core.handlers.base import BaseHandler
from django.core.handlers.wsgi import WSGIRequest
from django.core.signals import got_request_exception
from django.dispatch import dispatcher
-from django.http import urlencode, SimpleCookie, HttpRequest
+from django.http import SimpleCookie, HttpRequest
from django.test import signals
from django.utils.functional import curry
+from django.utils.encoding import smart_str
+from django.utils.http import urlencode
BOUNDARY = 'BoUnDaRyStRiNg'
MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
@@ -61,29 +63,30 @@ def encode_multipart(boundary, data):
as an application/octet-stream; otherwise, str(value) will be sent.
"""
lines = []
+ to_str = lambda s: smart_str(s, settings.DEFAULT_CHARSET)
for (key, value) in data.items():
if isinstance(value, file):
lines.extend([
'--' + boundary,
- 'Content-Disposition: form-data; name="%s"; filename="%s"' % (key, value.name),
+ 'Content-Disposition: form-data; name="%s"; filename="%s"' % (to_str(key), to_str(value.name)),
'Content-Type: application/octet-stream',
'',
value.read()
])
- elif hasattr(value, '__iter__'):
+ elif hasattr(value, '__iter__'):
for item in value:
- lines.extend([
- '--' + boundary,
- 'Content-Disposition: form-data; name="%s"' % key,
- '',
- str(item)
+ lines.extend([
+ '--' + boundary,
+ 'Content-Disposition: form-data; name="%s"' % to_str(key),
+ '',
+ to_str(item)
])
else:
lines.extend([
'--' + boundary,
- 'Content-Disposition: form-data; name="%s"' % key,
+ 'Content-Disposition: form-data; name="%s"' % to_str(key),
'',
- str(value)
+ to_str(value)
])
lines.extend([
@@ -115,7 +118,7 @@ class Client:
self.defaults = defaults
self.cookies = SimpleCookie()
self.exc_info = None
-
+
def store_exc_info(self, *args, **kwargs):
"""
Utility method that can be used to store exceptions when they are
@@ -131,7 +134,7 @@ class Client:
return SessionWrapper(cookie.value)
return {}
session = property(_session)
-
+
def request(self, **request):
"""
The master request method. Composes the environment dictionary
@@ -179,7 +182,7 @@ class Client:
# Look for a signalled exception and reraise it
if self.exc_info:
raise self.exc_info[1], None, self.exc_info[2]
-
+
# Update persistent cookie data
if response.cookies:
self.cookies.update(response.cookies)
@@ -243,9 +246,9 @@ class Client:
# Set the session values
Session.objects.save(obj.session_key, request.session._session,
- datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
+ datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
return True
else:
return False
-
+
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 2bc1b5a5f8..bc4cde226c 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -129,11 +129,11 @@ class TestCase(unittest.TestCase):
if isinstance(response.template, list):
template_names = [t.name for t in response.template]
self.failUnless(template_name in template_names,
- "Template '%s' was not one of the templates used to render the response. Templates used: %s" %
- (template_name, template_names))
+ u"Template '%s' was not one of the templates used to render the response. Templates used: %s" %
+ (template_name, u', '.join(template_names)))
elif response.template:
self.assertEqual(template_name, response.template.name,
- "Template '%s' was not used to render the response. Actual template was '%s'" %
+ u"Template '%s' was not used to render the response. Actual template was '%s'" %
(template_name, response.template.name))
else:
self.fail('No templates used to render the response')
@@ -142,8 +142,8 @@ class TestCase(unittest.TestCase):
"Assert that the template with the provided name was NOT used in rendering the response"
if isinstance(response.template, list):
self.failIf(template_name in [t.name for t in response.template],
- "Template '%s' was used unexpectedly in rendering the response" % template_name)
+ u"Template '%s' was used unexpectedly in rendering the response" % template_name)
elif response.template:
self.assertNotEqual(template_name, response.template.name,
- "Template '%s' was used unexpectedly in rendering the response" % template_name)
-
+ u"Template '%s' was used unexpectedly in rendering the response" % template_name)
+
diff --git a/django/test/utils.py b/django/test/utils.py
index 4a858a6ec7..0836b7eac6 100644
--- a/django/test/utils.py
+++ b/django/test/utils.py
@@ -12,9 +12,9 @@ from django.template import Template
TEST_DATABASE_PREFIX = 'test_'
def instrumented_test_render(self, context):
- """An instrumented Template render method, providing a signal
+ """
+ An instrumented Template render method, providing a signal
that can be intercepted by the test system Client
-
"""
dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
return self.nodelist.render(context)
diff --git a/django/utils/cache.py b/django/utils/cache.py
index c8031a409a..5b90ca291f 100644
--- a/django/utils/cache.py
+++ b/django/utils/cache.py
@@ -20,6 +20,7 @@ A example: i18n middleware would need to distinguish caches by the
import datetime, md5, re
from django.conf import settings
from django.core.cache import cache
+from django.utils.encoding import smart_str
cc_delim_re = re.compile(r'\s*,\s*')
@@ -46,7 +47,7 @@ def patch_cache_control(response, **kwargs):
if t[1] == True:
return t[0]
else:
- return t[0] + '=' + str(t[1])
+ return t[0] + '=' + smart_str(t[1])
if response.has_header('Cache-Control'):
cc = cc_delim_re.split(response['Cache-Control'])
diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py
index a558e3a69f..8a90e21cb1 100644
--- a/django/utils/dateformat.py
+++ b/django/utils/dateformat.py
@@ -11,9 +11,10 @@ Usage:
>>>
"""
-from django.utils.dates import MONTHS, MONTHS_3, MONTHS_AP, WEEKDAYS
+from django.utils.dates import MONTHS, MONTHS_3, MONTHS_AP, WEEKDAYS, WEEKDAYS_ABBR
from django.utils.tzinfo import LocalTimezone
-from django.utils.translation import gettext as _
+from django.utils.translation import string_concat, ugettext as _
+from django.utils.encoding import force_unicode
from calendar import isleap, monthrange
import re, time
@@ -23,12 +24,12 @@ re_escaped = re.compile(r'\\(.)')
class Formatter(object):
def format(self, formatstr):
pieces = []
- for i, piece in enumerate(re_formatchars.split(formatstr)):
+ for i, piece in enumerate(re_formatchars.split(force_unicode(formatstr))):
if i % 2:
- pieces.append(str(getattr(self, piece)()))
+ pieces.append(force_unicode(getattr(self, piece)()))
elif piece:
pieces.append(re_escaped.sub(r'\1', piece))
- return ''.join(pieces)
+ return u''.join(pieces)
class TimeFormat(Formatter):
def __init__(self, t):
@@ -52,13 +53,14 @@ class TimeFormat(Formatter):
def f(self):
"""
- Time, in 12-hour hours and minutes, with minutes left off if they're zero.
+ Time, in 12-hour hours and minutes, with minutes left off if they're
+ zero.
Examples: '1', '1:30', '2:05', '2'
Proprietary extension.
"""
if self.data.minute == 0:
return self.g()
- return '%s:%s' % (self.g(), self.i())
+ return u'%s:%s' % (self.g(), self.i())
def g(self):
"Hour, 12-hour format without leading zeros; i.e. '1' to '12'"
@@ -74,15 +76,15 @@ class TimeFormat(Formatter):
def h(self):
"Hour, 12-hour format; i.e. '01' to '12'"
- return '%02d' % self.g()
+ return u'%02d' % self.g()
def H(self):
"Hour, 24-hour format; i.e. '00' to '23'"
- return '%02d' % self.G()
+ return u'%02d' % self.G()
def i(self):
"Minutes; i.e. '00' to '59'"
- return '%02d' % self.data.minute
+ return u'%02d' % self.data.minute
def P(self):
"""
@@ -95,11 +97,11 @@ class TimeFormat(Formatter):
return _('midnight')
if self.data.minute == 0 and self.data.hour == 12:
return _('noon')
- return '%s %s' % (self.f(), self.a())
+ return u'%s %s' % (self.f(), self.a())
def s(self):
"Seconds; i.e. '00' to '59'"
- return '%02d' % self.data.second
+ return u'%02d' % self.data.second
class DateFormat(TimeFormat):
year_days = [None, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
@@ -117,11 +119,11 @@ class DateFormat(TimeFormat):
def d(self):
"Day of the month, 2 digits with leading zeros; i.e. '01' to '31'"
- return '%02d' % self.data.day
+ return u'%02d' % self.data.day
def D(self):
"Day of the week, textual, 3 letters; e.g. 'Fri'"
- return WEEKDAYS[self.data.weekday()][0:3]
+ return WEEKDAYS_ABBR[self.data.weekday()]
def F(self):
"Month, textual, long; e.g. 'January'"
@@ -130,9 +132,9 @@ class DateFormat(TimeFormat):
def I(self):
"'1' if Daylight Savings Time, '0' otherwise."
if self.timezone.dst(self.data):
- return '1'
+ return u'1'
else:
- return '0'
+ return u'0'
def j(self):
"Day of the month without leading zeros; i.e. '1' to '31'"
@@ -148,7 +150,7 @@ class DateFormat(TimeFormat):
def m(self):
"Month; i.e. '01' to '12'"
- return '%02d' % self.data.month
+ return u'%02d' % self.data.month
def M(self):
"Month, textual, 3 letters; e.g. 'Jan'"
@@ -165,7 +167,7 @@ class DateFormat(TimeFormat):
def O(self):
"Difference to Greenwich time in hours; e.g. '+0200'"
tz = self.timezone.utcoffset(self.data)
- return "%+03d%02d" % (tz.seconds // 3600, (tz.seconds // 60) % 60)
+ return u"%+03d%02d" % (tz.seconds // 3600, (tz.seconds // 60) % 60)
def r(self):
"RFC 822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
@@ -174,26 +176,26 @@ class DateFormat(TimeFormat):
def S(self):
"English ordinal suffix for the day of the month, 2 characters; i.e. 'st', 'nd', 'rd' or 'th'"
if self.data.day in (11, 12, 13): # Special case
- return 'th'
+ return u'th'
last = self.data.day % 10
if last == 1:
- return 'st'
+ return u'st'
if last == 2:
- return 'nd'
+ return u'nd'
if last == 3:
- return 'rd'
- return 'th'
+ return u'rd'
+ return u'th'
def t(self):
"Number of days in the given month; i.e. '28' to '31'"
- return '%02d' % monthrange(self.data.year, self.data.month)[1]
+ return u'%02d' % monthrange(self.data.year, self.data.month)[1]
def T(self):
"Time zone of this machine; e.g. 'EST' or 'MDT'"
name = self.timezone.tzname(self.data)
if name is None:
name = self.format('O')
- return name
+ return unicode(name)
def U(self):
"Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
@@ -232,7 +234,7 @@ class DateFormat(TimeFormat):
def y(self):
"Year, 2 digits; e.g. '99'"
- return str(self.data.year)[2:]
+ return unicode(self.data.year)[2:]
def Y(self):
"Year, 4 digits; e.g. '1999'"
diff --git a/django/utils/dates.py b/django/utils/dates.py
index 111f32e4fc..4427af8ca9 100644
--- a/django/utils/dates.py
+++ b/django/utils/dates.py
@@ -1,11 +1,15 @@
"Commonly-used date structures"
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ugettext_lazy as _
WEEKDAYS = {
0:_('Monday'), 1:_('Tuesday'), 2:_('Wednesday'), 3:_('Thursday'), 4:_('Friday'),
5:_('Saturday'), 6:_('Sunday')
}
+WEEKDAYS_ABBR = {
+ 0:_('Mon'), 1:_('Tue'), 2:_('Wed'), 3:_('Thu'), 4:_('Fri'),
+ 5:_('Sat'), 6:_('Sun')
+}
WEEKDAYS_REV = {
'monday':0, 'tuesday':1, 'wednesday':2, 'thursday':3, 'friday':4,
'saturday':5, 'sunday':6
diff --git a/django/utils/encoding.py b/django/utils/encoding.py
index 4774fb0d26..7515d0c41b 100644
--- a/django/utils/encoding.py
+++ b/django/utils/encoding.py
@@ -1,32 +1,84 @@
+import types
+import urllib
from django.conf import settings
from django.utils.functional import Promise
-def smart_unicode(s):
+class StrAndUnicode(object):
+ """
+ A class whose __str__ returns its __unicode__ as a UTF-8 bytestring.
+
+ Useful as a mix-in.
+ """
+ def __str__(self):
+ return self.__unicode__().encode('utf-8')
+
+def smart_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
+ """
+ Returns a unicode object representing 's'. Treats bytestrings using the
+ 'encoding' codec.
+
+ 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, or similar. It will
- # already be encoded in DEFAULT_CHARSET on evaluation and we don't want
- # to evaluate it until render time.
- # FIXME: This isn't totally consistent, because it eventually returns a
- # bytestring rather than a unicode object. It works wherever we use
- # smart_unicode() at the moment. Fixing this requires work in the
- # i18n internals.
+ # The input is the result of a gettext_lazy() call.
+ return s
+ return force_unicode(s, encoding, strings_only, errors)
+
+def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
+ """
+ Similar to smart_unicode, 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 strings_only and isinstance(s, (types.NoneType, int)):
return s
if not isinstance(s, basestring,):
if hasattr(s, '__unicode__'):
s = unicode(s)
else:
- s = unicode(str(s), settings.DEFAULT_CHARSET)
+ s = unicode(str(s), encoding, errors)
elif not isinstance(s, unicode):
- s = unicode(s, settings.DEFAULT_CHARSET)
+ s = unicode(s, encoding, errors)
return s
-class StrAndUnicode(object):
+def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
"""
- A class whose __str__ returns its __unicode__ as a bytestring
- according to settings.DEFAULT_CHARSET.
+ Returns a bytestring version of 's', encoded as specified in 'encoding'.
- Useful as a mix-in.
+ If strings_only is True, don't convert (some) non-string-like objects.
"""
- def __str__(self):
- return self.__unicode__().encode(settings.DEFAULT_CHARSET)
+ if strings_only and isinstance(s, (types.NoneType, int)):
+ return s
+ if isinstance(s, Promise):
+ return unicode(s).encode(encoding, errors)
+ elif not isinstance(s, basestring):
+ try:
+ return str(s)
+ except UnicodeEncodeError:
+ return unicode(s).encode(encoding, errors)
+ elif isinstance(s, unicode):
+ return s.encode(encoding, errors)
+ elif s and encoding != 'utf-8':
+ return s.decode('utf-8', errors).encode(encoding, errors)
+ else:
+ return s
+
+def iri_to_uri(iri):
+ """
+ Convert an Internationalized Resource Identifier (IRI) portion to a URI
+ portion that is suitable for inclusion in a URL.
+
+ This is the algorithm from section 3.1 of RFC 3987. However, since we are
+ assuming input is either UTF-8 or unicode already, we can simplify things a
+ little from the full method.
+
+ Returns an ASCII string containing the encoded result.
+ """
+ # The list of safe characters here is constructed from the printable ASCII
+ # characters that are not explicitly excluded by the list at the end of
+ # section 3.1 of RFC 3987.
+ if iri is None:
+ return iri
+ return urllib.quote(smart_str(iri), safe='/#%[]=:;$&()+,!?')
diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py
index 2c82e9a37a..064ec19120 100644
--- a/django/utils/feedgenerator.py
+++ b/django/utils/feedgenerator.py
@@ -19,6 +19,7 @@ http://diveintomark.org/archives/2004/02/04/incompatible-rss
"""
from django.utils.xmlutils import SimplerXMLGenerator
+from django.utils.encoding import force_unicode, iri_to_uri
import datetime, re, time
import email.Utils
@@ -34,25 +35,28 @@ def get_tag_uri(url, date):
if date is not None:
tag = re.sub('/', ',%s:/' % date.strftime('%Y-%m-%d'), tag, 1)
tag = re.sub('#', '/', tag)
- return 'tag:' + tag
+ return u'tag:' + tag
class SyndicationFeed(object):
"Base class for all syndication feeds. Subclasses should provide write()"
def __init__(self, title, link, description, language=None, author_email=None,
author_name=None, author_link=None, subtitle=None, categories=None,
feed_url=None, feed_copyright=None):
+ to_unicode = lambda s: force_unicode(s, strings_only=True)
+ if categories:
+ categories = [force_unicode(c) for c in categories]
self.feed = {
- 'title': title,
- 'link': link,
- 'description': description,
- 'language': language,
- 'author_email': author_email,
- 'author_name': author_name,
- 'author_link': author_link,
- 'subtitle': subtitle,
+ 'title': to_unicode(title),
+ 'link': iri_to_uri(link),
+ 'description': to_unicode(description),
+ 'language': force_unicode(language),
+ 'author_email': to_unicode(author_email),
+ 'author_name': to_unicode(author_name),
+ 'author_link': iri_to_uri(author_link),
+ 'subtitle': to_unicode(subtitle),
'categories': categories or (),
- 'feed_url': feed_url,
- 'feed_copyright': feed_copyright,
+ 'feed_url': iri_to_uri(feed_url),
+ 'feed_copyright': to_unicode(feed_copyright),
}
self.items = []
@@ -64,19 +68,22 @@ class SyndicationFeed(object):
objects except pubdate, which is a datetime.datetime object, and
enclosure, which is an instance of the Enclosure class.
"""
+ to_unicode = lambda s: force_unicode(s, strings_only=True)
+ if categories:
+ categories = [to_unicode(c) for c in categories]
self.items.append({
- 'title': title,
- 'link': link,
- 'description': description,
- 'author_email': author_email,
- 'author_name': author_name,
- 'author_link': author_link,
+ 'title': to_unicode(title),
+ 'link': iri_to_uri(link),
+ 'description': to_unicode(description),
+ 'author_email': to_unicode(author_email),
+ 'author_name': to_unicode(author_name),
+ 'author_link': iri_to_uri(author_link),
'pubdate': pubdate,
- 'comments': comments,
- 'unique_id': unique_id,
+ 'comments': to_unicode(comments),
+ 'unique_id': to_unicode(unique_id),
'enclosure': enclosure,
'categories': categories or (),
- 'item_copyright': item_copyright,
+ 'item_copyright': to_unicode(item_copyright),
})
def num_items(self):
@@ -114,7 +121,8 @@ class Enclosure(object):
"Represents an RSS enclosure"
def __init__(self, url, length, mime_type):
"All args are expected to be Python Unicode objects"
- self.url, self.length, self.mime_type = url, length, mime_type
+ self.length, self.mime_type = length, mime_type
+ self.url = iri_to_uri(url)
class RssFeed(SyndicationFeed):
mime_type = 'application/rss+xml'
diff --git a/django/utils/functional.py b/django/utils/functional.py
index a57546ad2d..656f3406c4 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -18,7 +18,7 @@ def memoize(func, cache):
return result
return wrapper
-class Promise:
+class Promise(object):
"""
This is just a base class for the proxy class created in
the closure of the lazy function. It can be used to recognize
@@ -47,6 +47,11 @@ def lazy(func, *resultclasses):
self.__dispatch[resultclass] = {}
for (k, v) in resultclass.__dict__.items():
setattr(self, k, self.__promise__(resultclass, k, v))
+ self._delegate_str = str in resultclasses
+ self._delegate_unicode = unicode in resultclasses
+ assert not (self._delegate_str and self._delegate_unicode), "Cannot call lazy() with both str and unicode return types."
+ if self._delegate_unicode:
+ self.__unicode__ = self.__unicode_cast
def __promise__(self, klass, funcname, func):
# Builds a wrapper around some magic method and registers that magic
@@ -62,8 +67,56 @@ def lazy(func, *resultclasses):
self.__dispatch[klass][funcname] = func
return __wrapper__
+ def __unicode_cast(self):
+ return self.__func(*self.__args, **self.__kw)
+
+ def __str__(self):
+ # As __str__ is always a method on the type (class), it is looked
+ # up (and found) there first. So we can't just assign to it on a
+ # per-instance basis in __init__.
+ if self._delegate_str:
+ return str(self.__func(*self.__args, **self.__kw))
+ else:
+ return Promise.__str__(self)
+
+ def __cmp__(self, rhs):
+ if self._delegate_str:
+ s = str(self.__func(*self.__args, **self.__kw))
+ elif self._delegate_unicode:
+ s = unicode(self.__func(*self.__args, **self.__kw))
+ else:
+ s = self.__func(*self.__args, **self.__kw)
+ if isinstance(rhs, Promise):
+ return -cmp(rhs, s)
+ else:
+ return cmp(s, rhs)
+
+ def __mod__(self, rhs):
+ if self._delegate_str:
+ return str(self) % rhs
+ elif self._delegate_unicode:
+ return unicode(self) % rhs
+ else:
+ raise AssertionError('__mod__ not supported for non-string types')
+
def __wrapper__(*args, **kw):
# Creates the proxy object, instead of the actual value.
return __proxy__(args, kw)
return __wrapper__
+
+def allow_lazy(func, *resultclasses):
+ """
+ A decorator that allows a function to be called with one or more lazy
+ arguments. If none of the args are lazy, the function is evaluated
+ immediately, otherwise a __proxy__ is returned that will evaluate the
+ function when needed.
+ """
+ def wrapper(*args, **kwargs):
+ for arg in list(args) + kwargs.values():
+ if isinstance(arg, Promise):
+ break
+ else:
+ return func(*args, **kwargs)
+ return lazy(func, *resultclasses)(*args, **kwargs)
+ return wrapper
diff --git a/django/utils/html.py b/django/utils/html.py
index e1860627ce..56216b5d48 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -1,7 +1,10 @@
"HTML utilities suitable for global use."
-import re, string
-from django.utils.encoding import smart_unicode
+import re
+import string
+import urllib
+from django.utils.encoding import force_unicode, smart_str
+from django.utils.functional import allow_lazy
# Configuration for urlize() function
LEADING_PUNCTUATION = ['(', '<', '&lt;']
@@ -24,32 +27,36 @@ del x # Temporary variable
def escape(html):
"Returns the given HTML with ampersands, quotes and carets encoded"
- if not isinstance(html, basestring):
- html = str(html)
- return html.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
+ return force_unicode(html).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
+escape = allow_lazy(escape, unicode)
def linebreaks(value):
"Converts newlines into <p> and <br />s"
- value = re.sub(r'\r\n|\r|\n', '\n', value) # normalize newlines
+ value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines
paras = re.split('\n{2,}', value)
- paras = ['<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
- return '\n\n'.join(paras)
+ paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
+ return u'\n\n'.join(paras)
+linebreaks = allow_lazy(linebreaks, unicode)
def strip_tags(value):
"Returns the given HTML with all tags stripped"
- return re.sub(r'<[^>]*?>', '', value)
+ return re.sub(r'<[^>]*?>', '', force_unicode(value))
+strip_tags = allow_lazy(strip_tags)
def strip_spaces_between_tags(value):
"Returns the given HTML with spaces between tags removed"
- return re.sub(r'>\s+<', '><', value)
+ return re.sub(r'>\s+<', '><', force_unicode(value))
+strip_spaces_between_tags = allow_lazy(strip_spaces_between_tags, unicode)
def strip_entities(value):
"Returns the given HTML with all entities (&something;) stripped"
- return re.sub(r'&(?:\w+|#\d);', '', value)
+ return re.sub(r'&(?:\w+|#\d);', '', force_unicode(value))
+strip_entities = allow_lazy(strip_entities, unicode)
def fix_ampersands(value):
"Returns the given HTML with all unencoded ampersands encoded correctly"
- return unencoded_ampersands_re.sub('&amp;', value)
+ return unencoded_ampersands_re.sub('&amp;', force_unicode(value))
+fix_ampersands = allow_lazy(fix_ampersands, unicode)
def urlize(text, trim_url_limit=None, nofollow=False):
"""
@@ -65,7 +72,7 @@ def urlize(text, trim_url_limit=None, nofollow=False):
attribute.
"""
trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
- words = word_split_re.split(text)
+ words = word_split_re.split(force_unicode(text))
nofollow_attr = nofollow and ' rel="nofollow"' or ''
for i, word in enumerate(words):
match = punctuation_re.match(word)
@@ -82,7 +89,8 @@ def urlize(text, trim_url_limit=None, nofollow=False):
middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
if lead + middle + trail != word:
words[i] = lead + middle + trail
- return ''.join(words)
+ return u''.join(words)
+urlize = allow_lazy(urlize, unicode)
def clean_html(text):
"""
@@ -97,7 +105,7 @@ def clean_html(text):
bottom of the text.
"""
from django.utils.text import normalize_newlines
- text = normalize_newlines(text)
+ text = normalize_newlines(force_unicode(text))
text = re.sub(r'<(/?)\s*b\s*>', '<\\1strong>', text)
text = re.sub(r'<(/?)\s*i\s*>', '<\\1em>', text)
text = fix_ampersands(text)
@@ -110,9 +118,10 @@ def clean_html(text):
s = match.group().replace('</p>', '</li>')
for d in DOTS:
s = s.replace('<p>%s' % d, '<li>')
- return '<ul>\n%s\n</ul>' % s
+ return u'<ul>\n%s\n</ul>' % s
text = hard_coded_bullets_re.sub(replace_p_tags, text)
# Remove stuff like "<p>&nbsp;&nbsp;</p>", but only if it's at the bottom of the text.
text = trailing_empty_content_re.sub('', text)
return text
+clean_html = allow_lazy(clean_html, unicode)
diff --git a/django/utils/http.py b/django/utils/http.py
new file mode 100644
index 0000000000..5a0c18d1c0
--- /dev/null
+++ b/django/utils/http.py
@@ -0,0 +1,35 @@
+import urllib
+from django.utils.encoding import smart_str, force_unicode
+from django.utils.functional import allow_lazy
+
+def urlquote(url, safe='/'):
+ """
+ A version of Python's urllib.quote() function that can operate on unicode
+ strings. The url is first UTF-8 encoded before quoting. The returned string
+ can safely be used as part of an argument to a subsequent iri_to_uri() call
+ without double-quoting occurring.
+ """
+ return force_unicode(urllib.quote(smart_str(url)))
+urlquote = allow_lazy(urlquote, unicode)
+
+def urlquote_plus(url, safe=''):
+ """
+ A version of Python's urllib.quote_plus() function that can operate on
+ unicode strings. The url is first UTF-8 encoded before quoting. The
+ returned string can safely be used as part of an argument to a subsequent
+ iri_to_uri() call without double-quoting occurring.
+ """
+ return force_unicode(urllib.quote_plus(smart_str(url), safe))
+urlquote_plus = allow_lazy(urlquote_plus, unicode)
+
+def urlencode(query, doseq=0):
+ """
+ A version of Python's urllib.urlencode() function that can operate on
+ unicode strings. The parameters are first case to UTF-8 encoded strings and
+ then encoded as per normal.
+ """
+ if hasattr(query, 'items'):
+ query = query.items()
+ return urllib.urlencode([(smart_str(k), smart_str(v)) for k,
+ v in query], doseq)
+
diff --git a/django/utils/stopwords.py b/django/utils/stopwords.py
index dea5660413..18aeb7f5d3 100644
--- a/django/utils/stopwords.py
+++ b/django/utils/stopwords.py
@@ -38,5 +38,5 @@ def strip_stopwords(sentence):
for word in words:
if word.lower() not in stopwords:
sentence.append(word)
- return ' '.join(sentence)
+ return u' '.join(sentence)
diff --git a/django/utils/text.py b/django/utils/text.py
index c73ab908f3..c41c35151b 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -1,15 +1,18 @@
import re
-
from django.conf import settings
+from django.utils.encoding import force_unicode
+from django.utils.functional import allow_lazy
# Capitalizes the first letter of a string.
-capfirst = lambda x: x and x[0].upper() + x[1:]
+capfirst = lambda x: x and force_unicode(x)[0].upper() + force_unicode(x)[1:]
+capfirst = allow_lazy(capfirst, unicode)
def wrap(text, width):
"""
A word-wrap function that preserves existing line breaks and most spaces in
the text. Expects that existing line breaks are posix newlines.
"""
+ text = force_unicode(text)
def _generator():
it = iter(text.split(' '))
word = it.next()
@@ -29,29 +32,34 @@ def wrap(text, width):
if len(lines) > 1:
pos = len(lines[-1])
yield word
- return "".join(_generator())
+ return u''.join(_generator())
+wrap = allow_lazy(wrap, unicode)
def truncate_words(s, num):
"Truncates a string after a certain number of words."
+ s = force_unicode(s)
length = int(num)
words = s.split()
if len(words) > length:
words = words[:length]
if not words[-1].endswith('...'):
words.append('...')
- return ' '.join(words)
+ return u' '.join(words)
+truncate_words = allow_lazy(truncate_words, unicode)
def truncate_html_words(s, num):
"""
- Truncates html to a certain number of words (not counting tags and comments).
- Closes opened tags if they were correctly closed in the given html.
+ Truncates html to a certain number of words (not counting tags and
+ comments). Closes opened tags if they were correctly closed in the given
+ html.
"""
+ s = force_unicode(s)
length = int(num)
if length <= 0:
- return ''
+ return u''
html4_singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input')
# Set up regular expressions
- re_words = re.compile(r'&.*?;|<.*?>|([A-Za-z0-9][\w-]*)')
+ re_words = re.compile(r'&.*?;|<.*?>|(\w[\w-]*)', re.U)
re_tag = re.compile(r'<(/)?([^ ]+?)(?: (/)| .*?)?>')
# Count non-HTML words and keep note of open tags
pos = 0
@@ -100,6 +108,7 @@ def truncate_html_words(s, num):
out += '</%s>' % tag
# Return string
return out
+truncate_html_words = allow_lazy(truncate_html_words, unicode)
def get_valid_filename(s):
"""
@@ -110,10 +119,11 @@ def get_valid_filename(s):
>>> get_valid_filename("john's portrait in 2004.jpg")
'johns_portrait_in_2004.jpg'
"""
- s = s.strip().replace(' ', '_')
+ s = force_unicode(s).strip().replace(' ', '_')
return re.sub(r'[^-A-Za-z0-9_.]', '', s)
+get_valid_filename = allow_lazy(get_valid_filename, unicode)
-def get_text_list(list_, last_word='or'):
+def get_text_list(list_, last_word=u'or'):
"""
>>> get_text_list(['a', 'b', 'c', 'd'])
'a, b, c or d'
@@ -126,23 +136,22 @@ def get_text_list(list_, last_word='or'):
>>> get_text_list([])
''
"""
- if len(list_) == 0: return ''
- if len(list_) == 1: return list_[0]
- return '%s %s %s' % (', '.join([str(i) for i in list_][:-1]), last_word, list_[-1])
+ if len(list_) == 0: return u''
+ if len(list_) == 1: return force_unicode(list_[0])
+ return u'%s %s %s' % (', '.join([force_unicode(i) for i in list_][:-1]), force_unicode(last_word), force_unicode(list_[-1]))
+get_text_list = allow_lazy(get_text_list, unicode)
def normalize_newlines(text):
- return re.sub(r'\r\n|\r|\n', '\n', text)
+ return force_unicode(re.sub(r'\r\n|\r|\n', '\n', text))
+normalize_newlines = allow_lazy(normalize_newlines, unicode)
def recapitalize(text):
"Recapitalizes text, placing caps after end-of-sentence punctuation."
-# capwords = ()
- text = text.lower()
+ text = force_unicode(text).lower()
capsRE = re.compile(r'(?:^|(?<=[\.\?\!] ))([a-z])')
text = capsRE.sub(lambda x: x.group(1).upper(), text)
-# for capword in capwords:
-# capwordRE = re.compile(r'\b%s\b' % capword, re.I)
-# text = capwordRE.sub(capword, text)
return text
+recapitalize = allow_lazy(recapitalize)
def phone2numeric(phone):
"Converts a phone number with letters into its numeric equivalent."
@@ -153,6 +162,7 @@ def phone2numeric(phone):
's': '7', 'r': '7', 'u': '8', 't': '8', 'w': '9', 'v': '8',
'y': '9', 'x': '9'}.get(m.group(0).lower())
return letters.sub(char2number, phone)
+phone2numeric = allow_lazy(phone2numeric)
# From http://www.xhaus.com/alan/python/httpcomp.html#gzip
# Used with permission.
@@ -172,7 +182,7 @@ def javascript_quote(s, quote_double_quotes=False):
return r"\u%04x" % ord(match.group(1))
if type(s) == str:
- s = s.decode(settings.DEFAULT_CHARSET)
+ s = s.decode('utf-8')
elif type(s) != unicode:
raise TypeError, s
s = s.replace('\\', '\\\\')
@@ -183,6 +193,7 @@ def javascript_quote(s, quote_double_quotes=False):
if quote_double_quotes:
s = s.replace('"', '&quot;')
return str(ustring_re.sub(fix, s))
+javascript_quote = allow_lazy(javascript_quote, unicode)
smart_split_re = re.compile('("(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'|[^\\s]+)')
def smart_split(text):
@@ -195,6 +206,7 @@ def smart_split(text):
>>> list(smart_split('This is "a person\'s" test.'))
['This', 'is', '"a person\'s"', 'test.']
"""
+ text = force_unicode(text)
for bit in smart_split_re.finditer(text):
bit = bit.group(0)
if bit[0] == '"' and bit[-1] == '"':
@@ -203,3 +215,5 @@ def smart_split(text):
yield "'" + bit[1:-1].replace("\\'", "'").replace("\\\\", "\\") + "'"
else:
yield bit
+smart_split = allow_lazy(smart_split, unicode)
+
diff --git a/django/utils/timesince.py b/django/utils/timesince.py
index 394f818395..8ecf970439 100644
--- a/django/utils/timesince.py
+++ b/django/utils/timesince.py
@@ -1,6 +1,6 @@
import datetime, math, time
from django.utils.tzinfo import LocalTimezone
-from django.utils.translation import ngettext, gettext
+from django.utils.translation import ungettext, ugettext
def timesince(d, now=None):
"""
@@ -9,12 +9,12 @@ def timesince(d, now=None):
Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
"""
chunks = (
- (60 * 60 * 24 * 365, lambda n: ngettext('year', 'years', n)),
- (60 * 60 * 24 * 30, lambda n: ngettext('month', 'months', n)),
- (60 * 60 * 24 * 7, lambda n : ngettext('week', 'weeks', n)),
- (60 * 60 * 24, lambda n : ngettext('day', 'days', n)),
- (60 * 60, lambda n: ngettext('hour', 'hours', n)),
- (60, lambda n: ngettext('minute', 'minutes', n))
+ (60 * 60 * 24 * 365, lambda n: ungettext('year', 'years', n)),
+ (60 * 60 * 24 * 30, lambda n: ungettext('month', 'months', n)),
+ (60 * 60 * 24 * 7, lambda n : ungettext('week', 'weeks', n)),
+ (60 * 60 * 24, lambda n : ungettext('day', 'days', n)),
+ (60 * 60, lambda n: ungettext('hour', 'hours', n)),
+ (60, lambda n: ungettext('minute', 'minutes', n))
)
# Convert datetime.date to datetime.datetime for comparison
if d.__class__ is not datetime.datetime:
@@ -37,14 +37,14 @@ def timesince(d, now=None):
if count != 0:
break
if count < 0:
- return gettext('%d milliseconds') % math.floor((now - d).microseconds / 1000)
- s = gettext('%(number)d %(type)s') % {'number': count, 'type': name(count)}
+ return ugettext('%d milliseconds') % math.floor((now - d).microseconds / 1000)
+ s = ugettext('%(number)d %(type)s') % {'number': count, 'type': name(count)}
if i + 1 < len(chunks):
# Now get the second item
seconds2, name2 = chunks[i + 1]
count2 = (since - (seconds * count)) / seconds2
if count2 != 0:
- s += gettext(', %(number)d %(type)s') % {'number': count2, 'type': name2(count2)}
+ s += ugettext(', %(number)d %(type)s') % {'number': count2, 'type': name2(count2)}
return s
def timeuntil(d, now=None):
diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py
index dbb97af76c..13fc8a847a 100644
--- a/django/utils/translation/__init__.py
+++ b/django/utils/translation/__init__.py
@@ -7,7 +7,8 @@ __all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',
'ngettext_lazy', 'string_concat', 'activate', 'deactivate',
'get_language', 'get_language_bidi', 'get_date_formats',
'get_partial_date_formats', 'check_for_language', 'to_locale',
- 'get_language_from_request', 'install', 'templatize']
+ 'get_language_from_request', 'install', 'templatize', 'ugettext',
+ 'ungettext', 'deactivate_all']
# Here be dragons, so a short explanation of the logic won't hurt:
# We are trying to solve two problems: (1) access settings, in particular
@@ -48,19 +49,28 @@ del g, delayed_loader
def gettext_noop(message):
return real_gettext_noop(message)
+ugettext_noop = gettext_noop
+
def gettext(message):
return real_gettext(message)
-
def ngettext(singular, plural, number):
return real_ngettext(singular, plural, number)
+def ugettext(message):
+ return real_ugettext(message)
+
+def ungettext(singular, plural, number):
+ return real_ungettext(singular, plural, number)
+
def string_concat(*strings):
return real_string_concat(*strings)
-ngettext_lazy = lazy(ngettext, str, unicode)
-gettext_lazy = lazy(gettext, str, unicode)
-string_concat = lazy(string_concat, str, unicode)
+ngettext_lazy = lazy(ngettext, str)
+gettext_lazy = lazy(gettext, str)
+ungettext_lazy = lazy(ungettext, unicode)
+ugettext_lazy = lazy(ugettext, unicode)
+string_concat = lazy(string_concat, unicode)
def activate(language):
return real_activate(language)
@@ -95,3 +105,6 @@ def install():
def templatize(src):
return real_templatize(src)
+def deactivate_all():
+ return real_deactivate_all()
+
diff --git a/django/utils/translation/trans_null.py b/django/utils/translation/trans_null.py
index 10b07529e3..e3f89567a5 100644
--- a/django/utils/translation/trans_null.py
+++ b/django/utils/translation/trans_null.py
@@ -3,15 +3,19 @@
# settings.USE_I18N = False can use this module rather than trans_real.py.
from django.conf import settings
+from django.utils.encoding import force_unicode
def ngettext(singular, plural, number):
if number == 1: return singular
return plural
ngettext_lazy = ngettext
-string_concat = lambda *strings: ''.join([str(el) for el in strings])
+def ungettext(singular, plural, number):
+ return force_unicode(ngettext(singular, plural, number))
+
+string_concat = lambda *strings: u''.join([force_unicode(el) for el in strings])
activate = lambda x: None
-deactivate = install = lambda: None
+deactivate = deactivate_all = install = lambda: None
get_language = lambda: settings.LANGUAGE_CODE
get_language_bidi = lambda: settings.LANGUAGE_CODE in settings.LANGUAGES_BIDI
get_date_formats = lambda: (settings.DATE_FORMAT, settings.DATETIME_FORMAT, settings.TIME_FORMAT)
@@ -30,6 +34,9 @@ TECHNICAL_ID_MAP = {
def gettext(message):
return TECHNICAL_ID_MAP.get(message, message)
+def ugettext(message):
+ return force_unicode(gettext(message))
+
gettext_noop = gettext_lazy = _ = gettext
def to_locale(language):
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index 293b4ef9cd..765152afce 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -3,6 +3,7 @@
import os, re, sys
import gettext as gettext_module
from cStringIO import StringIO
+from django.utils.encoding import force_unicode
try:
import threading
@@ -57,10 +58,10 @@ class DjangoTranslation(gettext_module.GNUTranslations):
# the output charset. Before 2.4, the output charset is
# identical with the translation file charset.
try:
- self.set_output_charset(settings.DEFAULT_CHARSET)
+ self.set_output_charset('utf-8')
except AttributeError:
pass
- self.django_output_charset = settings.DEFAULT_CHARSET
+ self.django_output_charset = 'utf-8'
self.__language = '??'
def merge(self, other):
@@ -202,6 +203,14 @@ def deactivate():
if currentThread() in _active:
del _active[currentThread()]
+def deactivate_all():
+ """
+ Makes the active translation object a NullTranslations() instance. This is
+ useful when we want delayed translations to appear as the original string
+ for some reason.
+ """
+ _active[currentThread()] = gettext_module.NullTranslations()
+
def get_language():
"Returns the currently selected language."
t = _active.get(currentThread(), None)
@@ -238,49 +247,72 @@ def catalog():
_default = translation(settings.LANGUAGE_CODE)
return _default
-def gettext(message):
+def do_translate(message, translation_function):
"""
- This function will be patched into the builtins module to provide the _
- helper function. It will use the current thread as a discriminator to find
- the translation object to use. If no current translation is activated, the
- message will be run through the default translation object.
+ Translate 'message' using the given 'translation_function' name -- which
+ will be either gettext or ugettext.
"""
global _default, _active
t = _active.get(currentThread(), None)
if t is not None:
- return t.gettext(message)
+ return getattr(t, translation_function)(message)
if _default is None:
from django.conf import settings
_default = translation(settings.LANGUAGE_CODE)
- return _default.gettext(message)
+ return getattr(_default, translation_function)(message)
+
+def gettext(message):
+ """
+ This function will be patched into the builtins module to provide the _
+ helper function. It will use the current thread as a discriminator to find
+ the translation object to use. If no current translation is activated, the
+ message will be run through the default translation object.
+ """
+ return do_translate(message, 'gettext')
+
+def ugettext(message):
+ return do_translate(message, 'ugettext')
def gettext_noop(message):
"""
Marks strings for translation but doesn't translate them now. This can be
used to store strings in global variables that should stay in the base
- language (because they might be used externally) and will be translated later.
+ language (because they might be used externally) and will be translated
+ later.
"""
return message
-def ngettext(singular, plural, number):
- """
- Returns the translation of either the singular or plural, based on the number.
- """
+def do_ntranslate(singular, plural, number, translation_function):
global _default, _active
t = _active.get(currentThread(), None)
if t is not None:
- return t.ngettext(singular, plural, number)
+ return getattr(t, translation_function)(singular, plural, number)
if _default is None:
from django.conf import settings
_default = translation(settings.LANGUAGE_CODE)
- return _default.ngettext(singular, plural, number)
+ return getattr(_default, translation_function)(singular, plural, number)
+
+def ngettext(singular, plural, number):
+ """
+ Returns a UTF-8 bytestring of the translation of either the singular or
+ plural, based on the number.
+ """
+ return do_ntranslate(singular, plural, number, 'ngettext')
+
+def ungettext(singular, plural, number):
+ """
+ Returns a unicode strings of the translation of either the singular or
+ plural, based on the number.
+ """
+ return do_ntranslate(singular, plural, number, 'ungettext')
def check_for_language(lang_code):
"""
- Checks whether there is a global language file for the given language code.
- This is used to decide whether a user-provided language is available. This is
- only used for language codes from either the cookies or session.
+ Checks whether there is a global language file for the given language
+ code. This is used to decide whether a user-provided language is
+ available. This is only used for language codes from either the cookies or
+ session.
"""
from django.conf import settings
globalpath = os.path.join(os.path.dirname(sys.modules[settings.__module__].__file__), 'locale')
@@ -291,9 +323,10 @@ def check_for_language(lang_code):
def get_language_from_request(request):
"""
- Analyzes the request to find what language the user wants the system to show.
- Only languages listed in settings.LANGUAGES are taken into account. If the user
- requests a sublanguage where we have a main language, we send out the main language.
+ Analyzes the request to find what language the user wants the system to
+ show. Only languages listed in settings.LANGUAGES are taken into account.
+ If the user requests a sublanguage where we have a main language, we send
+ out the main language.
"""
global _accepted
from django.conf import settings
@@ -355,9 +388,9 @@ def get_date_formats():
one, the formats provided in the settings will be used.
"""
from django.conf import settings
- date_format = _('DATE_FORMAT')
- datetime_format = _('DATETIME_FORMAT')
- time_format = _('TIME_FORMAT')
+ date_format = ugettext('DATE_FORMAT')
+ datetime_format = ugettext('DATETIME_FORMAT')
+ time_format = ugettext('TIME_FORMAT')
if date_format == 'DATE_FORMAT':
date_format = settings.DATE_FORMAT
if datetime_format == 'DATETIME_FORMAT':
@@ -373,8 +406,8 @@ def get_partial_date_formats():
one, the formats provided in the settings will be used.
"""
from django.conf import settings
- year_month_format = _('YEAR_MONTH_FORMAT')
- month_day_format = _('MONTH_DAY_FORMAT')
+ year_month_format = ugettext('YEAR_MONTH_FORMAT')
+ month_day_format = ugettext('MONTH_DAY_FORMAT')
if year_month_format == 'YEAR_MONTH_FORMAT':
year_month_format = settings.YEAR_MONTH_FORMAT
if month_day_format == 'MONTH_DAY_FORMAT':
@@ -483,9 +516,7 @@ def templatize(src):
def string_concat(*strings):
""""
- lazy variant of string concatenation, needed for translations that are
- constructed from multiple parts. Handles lazy strings and non-strings by
- first turning all arguments to strings, before joining them.
+ Lazy variant of string concatenation, needed for translations that are
+ constructed from multiple parts.
"""
- return ''.join([str(el) for el in strings])
-
+ return u''.join([force_unicode(s) for s in strings])
diff --git a/django/utils/tzinfo.py b/django/utils/tzinfo.py
index cc9f028e91..e2e1d10fc1 100644
--- a/django/utils/tzinfo.py
+++ b/django/utils/tzinfo.py
@@ -1,13 +1,17 @@
"Implementation of tzinfo classes for use with datetime.datetime."
+import locale
import time
from datetime import timedelta, tzinfo
+from django.utils.encoding import smart_unicode
+
+DEFAULT_ENCODING = locale.getdefaultlocale()[1] or 'ascii'
class FixedOffset(tzinfo):
"Fixed offset in minutes east from UTC."
def __init__(self, offset):
self.__offset = timedelta(minutes=offset)
- self.__name = "%+03d%02d" % (offset // 60, offset % 60)
+ self.__name = u"%+03d%02d" % (offset // 60, offset % 60)
def __repr__(self):
return self.__name
@@ -25,7 +29,7 @@ class LocalTimezone(tzinfo):
"Proxy timezone information from time module."
def __init__(self, dt):
tzinfo.__init__(self, dt)
- self._tzname = time.tzname[self._isdst(dt)]
+ self._tzname = self.tzname(dt)
def __repr__(self):
return self._tzname
@@ -43,7 +47,10 @@ class LocalTimezone(tzinfo):
return timedelta(0)
def tzname(self, dt):
- return time.tzname[self._isdst(dt)]
+ try:
+ return smart_unicode(time.tzname[self._isdst(dt)], DEFAULT_ENCODING)
+ except UnicodeDecodeError:
+ return None
def _isdst(self, dt):
tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.weekday(), 0, -1)
diff --git a/django/views/debug.py b/django/views/debug.py
index a534f17b33..d2efe76072 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -2,6 +2,7 @@ from django.conf import settings
from django.template import Template, Context, TemplateDoesNotExist
from django.utils.html import escape
from django.http import HttpResponseServerError, HttpResponseNotFound
+from django.utils.encoding import smart_unicode
import os, re, sys
HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST')
@@ -125,7 +126,7 @@ def technical_500_response(request, exc_type, exc_value, tb):
t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
c = Context({
'exception_type': exc_type.__name__,
- 'exception_value': exc_value,
+ 'exception_value': smart_unicode(exc_value, errors='replace'),
'frames': frames,
'lastframe': frames[-1],
'request': request,
@@ -190,6 +191,17 @@ def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_na
if source is None:
return None, [], None, []
+ encoding=None
+ for line in source[:2]:
+ # File coding may be specified (and may not be UTF-8). Match
+ # pattern from PEP-263 (http://www.python.org/dev/peps/pep-0263/)
+ match = re.search(r'coding[:=]\s*([-\w.]+)', line)
+ if match:
+ encoding = match.group(1)
+ break
+ if encoding:
+ source = [unicode(sline, encoding) for sline in source]
+
lower_bound = max(0, lineno - context_lines)
upper_bound = lineno + context_lines
diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py
index 28987f7544..a4559ad495 100644
--- a/django/views/generic/create_update.py
+++ b/django/views/generic/create_update.py
@@ -6,7 +6,7 @@ from django.contrib.auth.views import redirect_to_login
from django.template import RequestContext
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
-from django.utils.translation import gettext
+from django.utils.translation import ugettext
def create_object(request, model, template_name=None,
template_loader=loader, extra_context=None, post_save_redirect=None,
@@ -40,7 +40,7 @@ def create_object(request, model, template_name=None,
new_object = manipulator.save(new_data)
if request.user.is_authenticated():
- request.user.message_set.create(message=gettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
+ request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
# Redirect to the new object: first by trying post_save_redirect,
# then by obj.get_absolute_url; fail if neither works.
@@ -114,7 +114,7 @@ def update_object(request, model, object_id=None, slug=None,
object = manipulator.save(new_data)
if request.user.is_authenticated():
- request.user.message_set.create(message=gettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
+ request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
# Do a post-after-redirect so that reload works, etc.
if post_save_redirect:
@@ -181,7 +181,7 @@ def delete_object(request, model, post_delete_redirect,
if request.method == 'POST':
object.delete()
if request.user.is_authenticated():
- request.user.message_set.create(message=gettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
+ request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
return HttpResponseRedirect(post_delete_redirect)
else:
if not template_name:
diff --git a/docs/contributing.txt b/docs/contributing.txt
index 2813793fa5..e58b06881a 100644
--- a/docs/contributing.txt
+++ b/docs/contributing.txt
@@ -368,6 +368,7 @@ Model style
* All database fields
* ``class Meta``
* ``class Admin``
+ * ``def __unicode__()``
* ``def __str__()``
* ``def save()``
* ``def get_absolute_url()``
diff --git a/docs/db-api.txt b/docs/db-api.txt
index a4b920fb33..ef3d811189 100644
--- a/docs/db-api.txt
+++ b/docs/db-api.txt
@@ -15,14 +15,14 @@ a weblog application::
name = models.CharField(maxlength=100)
tagline = models.TextField()
- def __str__(self):
+ def __unicode__(self):
return self.name
class Author(models.Model):
name = models.CharField(maxlength=50)
email = models.URLField()
- def __str__(self):
+ def __unicode__(self):
return self.name
class Entry(models.Model):
@@ -32,7 +32,7 @@ a weblog application::
pub_date = models.DateTimeField()
authors = models.ManyToManyField(Author)
- def __str__(self):
+ def __unicode__(self):
return self.headline
Creating objects
diff --git a/docs/forms.txt b/docs/forms.txt
index f6cb55a3f6..18d3d3fcbe 100644
--- a/docs/forms.txt
+++ b/docs/forms.txt
@@ -47,7 +47,7 @@ this document, we'll be working with the following model, a "place" object::
class Admin:
pass
- def __str__(self):
+ def __unicode__(self):
return self.name
Defining the above class is enough to create an admin interface to a ``Place``,
diff --git a/docs/i18n.txt b/docs/i18n.txt
index 27abadacc9..9fe02c0342 100644
--- a/docs/i18n.txt
+++ b/docs/i18n.txt
@@ -68,23 +68,41 @@ In Python code
Standard translation
~~~~~~~~~~~~~~~~~~~~
-Specify a translation string by using the function ``_()``. (Yes, the name of
-the function is the "underscore" character.) This function is available
-globally in any Python module; you don't have to import it.
+Specify a translation string by using the function ``ugettext()``. It's
+convention to import this as a shorter alias, ``_``, to save typing.
+
+.. note::
+ Python's standard library ``gettext`` module installs ``_()`` into the
+ global namespace, as an alias for ``gettext()``. In Django, we have chosen
+ not to follow this practice, for a couple of reasons:
+
+ 1. For international character set (Unicode) support, ``ugettext()`` is
+ more useful than ``gettext()``. Sometimes, you should be using
+ ``ugettext_lazy()`` as the default translation method for a particular
+ file. Without ``_()`` in the global namespace, the developer has to
+ think about which is the most appropriate translation function.
+
+ 2. The underscore character (``_``) is used to represent "the previous
+ result" in Python's interactive shell and doctest tests. Installing a
+ global ``_()`` function causes interference. Explicitly importing
+ ``ugettext()`` as ``_()`` avoids this problem.
In this example, the text ``"Welcome to my site."`` is marked as a translation
string::
+ from django.utils.translation import ugettext as _
+
def my_view(request):
output = _("Welcome to my site.")
return HttpResponse(output)
-The function ``django.utils.translation.gettext()`` is identical to ``_()``.
-This example is identical to the previous one::
+Obviously, you could code this without using the alias. This example is
+identical to the previous one::
+
+ from django.utils.translation import ugettext
- from django.utils.translation import gettext
def my_view(request):
- output = gettext("Welcome to my site.")
+ output = ugettext("Welcome to my site.")
return HttpResponse(output)
Translation works on computed values. This example is identical to the previous
@@ -107,7 +125,7 @@ examples, is that Django's translation-string-detecting utility,
``make-messages.py``, won't be able to find these strings. More on
``make-messages`` later.)
-The strings you pass to ``_()`` or ``gettext()`` can take placeholders,
+The strings you pass to ``_()`` or ``ugettext()`` can take placeholders,
specified with Python's standard named-string interpolation syntax. Example::
def my_view(request, n):
@@ -120,14 +138,14 @@ while a Spanish translation may be ``"Me llamo Adrian."`` -- with the
placeholder (the name) placed after the translated text instead of before it.
For this reason, you should use named-string interpolation (e.g., ``%(name)s``)
-instead of positional interpolation (e.g., ``%s`` or ``%d``). If you used
-positional interpolation, translations wouldn't be able to reorder placeholder
-text.
+instead of positional interpolation (e.g., ``%s`` or ``%d``) whenever you
+have more than a single parameter. If you used positional interpolation,
+translations wouldn't be able to reorder placeholder text.
Marking strings as no-op
~~~~~~~~~~~~~~~~~~~~~~~~
-Use the function ``django.utils.translation.gettext_noop()`` to mark a string
+Use the function ``django.utils.translation.ugettext_noop()`` to mark a string
as a translation string without translating it. The string is later translated
from a variable.
@@ -139,35 +157,35 @@ as when the string is presented to the user.
Lazy translation
~~~~~~~~~~~~~~~~
-Use the function ``django.utils.translation.gettext_lazy()`` to translate
+Use the function ``django.utils.translation.ugettext_lazy()`` to translate
strings lazily -- when the value is accessed rather than when the
-``gettext_lazy()`` function is called.
+``ugettext_lazy()`` function is called.
For example, to translate a model's ``help_text``, do the following::
- from django.utils.translation import gettext_lazy
+ from django.utils.translation import ugettext_lazy
class MyThing(models.Model):
- name = models.CharField(help_text=gettext_lazy('This is the help text'))
+ name = models.CharField(help_text=ugettext_lazy('This is the help text'))
-In this example, ``gettext_lazy()`` stores a lazy reference to the string --
+In this example, ``ugettext_lazy()`` stores a lazy reference to the string --
not the actual translation. The translation itself will be done when the string
is used in a string context, such as template rendering on the Django admin site.
-If you don't like the verbose name ``gettext_lazy``, you can just alias it as
+If you don't like the verbose name ``ugettext_lazy``, you can just alias it as
``_`` (underscore), like so::
- from django.utils.translation import gettext_lazy as _
+ from django.utils.translation import ugettext_lazy as _
class MyThing(models.Model):
name = models.CharField(help_text=_('This is the help text'))
-Always use lazy translations in `Django models`_. And it's a good idea to add
+Always use lazy translations in `Django models`_. It's a good idea to add
translations for the field names and table names, too. This means writing
explicit ``verbose_name`` and ``verbose_name_plural`` options in the ``Meta``
class, though::
- from django.utils.translation import gettext_lazy as _
+ from django.utils.translation import ugettext_lazy as _
class MyThing(models.Model):
name = models.CharField(_('name'), help_text=_('This is the help text'))
@@ -180,24 +198,24 @@ class, though::
Pluralization
~~~~~~~~~~~~~
-Use the function ``django.utils.translation.ngettext()`` to specify pluralized
+Use the function ``django.utils.translation.ungettext()`` to specify pluralized
messages. Example::
- from django.utils.translation import ngettext
+ from django.utils.translation import ungettext
def hello_world(request, count):
- page = ngettext('there is %(count)d object', 'there are %(count)d objects', count) % {
+ page = ungettext('there is %(count)d object', 'there are %(count)d objects', count) % {
'count': count,
}
return HttpResponse(page)
-``ngettext`` takes three arguments: the singular translation string, the plural
+``ungettext`` takes three arguments: the singular translation string, the plural
translation string and the number of objects (which is passed to the
translation languages as the ``count`` variable).
In template code
----------------
-Using translations in `Django templates`_ uses two template tags and a slightly
+Translations in `Django templates`_ uses two template tags and a slightly
different syntax than in Python code. To give your template access to these
tags, put ``{% load i18n %}`` toward the top of your template.
@@ -243,7 +261,7 @@ To pluralize, specify both the singular and plural forms with the
{% endblocktrans %}
Internally, all block and inline translations use the appropriate
-``gettext`` / ``ngettext`` call.
+``ugettext`` / ``ungettext`` call.
Each ``RequestContext`` has access to two translation-specific variables:
@@ -276,6 +294,71 @@ string, so they don't need to be aware of translations.
.. _Django templates: ../templates_python/
+Working with lazy translation objects
+=====================================
+
+Using ``ugettext_lazy()`` and ``ungettext_lazy()`` to mark strings in models
+and utility functions is a common operation. When you're working with these
+objects elsewhere in your code, you should ensure that you don't accidentally
+convert them to strings, because they should be converted as late as possible
+(so that the correct locale is in effect). This necessitates the use of a
+couple of helper functions.
+
+Joining strings: string_concat()
+--------------------------------
+
+Standard Python string joins (``''.join([...])``) will not work on lists
+containing lazy translation objects. Instead, you can use
+``django.utils.translation.string_concat()``, which creates a lazy object that
+concatenates its contents *and* converts them to strings only when the result
+is included in a string. For example::
+
+ from django.utils.translation import string_concat
+ ...
+ name = ugettext_lazy(u'John Lennon')
+ instrument = ugettext_lazy(u'guitar')
+ result = string_concat([name, ': ', instrument])
+
+In this case, the lazy translations in ``result`` will only be converted to
+strings when ``result`` itself is used in a string (usually at template
+rendering time).
+
+The allow_lazy() decorator
+--------------------------
+
+Django offers many utility functions (particularly in ``django.utils``) that
+take a string as their first argument and do something to that string. These
+functions are used by template filters as well as directly in other code.
+
+If you write your own similar functions and deal with translations, you'll
+face the problem of what to do when the first argument is a lazy translation
+object. You don't want to convert it to a string immediately, because you might
+be using this function outside of a view (and hence the current thread's locale
+setting will not be correct).
+
+For cases like this, use the ``django.utils.functional.allow_lazy()``
+decorator. It modifies the function so that *if* it's called with a lazy
+translation as the first argument, the function evaluation is delayed until it
+needs to be converted to a string.
+
+For example::
+
+ from django.utils.functional import allow_lazy
+
+ def fancy_utility_function(s, ...):
+ # Do some conversion on string 's'
+ ...
+ fancy_utility_function = allow_lazy(fancy_utility_function, unicode)
+
+The ``allow_lazy()`` decorator takes, in addition to the function to decorate,
+a number of extra arguments (``*args``) specifying the type(s) that the
+original function can return. Usually, it's enough to include ``unicode`` here
+and ensure that your function returns only Unicode strings.
+
+Using this decorator means you can write your function and assume that the
+input is a proper string, then add support for lazy translation objects at the
+end.
+
How to create language files
============================
@@ -487,26 +570,26 @@ Notes:
* If you define a custom ``LANGUAGES`` setting, as explained in the
previous bullet, it's OK to mark the languages as translation strings
- -- but use a "dummy" ``gettext()`` function, not the one in
+ -- but use a "dummy" ``ugettext()`` function, not the one in
``django.utils.translation``. You should *never* import
``django.utils.translation`` from within your settings file, because that
module in itself depends on the settings, and that would cause a circular
import.
- The solution is to use a "dummy" ``gettext()`` function. Here's a sample
+ The solution is to use a "dummy" ``ugettext()`` function. Here's a sample
settings file::
- gettext = lambda s: s
+ ugettext = lambda s: s
LANGUAGES = (
- ('de', gettext('German')),
- ('en', gettext('English')),
+ ('de', ugettext('German')),
+ ('en', ugettext('English')),
)
With this arrangement, ``make-messages.py`` will still find and mark
these strings for translation, but the translation won't happen at
runtime -- so you'll have to remember to wrap the languages in the *real*
- ``gettext()`` in any code that uses ``LANGUAGES`` at runtime.
+ ``ugettext()`` in any code that uses ``LANGUAGES`` at runtime.
* The ``LocaleMiddleware`` can only select languages for which there is a
Django-provided base translation. If you want to provide translations
@@ -712,23 +795,23 @@ interface to access it::
document.write(gettext('this is to be translated'));
-There even is a ``ngettext`` interface and a string interpolation function::
+There even is a ``ungettext`` interface and a string interpolation function::
d = {
count: 10
};
- s = interpolate(ngettext('this is %(count)s object', 'this are %(count)s objects', d.count), d);
+ s = interpolate(ungettext('this is %(count)s object', 'this are %(count)s objects', d.count), d);
The ``interpolate`` function supports both positional interpolation and named
interpolation. So the above could have been written as::
- s = interpolate(ngettext('this is %s object', 'this are %s objects', 11), [11]);
+ s = interpolate(ungettext('this is %s object', 'this are %s objects', 11), [11]);
The interpolation syntax is borrowed from Python. You shouldn't go over the top
with string interpolation, though: this is still JavaScript, so the code will
have to do repeated regular-expression substitutions. This isn't as fast as
string interpolation in Python, so keep it to those cases where you really
-need it (for example, in conjunction with ``ngettext`` to produce proper
+need it (for example, in conjunction with ``ungettext`` to produce proper
pluralizations).
Creating JavaScript translation catalogs
@@ -750,16 +833,13 @@ Specialities of Django translation
If you know ``gettext``, you might note these specialities in the way Django
does translation:
- * The string domain is ``django`` or ``djangojs``. The string domain is used to
- differentiate between different programs that store their data in a
- common message-file library (usually ``/usr/share/locale/``). The ``django``
- domain is used for python and template translation strings and is loaded into
- the global translation catalogs. The ``djangojs`` domain is only used for
- JavaScript translation catalogs to make sure that those are as small as
- possible.
- * Django only uses ``gettext`` and ``gettext_noop``. That's because Django
- always uses ``DEFAULT_CHARSET`` strings internally. There isn't much use
- in using ``ugettext``, because you'll always need to produce utf-8
- anyway.
+ * The string domain is ``django`` or ``djangojs``. The string domain is
+ used to differentiate between different programs that store their data
+ in a common message-file library (usually ``/usr/share/locale/``). The
+ ``django`` domain is used for python and template translation strings
+ and is loaded into the global translation catalogs. The ``djangojs``
+ domain is only used for JavaScript translation catalogs to make sure
+ that those are as small as possible.
* Django doesn't use ``xgettext`` alone. It uses Python wrappers around
``xgettext`` and ``msgfmt``. That's mostly for convenience.
+
diff --git a/docs/model-api.txt b/docs/model-api.txt
index 22ff7445b5..4b0bc0d238 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -1367,10 +1367,11 @@ A few special cases to note about ``list_display``:
born_in_fifties.boolean = True
- * The ``__str__()`` method is just as valid in ``list_display`` as any
- other model method, so it's perfectly OK to do this::
+ * The ``__str__()`` and ``__unicode__()`` methods are just as valid in
+ ``list_display`` as any other model method, so it's perfectly OK to do
+ this::
- list_display = ('__str__', 'some_other_field')
+ list_display = ('__unicode__', 'some_other_field')
* Usually, elements of ``list_display`` that aren't actual database fields
can't be used in sorting (because Django does all the sorting at the
@@ -1776,11 +1777,13 @@ A few object methods have special meaning:
-----------
``__str__()`` is a Python "magic method" that defines what should be returned
-if you call ``str()`` on the object. Django uses ``str(obj)`` in a number of
-places, most notably as the value displayed to render an object in the Django
-admin site and as the value inserted into a template when it displays an
-object. Thus, you should always return a nice, human-readable string for the
-object's ``__str__``. Although this isn't required, it's strongly encouraged.
+if you call ``str()`` on the object. Django uses ``str(obj)`` (or the related
+function, ``unicode(obj)`` -- see below) in a number of places, most notably
+as the value displayed to render an object in the Django admin site and as the
+value inserted into a template when it displays an object. Thus, you should
+always return a nice, human-readable string for the object's ``__str__``.
+Although this isn't required, it's strongly encouraged (see the description of
+``__unicode__``, below, before putting ``_str__`` methods everywhere).
For example::
@@ -1789,7 +1792,32 @@ For example::
last_name = models.CharField(maxlength=50)
def __str__(self):
- return '%s %s' % (self.first_name, self.last_name)
+ # Note use of django.utils.encoding.smart_str() here because
+ # first_name and last_name will be unicode strings.
+ return smart_str('%s %s' % (self.first_name, self.last_name))
+
+``__unicode__``
+---------------
+
+The ``__unicode__()`` method is called whenever you call ``unicode()`` on an
+object. Since Django's database backends will return Unicode strings in your
+model's attributes, you would normally want to write a ``__unicode__()``
+method for your model. The example in the previous section could be written
+more simply as::
+
+ class Person(models.Model):
+ first_name = models.CharField(maxlength=50)
+ last_name = models.CharField(maxlength=50)
+
+ def __unicode__(self):
+ return u'%s %s' % (self.first_name, self.last_name)
+
+If you define a ``__unicode__()`` method on your model and not a ``__str__()``
+method, Django will automatically provide you with a ``__str__()`` that calls
+``__unicode()__`` and then converts the result correctly to a UTF-8 encoded
+string object. This is recommended development practice: define only
+``__unicode__()`` and let Django take care of the conversion to string objects
+when required.
``get_absolute_url``
--------------------
@@ -1823,7 +1851,9 @@ But this template code is good::
characters (required by the URI spec, `RFC 2396`_) that have been
URL-encoded, if necessary. Code and templates using ``get_absolute_url()``
should be able to use the result directly without needing to do any
- further processing.
+ further processing. You may wish to use the
+ ``django.utils.encoding.iri_to_uri()`` function to help with this if you
+ are using unicode strings a lot.
.. _RFC 2396: http://www.ietf.org/rfc/rfc2396.txt
diff --git a/docs/newforms.txt b/docs/newforms.txt
index 41db04a7dd..c2e08c63b9 100644
--- a/docs/newforms.txt
+++ b/docs/newforms.txt
@@ -1456,7 +1456,7 @@ Consider this set of models::
title = models.CharField(maxlength=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Book(models.Model):
diff --git a/docs/overview.txt b/docs/overview.txt
index 7b3559663a..041ad152c7 100644
--- a/docs/overview.txt
+++ b/docs/overview.txt
@@ -27,7 +27,7 @@ quick example::
class Reporter(models.Model):
full_name = models.CharField(maxlength=70)
- def __str__(self):
+ def __unicode__(self):
return self.full_name
class Article(models.Model):
@@ -36,7 +36,7 @@ quick example::
article = models.TextField()
reporter = models.ForeignKey(Reporter)
- def __str__(self):
+ def __unicode__(self):
return self.headline
Install it
diff --git a/docs/settings.txt b/docs/settings.txt
index 897cdc8099..9c9602d9ec 100644
--- a/docs/settings.txt
+++ b/docs/settings.txt
@@ -442,6 +442,16 @@ Default: ``False``
Whether to use a TLS (secure) connection when talking to the SMTP server.
+FILE_CHARSET
+------------
+
+**New in Django development version**
+
+Default: ``'utf-8'``
+
+The character encoding used to decode any files read from disk. This includes
+template files and initial SQL data files.
+
FIXTURE_DIRS
-------------
diff --git a/docs/templates.txt b/docs/templates.txt
index c32b1af1dd..f5ed1fe74b 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -1040,6 +1040,16 @@ right-most digit, 2 is the second-right-most digit, etc. Returns the original
value for invalid input (if input or argument is not an integer, or if argument
is less than 1). Otherwise, output is always an integer.
+iriencode
+~~~~~~~~~
+
+Converts an IRI (Internationalized Resource Identifier) to a string that is
+suitable for including in a URL. This is necessary if you're trying to use
+strings containing non-ASCII characters in a URL.
+
+It's safe to use this filter on a string that has already gone through the
+``urlencode`` filter.
+
join
~~~~
diff --git a/docs/tutorial01.txt b/docs/tutorial01.txt
index fdac9c554e..aea3af6922 100644
--- a/docs/tutorial01.txt
+++ b/docs/tutorial01.txt
@@ -474,22 +474,39 @@ Once you're in the shell, explore the database API::
Wait a minute. ``<Poll: Poll object>`` is, utterly, an unhelpful
representation of this object. Let's fix that by editing the polls model (in
-the ``polls/models.py`` file) and adding a ``__str__()`` method to both
+the ``polls/models.py`` file) and adding a ``__unicode__()`` method to both
``Poll`` and ``Choice``::
class Poll(models.Model):
# ...
- def __str__(self):
+ def __unicode__(self):
return self.question
class Choice(models.Model):
# ...
- def __str__(self):
+ def __unicode__(self):
return self.choice
-It's important to add ``__str__()`` methods to your models, not only for your
-own sanity when dealing with the interactive prompt, but also because objects'
-representations are used throughout Django's automatically-generated admin.
+It's important to add ``__unicode__()`` methods to your models, not only for
+your own sanity when dealing with the interactive prompt, but also because
+objects' representations are used throughout Django's automatically-generated
+admin.
+
+.. admonition:: Why ``__unicode__()`` and not ``__str__()``?
+
+ If you're familiar with Python, you might be in the habit of adding
+ ``__str__()`` methods to your classes, not ``__unicode__()`` methods.
+ We use ``__unicode__()`` here because Django models deal with Unicode by
+ default. All data stored in your database is converted to Unicode when it's
+ returned.
+
+ Django models have a default ``__str__()`` method that calls ``__unicode__()``
+ and converts the result to a UTF-8 bytestring. This means that ``unicode(p)``
+ will return a Unicode string, and ``str(p)`` will return a normal string,
+ with characters encoded as UTF-8.
+
+ If all of this is jibberish to you, just remember to add ``__unicode__()``
+ methods to your models. With any luck, things should Just Work for you.
Note these are normal Python methods. Let's add a custom method, just for
demonstration::
@@ -509,7 +526,7 @@ Let's jump back into the Python interactive shell by running
>>> from mysite.polls.models import Poll, Choice
- # Make sure our __str__() addition worked.
+ # Make sure our __unicode__() addition worked.
>>> Poll.objects.all()
[<Poll: What's up?>]
diff --git a/docs/unicode.txt b/docs/unicode.txt
new file mode 100644
index 0000000000..2313c28c98
--- /dev/null
+++ b/docs/unicode.txt
@@ -0,0 +1,363 @@
+======================
+Unicode data in Django
+======================
+
+**New in Django development version**
+
+Django natively supports Unicode data everywhere. Providing your database can
+somehow store the data, you can safely pass around Unicode strings to
+templates, models and the database.
+
+This document tells you what you need to know if you're writing applications
+that use data or templates that are encoded in something other than ASCII.
+
+Creating the database
+=====================
+
+Make sure your database is configured to be able to store arbitrary string
+data. Normally, this means giving it an encoding of UTF-8 or UTF-16. If you use
+a more restrictive encoding -- for example, latin1 (iso8859-1) -- you won't be
+able to store certain characters in the database, and information will be lost.
+
+ * MySQL users, refer to the `MySQL manual`_ (section 10.3.2 for MySQL 5.1) for
+ details on how to set or alter the database character set encoding.
+
+ * PostgreSQL users, refer to the `PostgreSQL manual`_ (section 21.2.2 in
+ PostgreSQL 8) for details on creating databases with the correct encoding.
+
+ * SQLite users, there is nothing you need to do. SQLite always uses UTF-8
+ for internal encoding.
+
+.. _MySQL manual: http://www.mysql.org/doc/refman/5.1/en/charset-database.html
+.. _PostgreSQL manual: http://www.postgresql.org/docs/8.2/static/multibyte.html#AEN24104
+
+All of Django's database backends automatically convert Unicode strings into
+the appropriate encoding for talking to the database. They also automatically
+convert strings retrieved from the database into Python Unicode strings. You
+don't even need to tell Django what encoding your database uses: that is
+handled transparently.
+
+For more, see the section "The database API" below.
+
+General string handling
+=======================
+
+Whenever you use strings with Django -- e.g., in database lookups, template
+rendering or anywhere else -- you have two choices for encoding those strings.
+You can use Unicode strings, or you can use normal strings (sometimes called
+"bytestrings") that are encoded using UTF-8.
+
+.. warning::
+ A bytestring does not carry any information with it about its encoding.
+ For that reason, we have to make an assumption, and Django assumes that all
+ bytestrings are in UTF-8.
+
+ If you pass a string to Django that has been encoded in some other format,
+ things will go wrong in interesting ways. Usually, Django will raise a
+ ``UnicodeDecodeError`` at some point.
+
+If your code only uses ASCII data, it's safe to use your normal strings,
+passing them around at will, because ASCII is a subset of UTF-8.
+
+Don't be fooled into thinking that if your ``DEFAULT_CHARSET`` setting is set
+to something other than ``'utf-8'`` you can use that other encoding in your
+bytestrings! ``DEFAULT_CHARSET`` only applies to the strings generated as
+the result of template rendering (and e-mail). Django will always assume UTF-8
+encoding for internal bytestrings. The reason for this is that the
+``DEFAULT_CHARSET`` setting is not actually under your control (if you are the
+application developer). It's under the control of the person installing and
+using your application -- and if that person chooses a different setting, your
+code must still continue to work. Ergo, it cannot rely on that setting.
+
+In most cases when Django is dealing with strings, it will convert them to
+Unicode strings before doing anything else. So, as a general rule, if you pass
+in a bytestring, be prepared to receive a Unicode string back in the result.
+
+Translated strings
+------------------
+
+Aside from Unicode strings and bytestrings, there's a third type of string-like
+object you may encounter when using Django. The framework's
+internationalization features introduce the concept of a "lazy translation" --
+a string that has been marked as translated but whose actual translation result
+isn't determined until the object is used in a string. This feature is useful
+in cases where the translation locale is unknown until the string is used, even
+though the string might have originally been created when the code was first
+imported.
+
+Normally, you won't have to worry about lazy translations. Just be aware that
+if you examine an object and it claims to be a
+``django.utils.functional.__proxy__`` object, it is a lazy translation.
+Calling ``unicode()`` with the lazy translation as the argument will generate a
+Unicode string in the current locale.
+
+For more details about lazy translation objects, refer to the
+internationalization_ documentation.
+
+.. _internationalization: ../i18n/#lazy-translation
+
+Useful utility functions
+------------------------
+
+Because some string operations come up again and again, Django ships with a few
+useful functions that should make working with Unicode and bytestring objects
+a bit easier.
+
+Conversion functions
+~~~~~~~~~~~~~~~~~~~~
+
+The ``django.utils.encoding`` module contains a few functions that are handy
+for converting back and forth between Unicode and bytestrings.
+
+ * ``smart_unicode(s, encoding='utf-8', errors='strict')`` converts its
+ input to a Unicode string. The ``encoding`` parameter specifies the input
+ encoding. (For example, Django uses this internally when processing form
+ input data, which might not be UTF-8 encoded.) The ``errors`` parameter
+ takes any of the values that are accepted by Python's ``unicode()``
+ function for its error handling.
+
+ If you pass ``smart_unicode()`` an object that has a ``__unicode__``
+ method, it will use that method to do the conversion.
+
+ * ``force_unicode(s, encoding='utf-8', errors='strict')`` is identical to
+ ``smart_unicode()`` in almost all cases. The difference is when the
+ first argument is a `lazy translation`_ instance. While
+ ``smart_unicode()`` preserves lazy translations, ``force_unicode()``
+ forces those objects to a Unicode string (causing the translation to
+ occur). Normally, you'll want to use ``smart_unicode()``. However,
+ ``force_unicode()`` is useful in template tags and filters that
+ absolutely *must* have a string to work with, not just something that can
+ be converted to a string.
+
+ * ``smart_str(s, encoding='utf-8', strings_only=False, errors='strict')``
+ is essentially the opposite of ``smart_unicode()``. It forces the first
+ argument to a bytestring. The ``strings_only`` parameter, if set to True,
+ will result in Python integers, booleans and ``None`` not being
+ converted to a string (they keep their original types). This is slightly
+ different semantics from Python's builtin ``str()`` function, but the
+ difference is needed in a few places within Django's internals.
+
+Normally, you'll only need to use ``smart_unicode()``. Call it as early as
+possible on any input data that might be either Unicode or a bytestring, and
+from then on, you can treat the result as always being Unicode.
+
+URI and IRI handling
+~~~~~~~~~~~~~~~~~~~~
+
+Web frameworks have to deal with URLs (which are a type of URI_). One
+requirement of URLs is that they are encoded using only ASCII characters.
+However, in an international environment, you might need to construct a
+URL from an IRI_ -- very loosely speaking, a URI that can contain Unicode
+characters. Quoting and converting an IRI to URI can be a little tricky, so
+Django provides some assistance.
+
+ * The function ``django.utils.encoding.iri_to_uri()`` implements the
+ conversion from IRI to URI as required by the specification (`RFC
+ 3987`_).
+
+ * The functions ``django.utils.http.urlquote()`` and
+ ``django.utils.http.urlquote_plus()`` are versions of Python's standard
+ ``urllib.quote()`` and ``urllib.quote_plus()`` that work with non-ASCII
+ characters. (The data is converted to UTF-8 prior to encoding.)
+
+These two groups of functions have slightly different purposes, and it's
+important to keep them straight. Normally, you would use ``urlquote()`` on the
+individual portions of the IRI or URI path so that any reserved characters
+such as '&' or '%' are correctly encoded. Then, you apply ``iri_to_uri()`` to
+the full IRI and it converts any non-ASCII characters to the correct encoded
+values.
+
+.. note::
+ Technically, it isn't correct to say that ``iri_to_uri()`` implements the
+ full algorithm in the IRI specification. It doesn't (yet) perform the
+ international domain name encoding portion of the algorithm.
+
+The ``iri_to_uri()`` function will not change ASCII characters that are
+otherwise permitted in a URL. So, for example, the character '%' is not
+further encoded when passed to ``iri_to_uri()``. This means you can pass a
+full URL to this function and it will not mess up the query string or anything
+like that.
+
+An example might clarify things here::
+
+ >>> urlquote(u'Paris & Orléans')
+ u'Paris%20%26%20Orl%C3%A9ans'
+ >>> iri_to_uri(u'/favorites/François/%s' % urlquote(u'Paris & Orléans'))
+ '/favorites/Fran%C3%A7ois/Paris%20%26%20Orl%C3%A9ans'
+
+If you look carefully, you can see that the portion that was generated by
+``urlquote()`` in the second example was not double-quoted when passed to
+``iri_to_uri()``. This is a very important and useful feature. It means that
+you can construct your IRI without worrying about whether it contains
+non-ASCII characters and then, right at the end, call ``iri_to_uri()`` on the
+result.
+
+The ``iri_to_uri()`` function is also idempotent, which means the following is
+always true::
+
+ iri_to_uri(iri_to_uri(some_string)) = iri_to_uri(some_string)
+
+So you can safely call it multiple times on the same IRI without risking
+double-quoting problems.
+
+.. _URI: http://www.ietf.org/rfc/rfc2396.txt
+.. _IRI: http://www.ietf.org/rfc/rfc3987.txt
+.. _RFC 3987: IRI_
+
+Models
+======
+
+Because all strings are returned from the database as Unicode strings, model
+fields that are character based (CharField, TextField, URLField, etc) will
+contain Unicode values when Django retrieves data from the database. This
+is *always* the case, even if the data could fit into an ASCII bytestring.
+
+You can pass in bytestrings when creating a model or populating a field, and
+Django will convert it to Unicode when it needs to.
+
+Choosing between ``__str__()`` and ``__unicode__()``
+----------------------------------------------------
+
+One consequence of using Unicode by default is that you have to take some care
+when printing data from the model.
+
+In particular, rather than giving your model a ``__str__()`` method, we
+recommended you implement a ``__unicode__()`` method. In the ``__unicode__()``
+method, you can quite safely return the values of all your fields without
+having to worry about whether they fit into a bytestring or not. (The way
+Python works, the result of ``__str__()`` is *always* a bytestring, even if you
+accidentally try to return a Unicode object).
+
+You can still create a ``__str__()`` method on your models if you want, of
+course, but you shouldn't need to do this unless you have a good reason.
+Django's ``Model`` base class automatically provides a ``__str__()``
+implementation that calls ``__unicode__()`` and encodes the result into UTF-8.
+This means you'll normally only need to implement a ``__unicode__()`` method
+and let Django handle the coercion to a bytestring when required.
+
+Taking care in ``get_absolute_url()``
+-------------------------------------
+
+URLs can only contain ASCII characters. If you're constructing a URL from
+pieces of data that might be non-ASCII, be careful to encode the results in a
+way that is suitable for a URL. The ``django.db.models.permalink()`` decorator
+handles this for you automatically.
+
+If you're constructing a URL manually (i.e., *not* using the ``permalink()``
+decorator), you'll need to take care of the encoding yourself. In this case,
+use the ``iri_to_uri()`` and ``urlquote()`` functions that were documented
+above_. For example::
+
+ from django.utils.encoding import iri_to_uri
+ from django.utils.http import urlquote
+
+ def get_absolute_url(self):
+ url = u'/person/%s/?x=0&y=0' % urlquote(self.location)
+ return iri_to_uri(url)
+
+This function returns a correctly encoded URL even if ``self.location`` is
+something like "Jack visited Paris & Orléans". (In fact, the ``iri_to_uri()``
+call isn't strictly necessary in the above example, because all the
+non-ASCII characters would have been removed in quoting in the first line.)
+
+.. _above: uri_and_iri_
+
+The database API
+================
+
+You can pass either Unicode strings or UTF-8 bytestrings as arguments to
+``filter()`` methods and the like in the database API. The following two
+querysets are identical::
+
+ qs = People.objects.filter(name__contains=u'Å')
+ qs = People.objects.filter(name__contains='\xc3\85') # UTF-8 encoding of Å
+
+Templates
+=========
+
+You can use either Unicode or bytestrings when creating templates manually::
+
+ from django.template import Template
+ t1 = Template('This is a bytestring template.')
+ t2 = Template(u'This is a Unicode template.')
+
+But the common case is to read templates from the filesystem, and this creates
+a slight complication: not all filesystems store their data encoded as UTF-8.
+If your template files are not stored with a UTF-8 encoding, set the ``FILE_CHARSET``
+setting to the encoding of the files on disk. When Django reads in a template
+file, it will convert the data from this encoding to Unicode. (``FILE_CHARSET``
+is set to ``'utf-8'`` by default.)
+
+The ``DEFAULT_CHARSET`` setting controls the encoding of rendered templates.
+This is set to UTF-8 by default.
+
+Template tags and filters
+-------------------------
+
+A couple of tips to remember when writing your own template tags and filters:
+
+ * Always return Unicode strings from a template tag's ``render()`` method
+ and from template filters.
+
+ * Use ``force_unicode()`` in preference to ``smart_unicode()`` in these
+ places. Tag rendering and filter calls occur as the template is being
+ rendered, so there is no advantage to postponing the conversion of lazy
+ translation objects into strings. It's easier to work solely with Unicode
+ strings at that point.
+
+E-mail
+======
+
+Django's e-mail framework (in ``django.core.mail``) supports Unicode
+transparently. You can use Unicode data in the message bodies and any headers.
+However, you're still obligated to respect the requirements of the e-mail
+specifications, so, for example, e-mail addresses should use only ASCII
+characters.
+
+The following code example demonstrates that everything except e-mail addresses
+can be non-ASCII::
+
+ from django.core.mail import EmailMessage
+
+ subject = u'My visit to Sør-Trøndelag'
+ sender = u'Arnbjörg Ráðormsdóttir <arnbjorg@example.com>'
+ recipients = ['Fred <fred@example.com']
+ body = u'...'
+ EmailMessage(subject, body, sender, recipients).send()
+
+Form submission
+===============
+
+HTML form submission is a tricky area. There's no guarantee that the
+submission will include encoding information, which means the framework might
+have to guess at the encoding of submitted data.
+
+Django adopts a "lazy" approach to decoding form data. The data in an
+``HttpRequest`` object is only decoded when you access it. In fact, most of
+the data is not decoded at all. Only the ``HttpRequest.GET`` and
+``HttpRequest.POST`` data structures have any decoding applied to them. Those
+two fields will return their members as Unicode data. All other attributes and
+methods of ``HttpRequest`` return data exactly as it was submitted by the
+client.
+
+By default, the ``DEFAULT_CHARSET`` setting is used as the assumed encoding
+for form data. If you need to change this for a particular form, you can set
+the ``encoding`` attribute on the ``GET`` and ``POST`` data structures. For
+convenience, changing the ``encoding`` property on an ``HttpRequest`` instance
+does this for you. For example::
+
+ def some_view(request):
+ # We know that the data must be encoded as KOI8-R (for some reason).
+ request.encoding = 'koi8-r'
+ ...
+
+You can even change the encoding after having accessed ``request.GET`` or
+``request.POST``, and all subsequent accesses will use the new encoding.
+
+Most developers won't need to worry about changing form encoding, but this is
+a useful feature for applications that talk to legacy systems whose encoding
+you cannot control.
+
+Django does not decode the data of file uploads, because that data is normally
+treated as collections of bytes, rather than strings. Any automatic decoding
+there would alter the meaning of the stream of bytes.
diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py
index 9af13c0e3e..e4fcaf0312 100644
--- a/tests/modeltests/basic/models.py
+++ b/tests/modeltests/basic/models.py
@@ -1,3 +1,4 @@
+# coding: utf-8
"""
1. Bare-bones model
@@ -13,7 +14,7 @@ class Article(models.Model):
class Meta:
ordering = ('pub_date','headline')
- def __str__(self):
+ def __unicode__(self):
return self.headline
__test__ = {'API_TESTS': """
@@ -351,7 +352,7 @@ __test__['API_TESTS'] += """
>>> a101.save()
>>> a101 = Article.objects.get(pk=101)
>>> a101.headline
-'Article 101'
+u'Article 101'
# You can create saved objects in a single step
>>> a10 = Article.objects.create(headline="Article 10", pub_date=datetime(2005, 7, 31, 12, 30, 45))
diff --git a/tests/modeltests/choices/models.py b/tests/modeltests/choices/models.py
index 37d36fe1d8..cb1bd481cd 100644
--- a/tests/modeltests/choices/models.py
+++ b/tests/modeltests/choices/models.py
@@ -20,7 +20,7 @@ class Person(models.Model):
name = models.CharField(maxlength=20)
gender = models.CharField(maxlength=1, choices=GENDER_CHOICES)
- def __str__(self):
+ def __unicode__(self):
return self.name
__test__ = {'API_TESTS':"""
@@ -33,7 +33,7 @@ __test__ = {'API_TESTS':"""
>>> s.gender
'F'
>>> a.get_gender_display()
-'Male'
+u'Male'
>>> s.get_gender_display()
-'Female'
+u'Female'
"""}
diff --git a/tests/modeltests/custom_columns/models.py b/tests/modeltests/custom_columns/models.py
index 1283da07cf..fb2487802c 100644
--- a/tests/modeltests/custom_columns/models.py
+++ b/tests/modeltests/custom_columns/models.py
@@ -6,11 +6,11 @@ If your database column name is different than your model attribute, use the
name, in API usage.
If your database table name is different than your model name, use the
-``db_table`` Meta attribute. This has no effect on the API used to
+``db_table`` Meta attribute. This has no effect on the API used to
query the database.
-If you need to use a table name for a many-to-many relationship that differs
-from the default generated name, use the ``db_table`` parameter on the
+If you need to use a table name for a many-to-many relationship that differs
+from the default generated name, use the ``db_table`` parameter on the
ManyToMany field. This has no effect on the API for querying the database.
"""
@@ -21,8 +21,8 @@ class Author(models.Model):
first_name = models.CharField(maxlength=30, db_column='firstname')
last_name = models.CharField(maxlength=30, db_column='last')
- def __str__(self):
- return '%s %s' % (self.first_name, self.last_name)
+ def __unicode__(self):
+ return u'%s %s' % (self.first_name, self.last_name)
class Meta:
db_table = 'my_author_table'
@@ -32,12 +32,12 @@ class Article(models.Model):
headline = models.CharField(maxlength=100)
authors = models.ManyToManyField(Author, db_table='my_m2m_table')
- def __str__(self):
+ def __unicode__(self):
return self.headline
class Meta:
ordering = ('headline',)
-
+
__test__ = {'API_TESTS':"""
# Create a Author.
>>> a = Author(first_name='John', last_name='Smith')
@@ -75,9 +75,9 @@ TypeError: Cannot resolve keyword 'firstname' into field. Choices are: article,
>>> a = Author.objects.get(last_name__exact='Smith')
>>> a.first_name
-'John'
+u'John'
>>> a.last_name
-'Smith'
+u'Smith'
>>> a.firstname
Traceback (most recent call last):
...
diff --git a/tests/modeltests/custom_managers/models.py b/tests/modeltests/custom_managers/models.py
index 99df875275..0b9edc88b9 100644
--- a/tests/modeltests/custom_managers/models.py
+++ b/tests/modeltests/custom_managers/models.py
@@ -23,8 +23,8 @@ class Person(models.Model):
fun = models.BooleanField()
objects = PersonManager()
- def __str__(self):
- return "%s %s" % (self.first_name, self.last_name)
+ def __unicode__(self):
+ return u"%s %s" % (self.first_name, self.last_name)
# An example of a custom manager that sets get_query_set().
@@ -39,7 +39,7 @@ class Book(models.Model):
published_objects = PublishedBookManager()
authors = models.ManyToManyField(Person, related_name='books')
- def __str__(self):
+ def __unicode__(self):
return self.title
# An example of providing multiple custom managers.
@@ -55,7 +55,7 @@ class Car(models.Model):
cars = models.Manager()
fast_cars = FastCarManager()
- def __str__(self):
+ def __unicode__(self):
return self.name
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/custom_methods/models.py b/tests/modeltests/custom_methods/models.py
index e8fb751d54..5a6581805a 100644
--- a/tests/modeltests/custom_methods/models.py
+++ b/tests/modeltests/custom_methods/models.py
@@ -11,7 +11,7 @@ class Article(models.Model):
headline = models.CharField(maxlength=100)
pub_date = models.DateField()
- def __str__(self):
+ def __unicode__(self):
return self.headline
def was_published_today(self):
diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py
index fd0901da3c..276fecc371 100644
--- a/tests/modeltests/custom_pk/models.py
+++ b/tests/modeltests/custom_pk/models.py
@@ -15,8 +15,8 @@ class Employee(models.Model):
class Meta:
ordering = ('last_name', 'first_name')
- def __str__(self):
- return "%s %s" % (self.first_name, self.last_name)
+ def __unicode__(self):
+ return u"%s %s" % (self.first_name, self.last_name)
class Business(models.Model):
name = models.CharField(maxlength=20, primary_key=True)
@@ -24,7 +24,7 @@ class Business(models.Model):
class Meta:
verbose_name_plural = 'businesses'
- def __str__(self):
+ def __unicode__(self):
return self.name
__test__ = {'API_TESTS':"""
@@ -62,7 +62,7 @@ DoesNotExist: Employee matching query does not exist.
>>> Employee.objects.filter(last_name__exact='Jones')
[<Employee: Dan Jones>, <Employee: Fran Jones>]
>>> Employee.objects.in_bulk(['ABC123', 'XYZ456'])
-{'XYZ456': <Employee: Fran Jones>, 'ABC123': <Employee: Dan Jones>}
+{u'XYZ456': <Employee: Fran Jones>, u'ABC123': <Employee: Dan Jones>}
>>> b = Business(name='Sears')
>>> b.save()
@@ -72,7 +72,7 @@ DoesNotExist: Employee matching query does not exist.
>>> fran.business_set.all()
[<Business: Sears>]
>>> Business.objects.in_bulk(['Sears'])
-{'Sears': <Business: Sears>}
+{u'Sears': <Business: Sears>}
>>> Business.objects.filter(name__exact='Sears')
[<Business: Sears>]
diff --git a/tests/modeltests/field_defaults/models.py b/tests/modeltests/field_defaults/models.py
index 8e803d00d8..7eafa03798 100644
--- a/tests/modeltests/field_defaults/models.py
+++ b/tests/modeltests/field_defaults/models.py
@@ -16,7 +16,7 @@ class Article(models.Model):
headline = models.CharField(maxlength=100, default='Default headline')
pub_date = models.DateTimeField(default=datetime.now)
- def __str__(self):
+ def __unicode__(self):
return self.headline
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/fixtures/models.py b/tests/modeltests/fixtures/models.py
index c75e6723fd..c713aa723d 100644
--- a/tests/modeltests/fixtures/models.py
+++ b/tests/modeltests/fixtures/models.py
@@ -1,10 +1,10 @@
"""
37. Fixtures.
-Fixtures are a way of loading data into the database in bulk. Fixure data
-can be stored in any serializable format (including JSON and XML). Fixtures
+Fixtures are a way of loading data into the database in bulk. Fixure data
+can be stored in any serializable format (including JSON and XML). Fixtures
are identified by name, and are stored in either a directory named 'fixtures'
-in the application directory, on in one of the directories named in the
+in the application directory, on in one of the directories named in the
FIXTURE_DIRS setting.
"""
@@ -14,17 +14,17 @@ class Article(models.Model):
headline = models.CharField(maxlength=100, default='Default headline')
pub_date = models.DateTimeField()
- def __str__(self):
+ def __unicode__(self):
return self.headline
-
+
class Meta:
ordering = ('-pub_date', 'headline')
-
+
__test__ = {'API_TESTS': """
>>> from django.core import management
>>> from django.db.models import get_app
-# Reset the database representation of this app.
+# Reset the database representation of this app.
# This will return the database to a clean initial state.
>>> management.flush(verbosity=0, interactive=False)
@@ -42,7 +42,7 @@ __test__ = {'API_TESTS': """
>>> Article.objects.all()
[<Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker has no place on ESPN>, <Article: Python program becomes self aware>]
-# Load fixture 3, XML format.
+# Load fixture 3, XML format.
>>> management.load_data(['fixture3.xml'], verbosity=0)
>>> Article.objects.all()
[<Article: XML identified as leading cause of cancer>, <Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker on TV is great!>, <Article: Python program becomes self aware>]
@@ -65,7 +65,7 @@ __test__ = {'API_TESTS': """
[<Article: Time to reform copyright>, <Article: Poker has no place on ESPN>, <Article: Python program becomes self aware>]
# Try to load fixture 2 using format discovery; this will fail
-# because there are two fixture2's in the fixtures directory
+# because there are two fixture2's in the fixtures directory
>>> management.load_data(['fixture2'], verbosity=0) # doctest: +ELLIPSIS
Multiple fixtures named 'fixture2' in '...fixtures'. Aborting.
@@ -81,7 +81,7 @@ from django.test import TestCase
class SampleTestCase(TestCase):
fixtures = ['fixture1.json', 'fixture2.json']
-
+
def testClassFixtures(self):
"Check that test case has installed 4 fixture objects"
self.assertEqual(Article.objects.count(), 4)
diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py
index 195f67db8f..d77a2ee43d 100644
--- a/tests/modeltests/generic_relations/models.py
+++ b/tests/modeltests/generic_relations/models.py
@@ -24,7 +24,7 @@ class TaggedItem(models.Model):
class Meta:
ordering = ["tag"]
- def __str__(self):
+ def __unicode__(self):
return self.tag
class Animal(models.Model):
@@ -33,7 +33,7 @@ class Animal(models.Model):
tags = generic.GenericRelation(TaggedItem)
- def __str__(self):
+ def __unicode__(self):
return self.common_name
class Vegetable(models.Model):
@@ -42,7 +42,7 @@ class Vegetable(models.Model):
tags = generic.GenericRelation(TaggedItem)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Mineral(models.Model):
@@ -51,7 +51,7 @@ class Mineral(models.Model):
# note the lack of an explicit GenericRelation here...
- def __str__(self):
+ def __unicode__(self):
return self.name
__test__ = {'API_TESTS':"""
@@ -111,17 +111,17 @@ __test__ = {'API_TESTS':"""
# objects are deleted when the source object is deleted.
# Original list of tags:
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
-[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('hairy', <ContentType: animal>, 1), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2), ('yellow', <ContentType: animal>, 1)]
+[(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'hairy', <ContentType: animal>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2), (u'yellow', <ContentType: animal>, 1)]
>>> lion.delete()
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
-[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)]
+[(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
# If Generic Relation is not explicitly defined, any related objects
# remain after deletion of the source object.
>>> quartz.delete()
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
-[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)]
+[(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
# If you delete a tag, the objects using the tag are unaffected
# (other than losing a tag)
@@ -130,6 +130,6 @@ __test__ = {'API_TESTS':"""
>>> bacon.tags.all()
[<TaggedItem: salty>]
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
-[('clearish', <ContentType: mineral>, 1), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)]
+[(u'clearish', <ContentType: mineral>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
"""}
diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py
index 84c6273818..e54e5e32b7 100644
--- a/tests/modeltests/get_latest/models.py
+++ b/tests/modeltests/get_latest/models.py
@@ -17,7 +17,7 @@ class Article(models.Model):
class Meta:
get_latest_by = 'pub_date'
- def __str__(self):
+ def __unicode__(self):
return self.headline
class Person(models.Model):
@@ -26,7 +26,7 @@ class Person(models.Model):
# Note that this model doesn't have "get_latest_by" set.
- def __str__(self):
+ def __unicode__(self):
return self.name
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/get_object_or_404/models.py b/tests/modeltests/get_object_or_404/models.py
index 2ad459669f..5f449f4cfe 100644
--- a/tests/modeltests/get_object_or_404/models.py
+++ b/tests/modeltests/get_object_or_404/models.py
@@ -17,7 +17,7 @@ from django.shortcuts import get_object_or_404, get_list_or_404
class Author(models.Model):
name = models.CharField(maxlength=50)
- def __str__(self):
+ def __unicode__(self):
return self.name
class ArticleManager(models.Manager):
@@ -30,7 +30,7 @@ class Article(models.Model):
objects = models.Manager()
by_a_sir = ArticleManager()
- def __str__(self):
+ def __unicode__(self):
return self.title
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/get_or_create/models.py b/tests/modeltests/get_or_create/models.py
index f974a82dee..ed26daa852 100644
--- a/tests/modeltests/get_or_create/models.py
+++ b/tests/modeltests/get_or_create/models.py
@@ -12,8 +12,8 @@ class Person(models.Model):
last_name = models.CharField(maxlength=100)
birthday = models.DateField()
- def __str__(self):
- return '%s %s' % (self.first_name, self.last_name)
+ def __unicode__(self):
+ return u'%s %s' % (self.first_name, self.last_name)
__test__ = {'API_TESTS':"""
# Acting as a divine being, create an Person.
diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py
index 9d3e8ca4b4..207b27a7d9 100644
--- a/tests/modeltests/lookup/models.py
+++ b/tests/modeltests/lookup/models.py
@@ -13,7 +13,7 @@ class Article(models.Model):
class Meta:
ordering = ('-pub_date', 'headline')
- def __str__(self):
+ def __unicode__(self):
return self.headline
__test__ = {'API_TESTS':r"""
@@ -100,7 +100,7 @@ TypeError: in_bulk() got an unexpected keyword argument 'headline__startswith'
# values() returns a list of dictionaries instead of object instances -- and
# you can specify which fields you want to retrieve.
>>> Article.objects.values('headline')
-[{'headline': 'Article 5'}, {'headline': 'Article 6'}, {'headline': 'Article 4'}, {'headline': 'Article 2'}, {'headline': 'Article 3'}, {'headline': 'Article 7'}, {'headline': 'Article 1'}]
+[{'headline': u'Article 5'}, {'headline': u'Article 6'}, {'headline': u'Article 4'}, {'headline': u'Article 2'}, {'headline': u'Article 3'}, {'headline': u'Article 7'}, {'headline': u'Article 1'}]
>>> Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).values('id')
[{'id': 2}, {'id': 3}, {'id': 7}]
>>> list(Article.objects.values('id', 'headline')) == [{'id': 5, 'headline': 'Article 5'}, {'id': 6, 'headline': 'Article 6'}, {'id': 4, 'headline': 'Article 4'}, {'id': 2, 'headline': 'Article 2'}, {'id': 3, 'headline': 'Article 3'}, {'id': 7, 'headline': 'Article 7'}, {'id': 1, 'headline': 'Article 1'}]
@@ -110,13 +110,13 @@ True
... i = d.items()
... i.sort()
... i
-[('headline', 'Article 5'), ('id', 5)]
-[('headline', 'Article 6'), ('id', 6)]
-[('headline', 'Article 4'), ('id', 4)]
-[('headline', 'Article 2'), ('id', 2)]
-[('headline', 'Article 3'), ('id', 3)]
-[('headline', 'Article 7'), ('id', 7)]
-[('headline', 'Article 1'), ('id', 1)]
+[('headline', u'Article 5'), ('id', 5)]
+[('headline', u'Article 6'), ('id', 6)]
+[('headline', u'Article 4'), ('id', 4)]
+[('headline', u'Article 2'), ('id', 2)]
+[('headline', u'Article 3'), ('id', 3)]
+[('headline', u'Article 7'), ('id', 7)]
+[('headline', u'Article 1'), ('id', 1)]
# You can use values() with iterator() for memory savings, because iterator()
# uses database-level iteration.
@@ -124,13 +124,13 @@ True
... i = d.items()
... i.sort()
... i
-[('headline', 'Article 5'), ('id', 5)]
-[('headline', 'Article 6'), ('id', 6)]
-[('headline', 'Article 4'), ('id', 4)]
-[('headline', 'Article 2'), ('id', 2)]
-[('headline', 'Article 3'), ('id', 3)]
-[('headline', 'Article 7'), ('id', 7)]
-[('headline', 'Article 1'), ('id', 1)]
+[('headline', u'Article 5'), ('id', 5)]
+[('headline', u'Article 6'), ('id', 6)]
+[('headline', u'Article 4'), ('id', 4)]
+[('headline', u'Article 2'), ('id', 2)]
+[('headline', u'Article 3'), ('id', 3)]
+[('headline', u'Article 7'), ('id', 7)]
+[('headline', u'Article 1'), ('id', 1)]
# you can use values() even on extra fields
diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/modeltests/m2m_and_m2o/models.py
index 09affb002f..b5adc63b9d 100644
--- a/tests/modeltests/m2m_and_m2o/models.py
+++ b/tests/modeltests/m2m_and_m2o/models.py
@@ -14,8 +14,8 @@ class Issue(models.Model):
cc = models.ManyToManyField(User, blank=True, related_name='test_issue_cc')
client = models.ForeignKey(User, related_name='test_issue_client')
- def __str__(self):
- return str(self.num)
+ def __unicode__(self):
+ return unicode(self.num)
class Meta:
ordering = ('num',)
diff --git a/tests/modeltests/m2m_intermediary/models.py b/tests/modeltests/m2m_intermediary/models.py
index b917db6189..5e56f44872 100644
--- a/tests/modeltests/m2m_intermediary/models.py
+++ b/tests/modeltests/m2m_intermediary/models.py
@@ -16,14 +16,14 @@ class Reporter(models.Model):
first_name = models.CharField(maxlength=30)
last_name = models.CharField(maxlength=30)
- def __str__(self):
- return "%s %s" % (self.first_name, self.last_name)
+ def __unicode__(self):
+ return u"%s %s" % (self.first_name, self.last_name)
class Article(models.Model):
headline = models.CharField(maxlength=100)
pub_date = models.DateField()
- def __str__(self):
+ def __unicode__(self):
return self.headline
class Writer(models.Model):
@@ -31,8 +31,8 @@ class Writer(models.Model):
article = models.ForeignKey(Article)
position = models.CharField(maxlength=100)
- def __str__(self):
- return '%s (%s)' % (self.reporter, self.position)
+ def __unicode__(self):
+ return u'%s (%s)' % (self.reporter, self.position)
__test__ = {'API_TESTS':"""
# Create a few Reporters.
diff --git a/tests/modeltests/m2m_multiple/models.py b/tests/modeltests/m2m_multiple/models.py
index 5a1aa122a9..78c35aafc2 100644
--- a/tests/modeltests/m2m_multiple/models.py
+++ b/tests/modeltests/m2m_multiple/models.py
@@ -14,7 +14,7 @@ class Category(models.Model):
class Meta:
ordering = ('name',)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Article(models.Model):
@@ -25,7 +25,7 @@ class Article(models.Model):
class Meta:
ordering = ('pub_date',)
- def __str__(self):
+ def __unicode__(self):
return self.headline
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/m2m_recursive/models.py b/tests/modeltests/m2m_recursive/models.py
index 15c713a759..28d98f0600 100644
--- a/tests/modeltests/m2m_recursive/models.py
+++ b/tests/modeltests/m2m_recursive/models.py
@@ -19,7 +19,7 @@ class Person(models.Model):
friends = models.ManyToManyField('self')
idols = models.ManyToManyField('self', symmetrical=False, related_name='stalkers')
- def __str__(self):
+ def __unicode__(self):
return self.name
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/m2o_recursive/models.py b/tests/modeltests/m2o_recursive/models.py
index 0b528faf9e..7421061489 100644
--- a/tests/modeltests/m2o_recursive/models.py
+++ b/tests/modeltests/m2o_recursive/models.py
@@ -16,7 +16,7 @@ class Category(models.Model):
name = models.CharField(maxlength=20)
parent = models.ForeignKey('self', null=True, related_name='child_set')
- def __str__(self):
+ def __unicode__(self):
return self.name
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/m2o_recursive2/models.py b/tests/modeltests/m2o_recursive2/models.py
index 5b7c5447ad..a8460d6149 100644
--- a/tests/modeltests/m2o_recursive2/models.py
+++ b/tests/modeltests/m2o_recursive2/models.py
@@ -14,7 +14,7 @@ class Person(models.Model):
mother = models.ForeignKey('self', null=True, related_name='mothers_child_set')
father = models.ForeignKey('self', null=True, related_name='fathers_child_set')
- def __str__(self):
+ def __unicode__(self):
return self.full_name
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py
index 1a44cfe7f4..7f1ff0e47b 100644
--- a/tests/modeltests/manipulators/models.py
+++ b/tests/modeltests/manipulators/models.py
@@ -10,15 +10,15 @@ class Musician(models.Model):
first_name = models.CharField(maxlength=30)
last_name = models.CharField(maxlength=30)
- def __str__(self):
- return "%s %s" % (self.first_name, self.last_name)
+ def __unicode__(self):
+ return u"%s %s" % (self.first_name, self.last_name)
class Album(models.Model):
name = models.CharField(maxlength=100)
musician = models.ForeignKey(Musician)
release_date = models.DateField(blank=True, null=True)
- def __str__(self):
+ def __unicode__(self):
return self.name
__test__ = {'API_TESTS':"""
@@ -41,24 +41,24 @@ True
# Attempt to add a Musician without a first_name.
>>> man.get_validation_errors(MultiValueDict({'last_name': ['Blakey']}))
-{'first_name': ['This field is required.']}
+{'first_name': [u'This field is required.']}
# Attempt to add a Musician without a first_name and last_name.
>>> man.get_validation_errors(MultiValueDict({}))
-{'first_name': ['This field is required.'], 'last_name': ['This field is required.']}
+{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.']}
# Attempt to create an Album without a name or musician.
>>> man = Album.AddManipulator()
>>> man.get_validation_errors(MultiValueDict({}))
-{'musician': ['This field is required.'], 'name': ['This field is required.']}
+{'musician': [u'This field is required.'], 'name': [u'This field is required.']}
# Attempt to create an Album with an invalid musician.
>>> man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['foo']}))
-{'musician': ["Select a valid choice; 'foo' is not in ['', '1']."]}
+{'musician': [u"Select a valid choice; 'foo' is not in ['', '1']."]}
# Attempt to create an Album with an invalid release_date.
>>> man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['1'], 'release_date': 'today'}))
-{'release_date': ['Enter a valid date in YYYY-MM-DD format.']}
+{'release_date': [u'Enter a valid date in YYYY-MM-DD format.']}
# Create an Album without a release_date (because it's optional).
>>> data = MultiValueDict({'name': ['Ella and Basie'], 'musician': ['1']})
diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py
index 5e46ad428d..446a39b472 100644
--- a/tests/modeltests/many_to_many/models.py
+++ b/tests/modeltests/many_to_many/models.py
@@ -12,7 +12,7 @@ from django.db import models
class Publication(models.Model):
title = models.CharField(maxlength=30)
- def __str__(self):
+ def __unicode__(self):
return self.title
class Meta:
@@ -22,7 +22,7 @@ class Article(models.Model):
headline = models.CharField(maxlength=100)
publications = models.ManyToManyField(Publication)
- def __str__(self):
+ def __unicode__(self):
return self.headline
class Meta:
diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py
index 02f7bf1066..1356b1924b 100644
--- a/tests/modeltests/many_to_one/models.py
+++ b/tests/modeltests/many_to_one/models.py
@@ -11,15 +11,15 @@ class Reporter(models.Model):
last_name = models.CharField(maxlength=30)
email = models.EmailField()
- def __str__(self):
- return "%s %s" % (self.first_name, self.last_name)
+ def __unicode__(self):
+ return u"%s %s" % (self.first_name, self.last_name)
class Article(models.Model):
headline = models.CharField(maxlength=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter)
- def __str__(self):
+ def __unicode__(self):
return self.headline
class Meta:
@@ -47,7 +47,7 @@ __test__ = {'API_TESTS':"""
# Article objects have access to their related Reporter objects.
>>> r = a.reporter
>>> r.first_name, r.last_name
-('John', 'Smith')
+(u'John', u'Smith')
# Create an Article via the Reporter object.
>>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29))
@@ -154,8 +154,13 @@ False
>>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_article__reporter.last_name='Smith'"])
[<Article: John's second story>, <Article: This is a test>]
+# And should work fine with the unicode that comes out of
+# newforms.Form.cleaned_data
+>>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_article__reporter.last_name='%s'" % u'Smith'])
+[<Article: John's second story>, <Article: This is a test>]
+
# Find all Articles for the Reporter whose ID is 1.
-# Use direct ID check, pk check, and object comparison
+# Use direct ID check, pk check, and object comparison
>>> Article.objects.filter(reporter__id__exact=1)
[<Article: John's second story>, <Article: This is a test>]
>>> Article.objects.filter(reporter__pk=1)
diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py
index fb0f6ac3b7..1469dbb5ca 100644
--- a/tests/modeltests/many_to_one_null/models.py
+++ b/tests/modeltests/many_to_one_null/models.py
@@ -10,7 +10,7 @@ from django.db import models
class Reporter(models.Model):
name = models.CharField(maxlength=30)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Article(models.Model):
@@ -20,7 +20,7 @@ class Article(models.Model):
class Meta:
ordering = ('headline',)
- def __str__(self):
+ def __unicode__(self):
return self.headline
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
index 6ffd4d1bce..a21bef02ce 100644
--- a/tests/modeltests/model_forms/models.py
+++ b/tests/modeltests/model_forms/models.py
@@ -34,13 +34,13 @@ class Category(models.Model):
name = models.CharField(maxlength=20)
url = models.CharField('The URL', maxlength=40)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Writer(models.Model):
name = models.CharField(maxlength=50, help_text='Use both first and last names.')
- def __str__(self):
+ def __unicode__(self):
return self.name
class Article(models.Model):
@@ -58,14 +58,14 @@ class Article(models.Model):
self.created = datetime.date.today()
return super(Article, self).save()
- def __str__(self):
+ def __unicode__(self):
return self.headline
class PhoneNumber(models.Model):
phone = models.PhoneNumberField()
description = models.CharField(maxlength=20)
- def __str__(self):
+ def __unicode__(self):
return self.phone
__test__ = {'API_TESTS': """
@@ -244,10 +244,10 @@ True
1
>>> test_art = Article.objects.get(id=1)
>>> test_art.headline
-'Test headline'
+u'Test headline'
-You can create a form over a subset of the available fields
-by specifying a 'fields' argument to form_for_instance.
+You can create a form over a subset of the available fields
+by specifying a 'fields' argument to form_for_instance.
>>> PartialArticleForm = form_for_instance(art, fields=('headline','pub_date'))
>>> f = PartialArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04'}, auto_id=False)
>>> print f.as_ul()
@@ -260,7 +260,7 @@ True
1
>>> new_art = Article.objects.get(id=1)
>>> new_art.headline
-'New headline'
+u'New headline'
Add some categories and test the many-to-many form output.
>>> new_art.categories.all()
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
index babef73e0a..8fbd518928 100644
--- a/tests/modeltests/model_inheritance/models.py
+++ b/tests/modeltests/model_inheritance/models.py
@@ -10,21 +10,21 @@ class Place(models.Model):
name = models.CharField(maxlength=50)
address = models.CharField(maxlength=80)
- def __str__(self):
- return "%s the place" % self.name
+ def __unicode__(self):
+ return u"%s the place" % self.name
class Restaurant(Place):
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
- def __str__(self):
- return "%s the restaurant" % self.name
+ def __unicode__(self):
+ return u"%s the restaurant" % self.name
class ItalianRestaurant(Restaurant):
serves_gnocchi = models.BooleanField()
- def __str__(self):
- return "%s the italian restaurant" % self.name
+ def __unicode__(self):
+ return u"%s the italian restaurant" % self.name
__test__ = {'API_TESTS':"""
# Make sure Restaurant has the right fields in the right order.
diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py
index 7488204ff1..99228d21a8 100644
--- a/tests/modeltests/one_to_one/models.py
+++ b/tests/modeltests/one_to_one/models.py
@@ -12,23 +12,23 @@ class Place(models.Model):
name = models.CharField(maxlength=50)
address = models.CharField(maxlength=80)
- def __str__(self):
- return "%s the place" % self.name
+ def __unicode__(self):
+ return u"%s the place" % self.name
class Restaurant(models.Model):
place = models.OneToOneField(Place)
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
- def __str__(self):
- return "%s the restaurant" % self.place.name
+ def __unicode__(self):
+ return u"%s the restaurant" % self.place.name
class Waiter(models.Model):
restaurant = models.ForeignKey(Restaurant)
name = models.CharField(maxlength=50)
- def __str__(self):
- return "%s the waiter at %s" % (self.name, self.restaurant)
+ def __unicode__(self):
+ return u"%s the waiter at %s" % (self.name, self.restaurant)
class ManualPrimaryKey(models.Model):
primary_key = models.CharField(maxlength=10, primary_key=True)
diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py
index 9f926a7373..142df16081 100644
--- a/tests/modeltests/or_lookups/models.py
+++ b/tests/modeltests/or_lookups/models.py
@@ -20,7 +20,7 @@ class Article(models.Model):
class Meta:
ordering = ('pub_date',)
- def __str__(self):
+ def __unicode__(self):
return self.headline
__test__ = {'API_TESTS':"""
@@ -100,7 +100,7 @@ __test__ = {'API_TESTS':"""
3
>>> list(Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')).values())
-[{'headline': 'Hello and goodbye', 'pub_date': datetime.datetime(2005, 11, 29, 0, 0), 'id': 3}]
+[{'headline': u'Hello and goodbye', 'pub_date': datetime.datetime(2005, 11, 29, 0, 0), 'id': 3}]
>>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2])
{1: <Article: Hello>}
diff --git a/tests/modeltests/ordering/models.py b/tests/modeltests/ordering/models.py
index 110ae3d7fc..1c4e08c11c 100644
--- a/tests/modeltests/ordering/models.py
+++ b/tests/modeltests/ordering/models.py
@@ -21,7 +21,7 @@ class Article(models.Model):
class Meta:
ordering = ('-pub_date', 'headline')
- def __str__(self):
+ def __unicode__(self):
return self.headline
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py
index 94deb885f5..2834d293e4 100644
--- a/tests/modeltests/pagination/models.py
+++ b/tests/modeltests/pagination/models.py
@@ -12,7 +12,7 @@ class Article(models.Model):
headline = models.CharField(maxlength=100, default='Default headline')
pub_date = models.DateTimeField()
- def __str__(self):
+ def __unicode__(self):
return self.headline
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/reserved_names/models.py b/tests/modeltests/reserved_names/models.py
index affe3f649d..acec978d86 100644
--- a/tests/modeltests/reserved_names/models.py
+++ b/tests/modeltests/reserved_names/models.py
@@ -21,7 +21,7 @@ class Thing(models.Model):
class Meta:
db_table = 'select'
- def __str__(self):
+ def __unicode__(self):
return self.when
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py
index 4d6591551a..957c1dfb96 100644
--- a/tests/modeltests/reverse_lookup/models.py
+++ b/tests/modeltests/reverse_lookup/models.py
@@ -9,14 +9,14 @@ from django.db import models
class User(models.Model):
name = models.CharField(maxlength=200)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Poll(models.Model):
question = models.CharField(maxlength=200)
creator = models.ForeignKey(User)
- def __str__(self):
+ def __unicode__(self):
return self.question
class Choice(models.Model):
@@ -24,7 +24,7 @@ class Choice(models.Model):
poll = models.ForeignKey(Poll, related_name="poll_choice")
related_poll = models.ForeignKey(Poll, related_name="related_choice")
- def __str(self):
+ def __unicode__(self):
return self.name
__test__ = {'API_TESTS':"""
diff --git a/tests/modeltests/save_delete_hooks/models.py b/tests/modeltests/save_delete_hooks/models.py
index 6e24c308ba..292cfd8e9e 100644
--- a/tests/modeltests/save_delete_hooks/models.py
+++ b/tests/modeltests/save_delete_hooks/models.py
@@ -11,8 +11,8 @@ class Person(models.Model):
first_name = models.CharField(maxlength=20)
last_name = models.CharField(maxlength=20)
- def __str__(self):
- return "%s %s" % (self.first_name, self.last_name)
+ def __unicode__(self):
+ return u"%s %s" % (self.first_name, self.last_name)
def save(self):
print "Before save"
diff --git a/tests/modeltests/select_related/models.py b/tests/modeltests/select_related/models.py
index cd34bd1d84..235712ef27 100644
--- a/tests/modeltests/select_related/models.py
+++ b/tests/modeltests/select_related/models.py
@@ -13,49 +13,49 @@ from django.db import models
class Domain(models.Model):
name = models.CharField(maxlength=50)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Kingdom(models.Model):
name = models.CharField(maxlength=50)
domain = models.ForeignKey(Domain)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Phylum(models.Model):
name = models.CharField(maxlength=50)
kingdom = models.ForeignKey(Kingdom)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Klass(models.Model):
name = models.CharField(maxlength=50)
phylum = models.ForeignKey(Phylum)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Order(models.Model):
name = models.CharField(maxlength=50)
klass = models.ForeignKey(Klass)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Family(models.Model):
name = models.CharField(maxlength=50)
order = models.ForeignKey(Order)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Genus(models.Model):
name = models.CharField(maxlength=50)
family = models.ForeignKey(Family)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Species(models.Model):
name = models.CharField(maxlength=50)
genus = models.ForeignKey(Genus)
- def __str__(self):
+ def __unicode__(self):
return self.name
def create_tree(stringtree):
diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py
index 8d44d5eae7..b7543f9348 100644
--- a/tests/modeltests/serializers/models.py
+++ b/tests/modeltests/serializers/models.py
@@ -13,7 +13,7 @@ class Category(models.Model):
class Meta:
ordering = ('name',)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Author(models.Model):
@@ -22,7 +22,7 @@ class Author(models.Model):
class Meta:
ordering = ('name',)
- def __str__(self):
+ def __unicode__(self):
return self.name
class Article(models.Model):
@@ -34,15 +34,15 @@ class Article(models.Model):
class Meta:
ordering = ('pub_date',)
- def __str__(self):
+ def __unicode__(self):
return self.headline
class AuthorProfile(models.Model):
author = models.OneToOneField(Author)
date_of_birth = models.DateField()
- def __str__(self):
- return "Profile of %s" % self.author
+ def __unicode__(self):
+ return u"Profile of %s" % self.author
__test__ = {'API_TESTS':"""
# Create some data:
diff --git a/tests/modeltests/str/models.py b/tests/modeltests/str/models.py
index 81230d538c..b4d3adcd6d 100644
--- a/tests/modeltests/str/models.py
+++ b/tests/modeltests/str/models.py
@@ -1,23 +1,39 @@
+# -*- coding: utf-8 -*-
"""
-2. Adding __str__() to models
+2. Adding __str__() or __unicode__() to models
-Although it's not a strict requirement, each model should have a ``__str__()``
-method to return a "human-readable" representation of the object. Do this not
-only for your own sanity when dealing with the interactive prompt, but also
-because objects' representations are used throughout Django's
-automatically-generated admin.
+Although it's not a strict requirement, each model should have a
+``_str__()`` or ``__unicode__()`` method to return a "human-readable"
+representation of the object. Do this not only for your own sanity when dealing
+with the interactive prompt, but also because objects' representations are used
+throughout Django's automatically-generated admin.
+
+Normally, you should write ``__unicode__``() method, since this will work for
+all field types (and Django will automatically provide an appropriate
+``__str__()`` method). However, you can write a ``__str__()`` method directly,
+if you prefer. You must be careful to encode the results correctly, though.
"""
from django.db import models
+from django.utils.encoding import smart_str
class Article(models.Model):
headline = models.CharField(maxlength=100)
pub_date = models.DateTimeField()
def __str__(self):
+ # Caution: this is only safe if you are certain that headline will be
+ # in ASCII.
+ return self.headline
+
+class InternationalArticle(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateTimeField()
+
+ def __unicode__(self):
return self.headline
-__test__ = {'API_TESTS':"""
+__test__ = {'API_TESTS':ur"""
# Create an Article.
>>> from datetime import datetime
>>> a = Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
@@ -28,4 +44,10 @@ __test__ = {'API_TESTS':"""
>>> a
<Article: Area man programs in Python>
+
+>>> a1 = InternationalArticle(headline=u'Girl wins €12.500 in lottery', pub_date=datetime(2005, 7, 28))
+
+# The default str() output will be the UTF-8 encoded output of __unicode__().
+>>> str(a1)
+'Girl wins \xe2\x82\xac12.500 in lottery'
"""}
diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py
index 5ebf29678c..06e574590f 100644
--- a/tests/modeltests/test_client/models.py
+++ b/tests/modeltests/test_client/models.py
@@ -1,3 +1,4 @@
+# coding: utf-8
"""
38. Testing using the Test Client
@@ -27,11 +28,14 @@ class ClientTest(TestCase):
def test_get_view(self):
"GET a view"
- response = self.client.get('/test_client/get_view/')
+ # The data is ignored, but let's check it doesn't crash the system
+ # anyway.
+ data = {'var': u'\xf2'}
+ response = self.client.get('/test_client/get_view/', data)
# Check some response details
self.assertContains(response, 'This is a test')
- self.assertEqual(response.context['var'], 42)
+ self.assertEqual(response.context['var'], u'\xf2')
self.assertEqual(response.template.name, 'GET Template')
def test_get_post_view(self):
@@ -281,4 +285,4 @@ class ClientTest(TestCase):
self.assertEqual(mail.outbox[1].from_email, 'from@example.com')
self.assertEqual(mail.outbox[1].to[0], 'second@example.com')
self.assertEqual(mail.outbox[1].to[1], 'third@example.com')
- \ No newline at end of file
+
diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py
index 9bdf213b35..21f577d758 100644
--- a/tests/modeltests/test_client/views.py
+++ b/tests/modeltests/test_client/views.py
@@ -10,7 +10,7 @@ from django.shortcuts import render_to_response
def get_view(request):
"A simple view that expects a GET request, and returns a rendered template"
t = Template('This is a test. {{ var }} is the value.', name='GET Template')
- c = Context({'var': 42})
+ c = Context({'var': request.GET.get('var', 42)})
return HttpResponse(t.render(c))
diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py
index e1fad8063e..d7eba6e4d3 100644
--- a/tests/modeltests/transactions/models.py
+++ b/tests/modeltests/transactions/models.py
@@ -14,8 +14,8 @@ class Reporter(models.Model):
last_name = models.CharField(maxlength=30)
email = models.EmailField()
- def __str__(self):
- return "%s %s" % (self.first_name, self.last_name)
+ def __unicode__(self):
+ return u"%s %s" % (self.first_name, self.last_name)
__test__ = {'API_TESTS':"""
>>> from django.db import connection, transaction
@@ -96,4 +96,4 @@ Exception: I meant to do that
Traceback (most recent call last):
...
TransactionManagementError: Transaction managed block ended with pending COMMIT/ROLLBACK
-""" \ No newline at end of file
+"""
diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
index b31f981aac..5be2f867a4 100644
--- a/tests/modeltests/validation/models.py
+++ b/tests/modeltests/validation/models.py
@@ -17,7 +17,7 @@ class Person(models.Model):
favorite_moment = models.DateTimeField()
email = models.EmailField()
- def __str__(self):
+ def __unicode__(self):
return self.name
__test__ = {'API_TESTS':"""
@@ -42,7 +42,7 @@ __test__ = {'API_TESTS':"""
>>> p = Person(**dict(valid_params, id='foo'))
>>> p.validate()
-{'id': ['This value must be an integer.']}
+{'id': [u'This value must be an integer.']}
>>> p = Person(**dict(valid_params, id=None))
>>> p.validate()
@@ -76,7 +76,7 @@ False
>>> p = Person(**dict(valid_params, is_child='foo'))
>>> p.validate()
-{'is_child': ['This value must be either True or False.']}
+{'is_child': [u'This value must be either True or False.']}
>>> p = Person(**dict(valid_params, name=u'Jose'))
>>> p.validate()
@@ -88,7 +88,7 @@ u'Jose'
>>> p.validate()
{}
>>> p.name
-'227'
+u'227'
>>> p = Person(**dict(valid_params, birthdate=datetime.date(2000, 5, 3)))
>>> p.validate()
@@ -116,7 +116,7 @@ datetime.date(2000, 5, 3)
>>> p = Person(**dict(valid_params, birthdate='foo'))
>>> p.validate()
-{'birthdate': ['Enter a valid date in YYYY-MM-DD format.']}
+{'birthdate': [u'Enter a valid date in YYYY-MM-DD format.']}
>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3, 13, 23)))
>>> p.validate()
@@ -144,10 +144,10 @@ u'john@example.com'
>>> p = Person(**dict(valid_params, email=22))
>>> p.validate()
-{'email': ['Enter a valid e-mail address.']}
+{'email': [u'Enter a valid e-mail address.']}
# Make sure that Date and DateTime return validation errors and don't raise Python errors.
>>> Person(name='John Doe', is_child=True, email='abc@def.com').validate()
-{'favorite_moment': ['This field is required.'], 'birthdate': ['This field is required.']}
+{'favorite_moment': [u'This field is required.'], 'birthdate': [u'This field is required.']}
"""}
diff --git a/tests/regressiontests/dateformat/tests.py b/tests/regressiontests/dateformat/tests.py
index f9f84145c5..30c9a4e6dd 100644
--- a/tests/regressiontests/dateformat/tests.py
+++ b/tests/regressiontests/dateformat/tests.py
@@ -1,54 +1,54 @@
r"""
>>> format(my_birthday, '')
-''
+u''
>>> format(my_birthday, 'a')
-'p.m.'
+u'p.m.'
>>> format(my_birthday, 'A')
-'PM'
+u'PM'
>>> format(my_birthday, 'd')
-'08'
+u'08'
>>> format(my_birthday, 'j')
-'8'
+u'8'
>>> format(my_birthday, 'l')
-'Sunday'
+u'Sunday'
>>> format(my_birthday, 'L')
-'False'
+u'False'
>>> format(my_birthday, 'm')
-'07'
+u'07'
>>> format(my_birthday, 'M')
-'Jul'
+u'Jul'
>>> format(my_birthday, 'b')
-'jul'
+u'jul'
>>> format(my_birthday, 'n')
-'7'
+u'7'
>>> format(my_birthday, 'N')
-'July'
+u'July'
>>> no_tz or format(my_birthday, 'O') == '+0100'
True
>>> format(my_birthday, 'P')
-'10 p.m.'
+u'10 p.m.'
>>> no_tz or format(my_birthday, 'r') == 'Sun, 8 Jul 1979 22:00:00 +0100'
True
>>> format(my_birthday, 's')
-'00'
+u'00'
>>> format(my_birthday, 'S')
-'th'
+u'th'
>>> format(my_birthday, 't')
-'31'
+u'31'
>>> no_tz or format(my_birthday, 'T') == 'CET'
True
>>> no_tz or format(my_birthday, 'U') == '300531600'
True
>>> format(my_birthday, 'w')
-'0'
+u'0'
>>> format(my_birthday, 'W')
-'27'
+u'27'
>>> format(my_birthday, 'y')
-'79'
+u'79'
>>> format(my_birthday, 'Y')
-'1979'
+u'1979'
>>> format(my_birthday, 'z')
-'189'
+u'189'
>>> no_tz or format(my_birthday, 'Z') == '3600'
True
@@ -62,10 +62,10 @@ True
True
>>> format(my_birthday, r'Y z \C\E\T')
-'1979 189 CET'
+u'1979 189 CET'
>>> format(my_birthday, r'jS o\f F')
-'8th of July'
+u'8th of July'
"""
from django.utils import dateformat, translation
diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
index b35636aba6..a1efae66f6 100644
--- a/tests/regressiontests/defaultfilters/tests.py
+++ b/tests/regressiontests/defaultfilters/tests.py
@@ -2,197 +2,211 @@
r"""
>>> floatformat(7.7)
-'7.7'
+u'7.7'
>>> floatformat(7.0)
-'7'
+u'7'
>>> floatformat(0.7)
-'0.7'
+u'0.7'
>>> floatformat(0.07)
-'0.1'
+u'0.1'
>>> floatformat(0.007)
-'0.0'
+u'0.0'
>>> floatformat(0.0)
-'0'
+u'0'
>>> floatformat(7.7,3)
-'7.700'
+u'7.700'
>>> floatformat(6.000000,3)
-'6.000'
+u'6.000'
>>> floatformat(13.1031,-3)
-'13.103'
+u'13.103'
>>> floatformat(11.1197, -2)
-'11.12'
+u'11.12'
>>> floatformat(11.0000, -2)
-'11'
+u'11'
>>> floatformat(11.000001, -2)
-'11.00'
+u'11.00'
>>> floatformat(8.2798, 3)
-'8.280'
->>> floatformat('foo')
-''
->>> floatformat(13.1031, 'bar')
-'13.1031'
->>> floatformat('foo', 'bar')
-''
+u'8.280'
+>>> floatformat(u'foo')
+u''
+>>> floatformat(13.1031, u'bar')
+u'13.1031'
+>>> floatformat(u'foo', u'bar')
+u''
->>> addslashes('"double quotes" and \'single quotes\'')
-'\\"double quotes\\" and \\\'single quotes\\\''
+>>> addslashes(u'"double quotes" and \'single quotes\'')
+u'\\"double quotes\\" and \\\'single quotes\\\''
->>> addslashes(r'\ : backslashes, too')
-'\\\\ : backslashes, too'
+>>> addslashes(ur'\ : backslashes, too')
+u'\\\\ : backslashes, too'
->>> capfirst('hello world')
-'Hello world'
+>>> capfirst(u'hello world')
+u'Hello world'
->>> fix_ampersands('Jack & Jill & Jeroboam')
-'Jack &amp; Jill &amp; Jeroboam'
+>>> fix_ampersands(u'Jack & Jill & Jeroboam')
+u'Jack &amp; Jill &amp; Jeroboam'
->>> linenumbers('line 1\nline 2')
-'1. line 1\n2. line 2'
+>>> linenumbers(u'line 1\nline 2')
+u'1. line 1\n2. line 2'
->>> linenumbers('\n'.join(['x'] * 10))
-'01. x\n02. x\n03. x\n04. x\n05. x\n06. x\n07. x\n08. x\n09. x\n10. x'
+>>> linenumbers(u'\n'.join([u'x'] * 10))
+u'01. x\n02. x\n03. x\n04. x\n05. x\n06. x\n07. x\n08. x\n09. x\n10. x'
>>> lower('TEST')
-'test'
+u'test'
>>> lower(u'\xcb') # uppercase E umlaut
u'\xeb'
>>> make_list('abc')
-['a', 'b', 'c']
+[u'a', u'b', u'c']
>>> make_list(1234)
-['1', '2', '3', '4']
+[u'1', u'2', u'3', u'4']
>>> slugify(' Jack & Jill like numbers 1,2,3 and 4 and silly characters ?%.$!/')
-'jack-jill-like-numbers-123-and-4-and-silly-characters'
+u'jack-jill-like-numbers-123-and-4-and-silly-characters'
->>> stringformat(1, '03d')
-'001'
+>>> slugify(u"Un \xe9l\xe9phant \xe0 l'or\xe9e du bois")
+u'un-elephant-a-loree-du-bois'
->>> stringformat(1, 'z')
-''
+>>> stringformat(1, u'03d')
+u'001'
+
+>>> stringformat(1, u'z')
+u''
>>> title('a nice title, isn\'t it?')
-"A Nice Title, Isn't It?"
+u"A Nice Title, Isn't It?"
+
+>>> title(u'discoth\xe8que')
+u'Discoth\xe8que'
+>>> truncatewords(u'A sentence with a few words in it', 1)
+u'A ...'
->>> truncatewords('A sentence with a few words in it', 1)
-'A ...'
+>>> truncatewords(u'A sentence with a few words in it', 5)
+u'A sentence with a few ...'
->>> truncatewords('A sentence with a few words in it', 5)
-'A sentence with a few ...'
+>>> truncatewords(u'A sentence with a few words in it', 100)
+u'A sentence with a few words in it'
->>> truncatewords('A sentence with a few words in it', 100)
-'A sentence with a few words in it'
+>>> truncatewords(u'A sentence with a few words in it', 'not a number')
+u'A sentence with a few words in it'
->>> truncatewords('A sentence with a few words in it', 'not a number')
-'A sentence with a few words in it'
+>>> truncatewords_html(u'<p>one <a href="#">two - three <br>four</a> five</p>', 0)
+u''
->>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 0)
-''
+>>> truncatewords_html(u'<p>one <a href="#">two - three <br>four</a> five</p>', 2)
+u'<p>one <a href="#">two ...</a></p>'
->>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 2)
-'<p>one <a href="#">two ...</a></p>'
+>>> truncatewords_html(u'<p>one <a href="#">two - three <br>four</a> five</p>', 4)
+u'<p>one <a href="#">two - three <br>four ...</a></p>'
->>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 4)
-'<p>one <a href="#">two - three <br>four ...</a></p>'
+>>> truncatewords_html(u'<p>one <a href="#">two - three <br>four</a> five</p>', 5)
+u'<p>one <a href="#">two - three <br>four</a> five</p>'
->>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 5)
-'<p>one <a href="#">two - three <br>four</a> five</p>'
+>>> truncatewords_html(u'<p>one <a href="#">two - three <br>four</a> five</p>', 100)
+u'<p>one <a href="#">two - three <br>four</a> five</p>'
->>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 100)
-'<p>one <a href="#">two - three <br>four</a> five</p>'
+>>> truncatewords_html(u'\xc5ngstr\xf6m was here', 1)
+u'\xc5ngstr\xf6m ...'
->>> upper('Mixed case input')
-'MIXED CASE INPUT'
+>>> upper(u'Mixed case input')
+u'MIXED CASE INPUT'
>>> upper(u'\xeb') # lowercase e umlaut
u'\xcb'
->>> urlencode('jack & jill')
-'jack%20%26%20jill'
+>>> urlencode(u'fran\xe7ois & jill')
+u'fran%C3%A7ois%20%26%20jill'
>>> urlencode(1)
-'1'
+u'1'
+>>> iriencode(u'S\xf8r-Tr\xf8ndelag')
+u'S%C3%B8r-Tr%C3%B8ndelag'
+>>> iriencode(urlencode(u'fran\xe7ois & jill'))
+u'fran%C3%A7ois%20%26%20jill'
+>>> urlizetrunc(u'http://short.com/', 20)
+u'<a href="http://short.com/" rel="nofollow">http://short.com/</a>'
->>> urlizetrunc('http://short.com/', 20)
-'<a href="http://short.com/" rel="nofollow">http://short.com/</a>'
+>>> urlizetrunc(u'http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20)
+u'<a href="http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=" rel="nofollow">http://www.google....</a>'
>>> urlizetrunc('http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20)
-'<a href="http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=" rel="nofollow">http://www.google...</a>'
+u'<a href="http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=" rel="nofollow">http://www.google...</a>'
# Check truncating of URIs which are the exact length
>>> uri = 'http://31characteruri.com/test/'
>>> len(uri)
31
>>> urlizetrunc(uri, 31)
-'<a href="http://31characteruri.com/test/" rel="nofollow">http://31characteruri.com/test/</a>'
+u'<a href="http://31characteruri.com/test/" rel="nofollow">http://31characteruri.com/test/</a>'
>>> urlizetrunc(uri, 30)
-'<a href="http://31characteruri.com/test/" rel="nofollow">http://31characteruri.com/t...</a>'
+u'<a href="http://31characteruri.com/test/" rel="nofollow">http://31characteruri.com/t...</a>'
>>> urlizetrunc(uri, 2)
-'<a href="http://31characteruri.com/test/" rel="nofollow">...</a>'
+u'<a href="http://31characteruri.com/test/" rel="nofollow">...</a>'
>>> wordcount('')
0
->>> wordcount('oneword')
+>>> wordcount(u'oneword')
1
->>> wordcount('lots of words')
+>>> wordcount(u'lots of words')
3
->>> wordwrap('this is a long paragraph of text that really needs to be wrapped I\'m afraid', 14)
-"this is a long\nparagraph of\ntext that\nreally needs\nto be wrapped\nI'm afraid"
+>>> wordwrap(u'this is a long paragraph of text that really needs to be wrapped I\'m afraid', 14)
+u"this is a long\nparagraph of\ntext that\nreally needs\nto be wrapped\nI'm afraid"
->>> wordwrap('this is a short paragraph of text.\n But this line should be indented',14)
-'this is a\nshort\nparagraph of\ntext.\n But this\nline should be\nindented'
+>>> wordwrap(u'this is a short paragraph of text.\n But this line should be indented',14)
+u'this is a\nshort\nparagraph of\ntext.\n But this\nline should be\nindented'
->>> wordwrap('this is a short paragraph of text.\n But this line should be indented',15)
-'this is a short\nparagraph of\ntext.\n But this line\nshould be\nindented'
+>>> wordwrap(u'this is a short paragraph of text.\n But this line should be indented',15)
+u'this is a short\nparagraph of\ntext.\n But this line\nshould be\nindented'
->>> ljust('test', 10)
-'test '
+>>> ljust(u'test', 10)
+u'test '
->>> ljust('test', 3)
-'test'
+>>> ljust(u'test', 3)
+u'test'
->>> rjust('test', 10)
-' test'
+>>> rjust(u'test', 10)
+u' test'
->>> rjust('test', 3)
-'test'
+>>> rjust(u'test', 3)
+u'test'
->>> center('test', 6)
-' test '
+>>> center(u'test', 6)
+u' test '
->>> cut('a string to be mangled', 'a')
-' string to be mngled'
+>>> cut(u'a string to be mangled', 'a')
+u' string to be mngled'
->>> cut('a string to be mangled', 'ng')
-'a stri to be maled'
+>>> cut(u'a string to be mangled', 'ng')
+u'a stri to be maled'
->>> cut('a string to be mangled', 'strings')
-'a string to be mangled'
+>>> cut(u'a string to be mangled', 'strings')
+u'a string to be mangled'
->>> escape('<some html & special characters > here')
-'&lt;some html &amp; special characters &gt; here'
+>>> escape(u'<some html & special characters > here')
+u'&lt;some html &amp; special characters &gt; here'
>>> escape(u'<some html & special characters > here ĐÅ€£')
u'&lt;some html &amp; special characters &gt; here \xc4\x90\xc3\x85\xe2\x82\xac\xc2\xa3'
->>> linebreaks('line 1')
-'<p>line 1</p>'
+>>> linebreaks(u'line 1')
+u'<p>line 1</p>'
->>> linebreaks('line 1\nline 2')
-'<p>line 1<br />line 2</p>'
+>>> linebreaks(u'line 1\nline 2')
+u'<p>line 1<br />line 2</p>'
->>> removetags('some <b>html</b> with <script>alert("You smell")</script> disallowed <img /> tags', 'script img')
-'some <b>html</b> with alert("You smell") disallowed tags'
+>>> removetags(u'some <b>html</b> with <script>alert("You smell")</script> disallowed <img /> tags', 'script img')
+u'some <b>html</b> with alert("You smell") disallowed tags'
->>> striptags('some <b>html</b> with <script>alert("You smell")</script> disallowed <img /> tags')
-'some html with alert("You smell") disallowed tags'
+>>> striptags(u'some <b>html</b> with <script>alert("You smell")</script> disallowed <img /> tags')
+u'some html with alert("You smell") disallowed tags'
>>> dictsort([{'age': 23, 'name': 'Barbara-Ann'},
... {'age': 63, 'name': 'Ra Ra Rasputin'},
@@ -207,16 +221,16 @@ u'&lt;some html &amp; special characters &gt; here \xc4\x90\xc3\x85\xe2\x82\xac\
>>> first([0,1,2])
0
->>> first('')
-''
+>>> first(u'')
+u''
->>> first('test')
-'t'
+>>> first(u'test')
+u't'
->>> join([0,1,2], 'glue')
-'0glue1glue2'
+>>> join([0,1,2], u'glue')
+u'0glue1glue2'
->>> length('1234')
+>>> length(u'1234')
4
>>> length([1,2,3,4])
@@ -231,37 +245,37 @@ False
>>> length_is('a', 1)
True
->>> length_is('a', 10)
+>>> length_is(u'a', 10)
False
->>> slice_('abcdefg', '0')
-''
+>>> slice_(u'abcdefg', u'0')
+u''
->>> slice_('abcdefg', '1')
-'a'
+>>> slice_(u'abcdefg', u'1')
+u'a'
->>> slice_('abcdefg', '-1')
-'abcdef'
+>>> slice_(u'abcdefg', u'-1')
+u'abcdef'
->>> slice_('abcdefg', '1:2')
-'b'
+>>> slice_(u'abcdefg', u'1:2')
+u'b'
->>> slice_('abcdefg', '1:3')
-'bc'
+>>> slice_(u'abcdefg', u'1:3')
+u'bc'
->>> slice_('abcdefg', '0::2')
-'aceg'
+>>> slice_(u'abcdefg', u'0::2')
+u'aceg'
->>> unordered_list(['item 1', []])
-'\t<li>item 1</li>'
+>>> unordered_list([u'item 1', []])
+u'\t<li>item 1</li>'
->>> unordered_list(['item 1', [['item 1.1', []]]])
-'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t</ul>\n\t</li>'
+>>> unordered_list([u'item 1', [[u'item 1.1', []]]])
+u'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t</ul>\n\t</li>'
->>> unordered_list(['item 1', [['item 1.1', []], ['item 1.2', []]]])
-'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t\t<li>item 1.2</li>\n\t</ul>\n\t</li>'
+>>> unordered_list([u'item 1', [[u'item 1.1', []], [u'item 1.2', []]]])
+u'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t\t<li>item 1.2</li>\n\t</ul>\n\t</li>'
->>> add('1', '2')
+>>> add(u'1', u'2')
3
>>> get_digit(123, 1)
@@ -279,43 +293,43 @@ False
>>> get_digit(123, 0)
123
->>> get_digit('xyz', 0)
-'xyz'
+>>> get_digit(u'xyz', 0)
+u'xyz'
# real testing of date() is in dateformat.py
->>> date(datetime.datetime(2005, 12, 29), "d F Y")
-'29 December 2005'
->>> date(datetime.datetime(2005, 12, 29), r'jS o\f F')
-'29th of December'
+>>> date(datetime.datetime(2005, 12, 29), u"d F Y")
+u'29 December 2005'
+>>> date(datetime.datetime(2005, 12, 29), ur'jS o\f F')
+u'29th of December'
# real testing of time() is done in dateformat.py
->>> time(datetime.time(13), "h")
-'01'
+>>> time(datetime.time(13), u"h")
+u'01'
->>> time(datetime.time(0), "h")
-'12'
+>>> time(datetime.time(0), u"h")
+u'12'
# real testing is done in timesince.py, where we can provide our own 'now'
>>> timesince(datetime.datetime.now() - datetime.timedelta(1))
-'1 day'
+u'1 day'
->>> default("val", "default")
-'val'
+>>> default(u"val", u"default")
+u'val'
->>> default(None, "default")
-'default'
+>>> default(None, u"default")
+u'default'
->>> default('', "default")
-'default'
+>>> default(u'', u"default")
+u'default'
->>> default_if_none("val", "default")
-'val'
+>>> default_if_none(u"val", u"default")
+u'val'
->>> default_if_none(None, "default")
-'default'
+>>> default_if_none(None, u"default")
+u'default'
->>> default_if_none('', "default")
-''
+>>> default_if_none(u'', u"default")
+u''
>>> divisibleby(4, 2)
True
@@ -324,139 +338,139 @@ True
False
>>> yesno(True)
-'yes'
+u'yes'
>>> yesno(False)
-'no'
+u'no'
>>> yesno(None)
-'maybe'
+u'maybe'
->>> yesno(True, 'certainly,get out of town,perhaps')
-'certainly'
+>>> yesno(True, u'certainly,get out of town,perhaps')
+u'certainly'
->>> yesno(False, 'certainly,get out of town,perhaps')
-'get out of town'
+>>> yesno(False, u'certainly,get out of town,perhaps')
+u'get out of town'
->>> yesno(None, 'certainly,get out of town,perhaps')
-'perhaps'
+>>> yesno(None, u'certainly,get out of town,perhaps')
+u'perhaps'
->>> yesno(None, 'certainly,get out of town')
-'get out of town'
+>>> yesno(None, u'certainly,get out of town')
+u'get out of town'
>>> filesizeformat(1023)
-'1023 bytes'
+u'1023 bytes'
>>> filesizeformat(1024)
-'1.0 KB'
+u'1.0 KB'
>>> filesizeformat(10*1024)
-'10.0 KB'
+u'10.0 KB'
>>> filesizeformat(1024*1024-1)
-'1024.0 KB'
+u'1024.0 KB'
>>> filesizeformat(1024*1024)
-'1.0 MB'
+u'1.0 MB'
>>> filesizeformat(1024*1024*50)
-'50.0 MB'
+u'50.0 MB'
>>> filesizeformat(1024*1024*1024-1)
-'1024.0 MB'
+u'1024.0 MB'
>>> filesizeformat(1024*1024*1024)
-'1.0 GB'
+u'1.0 GB'
>>> pluralize(1)
-''
+u''
>>> pluralize(0)
-'s'
+u's'
>>> pluralize(2)
-'s'
+u's'
>>> pluralize([1])
-''
+u''
>>> pluralize([])
-'s'
+u's'
>>> pluralize([1,2,3])
-'s'
+u's'
->>> pluralize(1,'es')
-''
+>>> pluralize(1,u'es')
+u''
->>> pluralize(0,'es')
-'es'
+>>> pluralize(0,u'es')
+u'es'
->>> pluralize(2,'es')
-'es'
+>>> pluralize(2,u'es')
+u'es'
->>> pluralize(1,'y,ies')
-'y'
+>>> pluralize(1,u'y,ies')
+u'y'
->>> pluralize(0,'y,ies')
-'ies'
+>>> pluralize(0,u'y,ies')
+u'ies'
->>> pluralize(2,'y,ies')
-'ies'
+>>> pluralize(2,u'y,ies')
+u'ies'
->>> pluralize(0,'y,ies,error')
-''
+>>> pluralize(0,u'y,ies,error')
+u''
->>> phone2numeric('0800 flowers')
-'0800 3569377'
+>>> phone2numeric(u'0800 flowers')
+u'0800 3569377'
# Filters shouldn't break if passed non-strings
>>> addslashes(123)
-'123'
+u'123'
>>> linenumbers(123)
-'1. 123'
+u'1. 123'
>>> lower(123)
-'123'
+u'123'
>>> make_list(123)
-['1', '2', '3']
+[u'1', u'2', u'3']
>>> slugify(123)
-'123'
+u'123'
>>> title(123)
-'123'
+u'123'
>>> truncatewords(123, 2)
-'123'
+u'123'
>>> upper(123)
-'123'
+u'123'
>>> urlencode(123)
-'123'
+u'123'
>>> urlize(123)
-'123'
+u'123'
>>> urlizetrunc(123, 1)
-'123'
+u'123'
>>> wordcount(123)
1
>>> wordwrap(123, 2)
-'123'
+u'123'
>>> ljust('123', 4)
-'123 '
+u'123 '
>>> rjust('123', 4)
-' 123'
+u' 123'
>>> center('123', 5)
-' 123 '
+u' 123 '
>>> center('123', 6)
-' 123 '
+u' 123 '
>>> cut(123, '2')
-'13'
+u'13'
>>> escape(123)
-'123'
+u'123'
>>> linebreaks(123)
-'<p>123</p>'
+u'<p>123</p>'
>>> linebreaksbr(123)
-'123'
+u'123'
>>> removetags(123, 'a')
-'123'
+u'123'
>>> striptags(123)
-'123'
+u'123'
"""
diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py
index dd407df353..31528238e9 100644
--- a/tests/regressiontests/fixtures_regress/models.py
+++ b/tests/regressiontests/fixtures_regress/models.py
@@ -4,8 +4,8 @@ class Animal(models.Model):
name = models.CharField(maxlength=150)
latin_name = models.CharField(maxlength=150)
- def __str__(self):
- return self.common_name
+ def __unicode__(self):
+ return self.common_name
class Plant(models.Model):
name = models.CharField(maxlength=150)
@@ -26,4 +26,4 @@ __test__ = {'API_TESTS':"""
>>> animal = Animal(name='Platypus', latin_name='Ornithorhynchus anatinus')
>>> animal.save()
-"""} \ No newline at end of file
+"""}
diff --git a/tests/regressiontests/forms/localflavor.py b/tests/regressiontests/forms/localflavor.py
index ede89de2a0..252aa10b34 100644
--- a/tests/regressiontests/forms/localflavor.py
+++ b/tests/regressiontests/forms/localflavor.py
@@ -1303,9 +1303,9 @@ strict.
>>> rut = CLRutField()
>>> rut.clean('11-6')
-'11-6'
+u'11-6'
>>> rut.clean('116')
-'11-6'
+u'11-6'
# valid format, bad verifier.
>>> rut.clean('11.111.111-0')
@@ -1318,13 +1318,13 @@ Traceback (most recent call last):
ValidationError: [u'The Chilean RUT is not valid.']
>>> rut.clean('767484100')
-'76.748.410-0'
+u'76.748.410-0'
>>> rut.clean('78.412.790-7')
-'78.412.790-7'
+u'78.412.790-7'
>>> rut.clean('8.334.6043')
-'8.334.604-3'
+u'8.334.604-3'
>>> rut.clean('76793310-K')
-'76.793.310-K'
+u'76.793.310-K'
Strict RUT usage (does not allow imposible values)
>>> rut = CLRutField(strict=True)
@@ -1346,7 +1346,7 @@ Traceback (most recent call last):
...
ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.']
>>> rut.clean('78.412.790-7')
-'78.412.790-7'
+u'78.412.790-7'
>>> rut.clean('8.334.6043')
Traceback (most recent call last):
...
diff --git a/tests/regressiontests/forms/regressions.py b/tests/regressiontests/forms/regressions.py
index 5fe057b5d8..784ef49902 100644
--- a/tests/regressiontests/forms/regressions.py
+++ b/tests/regressiontests/forms/regressions.py
@@ -16,24 +16,63 @@ u'<p>F1: <input type="text" class="special" name="f1" maxlength="10" /></p>\n<p>
#######################
There were some problems with form translations in #3600
->>> from django.utils.translation import gettext_lazy, activate, deactivate
+>>> from django.utils.translation import ugettext_lazy, activate, deactivate
>>> class SomeForm(Form):
-... username = CharField(max_length=10, label=gettext_lazy('Username'))
+... username = CharField(max_length=10, label=ugettext_lazy('Username'))
>>> f = SomeForm()
>>> print f.as_p()
<p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>
+
+Translations are done at rendering time, so multi-lingual apps can define forms
+early and still send back the right translation.
+
+# XFAIL
>>> activate('de')
>>> print f.as_p()
<p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>
+>>> activate('pl')
+>>> f.as_p()
+u'<p><label for="id_username">Nazwa u\u017cytkownika:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>'
>>> deactivate()
Unicode decoding problems...
->>> GENDERS = (('0', u'En tied\xe4'), ('1', u'Mies'), ('2', u'Nainen'))
+>>> GENDERS = ((u'\xc5', u'En tied\xe4'), (u'\xf8', u'Mies'), (u'\xdf', u'Nainen'))
>>> class SomeForm(Form):
-... somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect())
+... somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect(), label=u'\xc5\xf8\xdf')
>>> f = SomeForm()
>>> f.as_p()
-u'<p><label for="id_somechoice_0">Somechoice:</label> <ul>\n<li><label><input type="radio" id="id_somechoice_0" value="0" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="1" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="2" name="somechoice" /> Nainen</label></li>\n</ul></p>'
+u'<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'
+
+Testing choice validation with UTF-8 bytestrings as input (these are the
+Russian abbreviations "мес." and "шт.".
+
+>>> UNITS = (('\xd0\xbc\xd0\xb5\xd1\x81.', '\xd0\xbc\xd0\xb5\xd1\x81.'), ('\xd1\x88\xd1\x82.', '\xd1\x88\xd1\x82.'))
+>>> f = ChoiceField(choices=UNITS)
+>>> f.clean(u'\u0448\u0442.')
+u'\u0448\u0442.'
+>>> f.clean('\xd1\x88\xd1\x82.')
+u'\u0448\u0442.'
+
+Translated error messages used to be buggy.
+>>> activate('ru')
+>>> f = SomeForm({})
+>>> f.as_p()
+u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul>\n<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'
+>>> deactivate()
+
+#######################
+# Miscellaneous Tests #
+#######################
+
+There once was a problem with Form fields called "data". Let's make sure that
+doesn't come back.
+>>> class DataForm(Form):
+... data = CharField(max_length=10)
+>>> f = DataForm({'data': 'xyzzy'})
+>>> f.is_valid()
+True
+>>> f.cleaned_data
+{'data': u'xyzzy'}
#######################
# Miscellaneous Tests #
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index 0808ebe97e..67d527a08e 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -3374,7 +3374,7 @@ does not have help text, nothing will be output.
<input type="submit" />
</form>
>>> Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)}))
-''
+u''
The label_tag() method takes an optional attrs argument: a dictionary of HTML
attributes to add to the <label> tag.
diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py
index e7245104e9..9d5ca2006a 100644
--- a/tests/regressiontests/httpwrappers/tests.py
+++ b/tests/regressiontests/httpwrappers/tests.py
@@ -94,7 +94,7 @@ MultiValueDictKeyError: "Key 'foo' not found in <MultiValueDict: {}>"
>>> q['name'] = 'john'
>>> q['name']
-'john'
+u'john'
>>> del q['name']
>>> 'name' in q
@@ -106,10 +106,10 @@ False
'default'
>>> q.get('name', 'default')
-'john'
+u'john'
>>> q.getlist('name')
-['john']
+[u'john']
>>> q.getlist('foo')
[]
@@ -117,18 +117,18 @@ False
>>> q.setlist('foo', ['bar', 'baz'])
>>> q.get('foo', 'default')
-'baz'
+u'baz'
>>> q.getlist('foo')
-['bar', 'baz']
+[u'bar', u'baz']
>>> q.appendlist('foo', 'another')
>>> q.getlist('foo')
-['bar', 'baz', 'another']
+[u'bar', u'baz', u'another']
>>> q['foo']
-'another'
+u'another'
>>> q.has_key('foo')
True
@@ -137,16 +137,16 @@ True
True
>>> q.items()
-[('foo', 'another'), ('name', 'john')]
+[(u'foo', u'another'), (u'name', u'john')]
>>> q.lists()
-[('foo', ['bar', 'baz', 'another']), ('name', ['john'])]
+[(u'foo', [u'bar', u'baz', u'another']), (u'name', [u'john'])]
>>> q.keys()
-['foo', 'name']
+[u'foo', u'name']
>>> q.values()
-['another', 'john']
+[u'another', u'john']
>>> len(q)
2
@@ -155,16 +155,16 @@ True
# Displays last value
>>> q['foo']
-'hello'
+u'hello'
>>> q.get('foo', 'not available')
-'hello'
+u'hello'
>>> q.getlist('foo')
-['bar', 'baz', 'another', 'hello']
+[u'bar', u'baz', u'another', u'hello']
>>> q.pop('foo')
-['bar', 'baz', 'another', 'hello']
+[u'bar', u'baz', u'another', u'hello']
>>> q.pop('foo', 'not there')
'not there'
@@ -173,13 +173,13 @@ True
'not there'
>>> q.setdefault('foo', 'bar')
-'bar'
+u'bar'
>>> q['foo']
-'bar'
+u'bar'
>>> q.getlist('foo')
-['bar']
+[u'bar']
>>> q.urlencode()
'foo=bar&name=john'
@@ -196,12 +196,12 @@ True
>>> q = QueryDict('foo=bar')
>>> q['foo']
-'bar'
+u'bar'
>>> q['bar']
Traceback (most recent call last):
...
-MultiValueDictKeyError: "Key 'bar' not found in <MultiValueDict: {'foo': ['bar']}>"
+MultiValueDictKeyError: "Key 'bar' not found in <MultiValueDict: {u'foo': [u'bar']}>"
>>> q['something'] = 'bar'
Traceback (most recent call last):
@@ -209,13 +209,13 @@ Traceback (most recent call last):
AttributeError: This QueryDict instance is immutable
>>> q.get('foo', 'default')
-'bar'
+u'bar'
>>> q.get('bar', 'default')
'default'
>>> q.getlist('foo')
-['bar']
+[u'bar']
>>> q.getlist('bar')
[]
@@ -243,16 +243,16 @@ False
False
>>> q.items()
-[('foo', 'bar')]
+[(u'foo', u'bar')]
>>> q.lists()
-[('foo', ['bar'])]
+[(u'foo', [u'bar'])]
>>> q.keys()
-['foo']
+[u'foo']
>>> q.values()
-['bar']
+[u'bar']
>>> len(q)
1
@@ -292,7 +292,7 @@ AttributeError: This QueryDict instance is immutable
>>> q = QueryDict('vote=yes&vote=no')
>>> q['vote']
-'no'
+u'no'
>>> q['something'] = 'bar'
Traceback (most recent call last):
@@ -300,13 +300,13 @@ Traceback (most recent call last):
AttributeError: This QueryDict instance is immutable
>>> q.get('vote', 'default')
-'no'
+u'no'
>>> q.get('foo', 'default')
'default'
>>> q.getlist('vote')
-['yes', 'no']
+[u'yes', u'no']
>>> q.getlist('foo')
[]
@@ -334,16 +334,16 @@ False
False
>>> q.items()
-[('vote', 'no')]
+[(u'vote', u'no')]
>>> q.lists()
-[('vote', ['yes', 'no'])]
+[(u'vote', [u'yes', u'no'])]
>>> q.keys()
-['vote']
+[u'vote']
>>> q.values()
-['no']
+[u'no']
>>> len(q)
1
@@ -381,6 +381,16 @@ Traceback (most recent call last):
...
AttributeError: This QueryDict instance is immutable
+# QueryDicts must be able to handle invalid input encoding (in this case, bad
+# UTF-8 encoding).
+>>> q = QueryDict('foo=bar&foo=\xff')
+
+>>> q['foo']
+u'\ufffd'
+
+>>> q.getlist('foo')
+[u'bar', u'\ufffd']
+
"""
from django.http import QueryDict
diff --git a/tests/regressiontests/humanize/tests.py b/tests/regressiontests/humanize/tests.py
index eca65f7575..f94d642973 100644
--- a/tests/regressiontests/humanize/tests.py
+++ b/tests/regressiontests/humanize/tests.py
@@ -15,7 +15,7 @@ class HumanizeTests(unittest.TestCase):
self.assertEqual(rendered, result_list[index],
msg="""%s test failed, produced %s,
should've produced %s""" % (method, rendered, result_list[index]))
-
+
def test_ordinal(self):
test_list = ('1','2','3','4','11','12',
'13','101','102','103','111',
@@ -43,12 +43,12 @@ should've produced %s""" % (method, rendered, result_list[index]))
self.humanize_tester(test_list, result_list, 'intword')
def test_apnumber(self):
- test_list = [str(x) for x in xrange(1,11)]
- result_list = ('one', 'two', 'three', 'four', 'five', 'six',
- 'seven', 'eight', 'nine', '10')
+ test_list = [str(x) for x in range(1, 11)]
+ result_list = (u'one', u'two', u'three', u'four', u'five', u'six',
+ u'seven', u'eight', u'nine', u'10')
self.humanize_tester(test_list, result_list, 'apnumber')
if __name__ == '__main__':
unittest.main()
-
+
diff --git a/tests/regressiontests/i18n/__init__.py b/tests/regressiontests/i18n/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/i18n/__init__.py
diff --git a/tests/regressiontests/i18n/models.py b/tests/regressiontests/i18n/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/i18n/models.py
diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
new file mode 100644
index 0000000000..8a7d2bee3e
--- /dev/null
+++ b/tests/regressiontests/i18n/tests.py
@@ -0,0 +1,33 @@
+# coding: utf-8
+
+ur"""
+Format string interpolation should work with *_lazy objects.
+
+>>> from django.utils.translation import ugettext_lazy, activate, deactivate, gettext_lazy
+>>> s = ugettext_lazy('Add %(name)s')
+>>> d = {'name': 'Ringo'}
+>>> s % d
+u'Add Ringo'
+>>> activate('de')
+>>> s % d
+u'Ringo hinzuf\xfcgen'
+>>> activate('pl')
+>>> s % d
+u'Dodaj Ringo'
+>>> deactivate()
+
+It should be possible to compare *_lazy objects.
+
+>>> s1 = ugettext_lazy('Add %(name)s')
+>>> s == s1
+True
+>>> s2 = gettext_lazy('Add %(name)s')
+>>> s3 = gettext_lazy('Add %(name)s')
+>>> s2 == s3
+True
+>>> s == s2
+True
+>>> s4 = ugettext_lazy('Some other string')
+>>> s == s4
+False
+"""
diff --git a/tests/regressiontests/model_regress/__init__.py b/tests/regressiontests/model_regress/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/model_regress/__init__.py
diff --git a/tests/regressiontests/model_regress/models.py b/tests/regressiontests/model_regress/models.py
new file mode 100644
index 0000000000..b4d432d8fa
--- /dev/null
+++ b/tests/regressiontests/model_regress/models.py
@@ -0,0 +1,34 @@
+# coding: utf-8
+from django.db import models
+
+CHOICES = (
+ (1, 'first'),
+ (2, 'second'),
+)
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100, default='Default headline')
+ pub_date = models.DateTimeField()
+ status = models.IntegerField(blank=True, null=True, choices=CHOICES)
+
+ class Meta:
+ ordering = ('pub_date','headline')
+ # A utf-8 verbose name (Ångström's Articles) to test they are valid.
+ verbose_name = "\xc3\x85ngstr\xc3\xb6m's Articles"
+
+ def __unicode__(self):
+ return self.headline
+
+__test__ = {'API_TESTS': """
+(NOTE: Part of the regression test here is merely parsing the model
+declaration. The verbose_name, in particular, did not always work.)
+
+An empty choice field should return None for the display name.
+
+>>> from datetime import datetime
+>>> a = Article(headline="Look at me!", pub_date=datetime.now())
+>>> a.save()
+>>> a.get_status_display() is None
+True
+"""
+}
diff --git a/tests/regressiontests/null_queries/models.py b/tests/regressiontests/null_queries/models.py
index 21944d9e7a..2e903876bf 100644
--- a/tests/regressiontests/null_queries/models.py
+++ b/tests/regressiontests/null_queries/models.py
@@ -3,15 +3,15 @@ from django.db import models
class Poll(models.Model):
question = models.CharField(maxlength=200)
- def __str__(self):
- return "Q: %s " % self.question
+ def __unicode__(self):
+ return u"Q: %s " % self.question
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice = models.CharField(maxlength=200)
- def __str__(self):
- return "Choice: %s in poll %s" % (self.choice, self.poll)
+ def __unicode__(self):
+ return u"Choice: %s in poll %s" % (self.choice, self.poll)
__test__ = {'API_TESTS':"""
# Regression test for the use of None as a query value. None is interpreted as
diff --git a/tests/regressiontests/one_to_one_regress/models.py b/tests/regressiontests/one_to_one_regress/models.py
index b81f4266e1..be48c842ed 100644
--- a/tests/regressiontests/one_to_one_regress/models.py
+++ b/tests/regressiontests/one_to_one_regress/models.py
@@ -4,23 +4,23 @@ class Place(models.Model):
name = models.CharField(maxlength=50)
address = models.CharField(maxlength=80)
- def __str__(self):
- return "%s the place" % self.name
+ def __unicode__(self):
+ return u"%s the place" % self.name
class Restaurant(models.Model):
place = models.OneToOneField(Place)
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
- def __str__(self):
- return "%s the restaurant" % self.place.name
+ def __unicode__(self):
+ return u"%s the restaurant" % self.place.name
class Favorites(models.Model):
name = models.CharField(maxlength = 50)
restaurants = models.ManyToManyField(Restaurant)
- def __str__(self):
- return "Favorites for %s" % self.name
+ def __unicode__(self):
+ return u"Favorites for %s" % self.name
__test__ = {'API_TESTS':"""
# Regression test for #1064 and #1506: Check that we create models via the m2m
diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py
index febcfa822e..7c6da9d356 100644
--- a/tests/regressiontests/serializers_regress/tests.py
+++ b/tests/regressiontests/serializers_regress/tests.py
@@ -111,6 +111,9 @@ test_data = [
(data_obj, 13, CharData, "null"),
(data_obj, 14, CharData, "NULL"),
(data_obj, 15, CharData, None),
+ # (We use something that will fit into a latin1 database encoding here,
+ # because that is still the default used on many system setups.)
+ (data_obj, 16, CharData, u'\xa5'),
(data_obj, 20, DateData, datetime.date(2006,6,16)),
(data_obj, 21, DateData, None),
(data_obj, 30, DateTimeData, datetime.datetime(2006,6,16,10,42,37)),
diff --git a/tests/regressiontests/string_lookup/models.py b/tests/regressiontests/string_lookup/models.py
index 441bb3f8a3..66651ce680 100644
--- a/tests/regressiontests/string_lookup/models.py
+++ b/tests/regressiontests/string_lookup/models.py
@@ -1,9 +1,11 @@
+# -*- coding: utf-8 -*-
from django.db import models
class Foo(models.Model):
name = models.CharField(maxlength=50)
+ friend = models.CharField(maxlength=50, blank=True)
- def __str__(self):
+ def __unicode__(self):
return "Foo %s" % self.name
class Bar(models.Model):
@@ -12,35 +14,35 @@ class Bar(models.Model):
fwd = models.ForeignKey("Whiz")
back = models.ForeignKey("Foo")
- def __str__(self):
+ def __unicode__(self):
return "Bar %s" % self.place.name
class Whiz(models.Model):
name = models.CharField(maxlength = 50)
- def __str__(self):
+ def __unicode__(self):
return "Whiz %s" % self.name
class Child(models.Model):
parent = models.OneToOneField('Base')
name = models.CharField(maxlength = 50)
- def __str__(self):
+ def __unicode__(self):
return "Child %s" % self.name
-
+
class Base(models.Model):
name = models.CharField(maxlength = 50)
- def __str__(self):
+ def __unicode__(self):
return "Base %s" % self.name
-__test__ = {'API_TESTS':"""
-# Regression test for #1661 and #1662: Check that string form referencing of models works,
-# both as pre and post reference, on all RelatedField types.
+__test__ = {'API_TESTS': ur"""
+# Regression test for #1661 and #1662: Check that string form referencing of
+# models works, both as pre and post reference, on all RelatedField types.
>>> f1 = Foo(name="Foo1")
>>> f1.save()
->>> f2 = Foo(name="Foo1")
+>>> f2 = Foo(name="Foo2")
>>> f2.save()
>>> w1 = Whiz(name="Whiz1")
@@ -56,7 +58,7 @@ __test__ = {'API_TESTS':"""
<Whiz: Whiz Whiz1>
>>> b1.back
-<Foo: Foo Foo1>
+<Foo: Foo Foo2>
>>> base1 = Base(name="Base1")
>>> base1.save()
@@ -66,4 +68,18 @@ __test__ = {'API_TESTS':"""
>>> child1.parent
<Base: Base Base1>
+
+# Regression tests for #3937: make sure we can use unicode characters in
+# queries.
+# BUG: These tests fail on MySQL, but it's a problem with the test setup. A
+# properly configured UTF-8 database can handle this.
+
+>>> fx = Foo(name='Bjorn', friend=u'François')
+>>> fx.save()
+>>> Foo.objects.get(friend__contains=u'\xe7')
+<Foo: Foo Bjorn>
+
+# We can also do the above query using UTF-8 strings.
+>>> Foo.objects.get(friend__contains='\xc3\xa7')
+<Foo: Foo Bjorn>
"""}
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index 8801100bcc..5b4ff10bdb 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -8,11 +8,17 @@ if __name__ == '__main__':
from django import template
from django.template import loader
-from django.utils.translation import activate, deactivate, install
+from django.utils.translation import activate, deactivate, install, ugettext as _
from django.utils.tzinfo import LocalTimezone
from datetime import datetime, timedelta
+from unicode import unicode_tests
import unittest
+# Some other tests we would like to run
+__test__ = {
+ 'unicode': unicode_tests,
+}
+
#################################
# Custom template tag for tests #
#################################
@@ -63,10 +69,10 @@ class OtherClass:
def method(self):
return "OtherClass.method"
-class UnicodeInStrClass:
- "Class whose __str__ returns a Unicode object."
+class UTF8Class:
+ "Class whose __str__ returns non-ASCII data"
def __str__(self):
- return u'ŠĐĆŽćžšđ'
+ return u'ŠĐĆŽćžšđ'.encode('utf-8')
class Templates(unittest.TestCase):
def test_templates(self):
@@ -215,9 +221,9 @@ class Templates(unittest.TestCase):
# Empty strings can be passed as arguments to filters
'filter-syntax17': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'),
- # If a variable has a __str__() that returns a Unicode object, the
- # value will be converted to a bytestring.
- 'filter-syntax18': (r'{{ var }}', {'var': UnicodeInStrClass()}, '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91'),
+ # Make sure that any unicode strings are converted to bytestrings
+ # in the final output.
+ 'filter-syntax18': (r'{{ var }}', {'var': UTF8Class()}, u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'),
# Numbers as filter arguments should work
'filter-syntax19': ('{{ var|truncatewords:1 }}', {"var": "hello world"}, "hello ..."),
@@ -729,6 +735,7 @@ class Templates(unittest.TestCase):
'url02' : ('{% url regressiontests.templates.views.client_action client.id, action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url03' : ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'),
'url04' : ('{% url named.client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'),
+ 'url05' : (u'{% url метка_оператора 1 %}', {}, '/url_tag/unicode/1/'),
# Failures
'url-fail01' : ('{% url %}', {}, template.TemplateSyntaxError),
diff --git a/tests/regressiontests/templates/unicode.py b/tests/regressiontests/templates/unicode.py
new file mode 100644
index 0000000000..efda11c2da
--- /dev/null
+++ b/tests/regressiontests/templates/unicode.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+unicode_tests = ur"""
+Templates can be created from unicode strings.
+>>> from django.template import *
+>>> t1 = Template(u'ŠĐĆŽćžšđ {{ var }}')
+
+Templates can also be created from bytestrings. These are assumed by encoded
+using UTF-8.
+
+>>> s = '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91 {{ var }}'
+>>> t2 = Template(s)
+>>> s = '\x80\xc5\xc0'
+>>> Template(s)
+Traceback (most recent call last):
+ ...
+TemplateEncodingError: Templates can only be constructed from unicode or UTF-8 strings.
+
+Contexts can be constructed from unicode or UTF-8 bytestrings.
+
+>>> c1 = Context({'var': 'foo'})
+>>> c2 = Context({u'var': 'foo'})
+>>> c3 = Context({'var': u'Đđ'})
+>>> c4 = Context({u'var': '\xc4\x90\xc4\x91'})
+
+Since both templates and all four contexts represent the same thing, they all
+render the same (and are returned as unicode objects).
+
+>>> t1.render(c3) == t2.render(c3)
+True
+>>> type(t1.render(c3))
+<type 'unicode'>
+"""
diff --git a/tests/regressiontests/templates/urls.py b/tests/regressiontests/templates/urls.py
index 5fbade5c58..4ac9eb29f7 100644
--- a/tests/regressiontests/templates/urls.py
+++ b/tests/regressiontests/templates/urls.py
@@ -1,3 +1,4 @@
+# coding: utf-8
from django.conf.urls.defaults import *
from regressiontests.templates import views
@@ -8,4 +9,5 @@ urlpatterns = patterns('',
(r'^client/(\d+)/$', views.client),
(r'^client/(\d+)/(?P<action>[^/]+)/$', views.client_action),
url(r'^named-client/(\d+)/$', views.client, name="named.client"),
+ url(r'^unicode/(\d+)/$', views.client, name=u"метка_оператора"),
)
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
index 40d022a47a..35aaaf9720 100644
--- a/tests/regressiontests/test_client_regress/models.py
+++ b/tests/regressiontests/test_client_regress/models.py
@@ -4,6 +4,7 @@ Regression tests for the Test Client, especially the customized assertions.
"""
from django.test import Client, TestCase
from django.core import mail
+import os
class AssertTemplateUsedTests(TestCase):
fixtures = ['testdata.json']
@@ -59,7 +60,7 @@ class AssertTemplateUsedTests(TestCase):
try:
self.assertTemplateUsed(response, "Valid POST Template")
except AssertionError, e:
- self.assertEquals(str(e), "Template 'Valid POST Template' was not one of the templates used to render the response. Templates used: ['form_view.html', 'base.html']")
+ self.assertEquals(str(e), "Template 'Valid POST Template' was not one of the templates used to render the response. Templates used: form_view.html, base.html")
class AssertRedirectsTests(TestCase):
def test_redirect_page(self):
@@ -162,3 +163,12 @@ class AssertFormErrorTests(TestCase):
except AssertionError, e:
self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])")
+class AssertFileUploadTests(TestCase):
+ def test_simple_upload(self):
+ fd = open(os.path.join(os.path.dirname(__file__), "views.py"))
+ post_data = {
+ 'name': 'Ringo',
+ 'file_field': fd,
+ }
+ response = self.client.post('/test_client_regress/file_upload/', post_data)
+ self.assertEqual(response.status_code, 200)
diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py
index e9cd0aea15..160f9f992d 100644
--- a/tests/regressiontests/test_client_regress/urls.py
+++ b/tests/regressiontests/test_client_regress/urls.py
@@ -1,7 +1,7 @@
from django.conf.urls.defaults import *
-from django.views.generic.simple import redirect_to
import views
urlpatterns = patterns('',
(r'^no_template_view/$', views.no_template_view),
+ (r'^file_upload/$', views.file_upload_view),
)
diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py
index d8dd2b349c..8728ab0d7d 100644
--- a/tests/regressiontests/test_client_regress/views.py
+++ b/tests/regressiontests/test_client_regress/views.py
@@ -1,8 +1,20 @@
from django.core.mail import EmailMessage, SMTPConnection
-from django.http import HttpResponse
+from django.http import HttpResponse, HttpResponseServerError
from django.shortcuts import render_to_response
def no_template_view(request):
"A simple view that expects a GET request, and returns a rendered template"
return HttpResponse("No template used")
+def file_upload_view(request):
+ """
+ Check that a file upload can be updated into the POST dictionary without
+ going pear-shaped.
+ """
+ form_data = request.POST.copy()
+ form_data.update(request.FILES)
+ if isinstance(form_data['file_field'], dict) and isinstance(form_data['name'], unicode):
+ return HttpResponse('')
+ else:
+ return HttpResponseServerError()
+
diff --git a/tests/regressiontests/text/tests.py b/tests/regressiontests/text/tests.py
index f758ecaf90..0fd22b58b0 100644
--- a/tests/regressiontests/text/tests.py
+++ b/tests/regressiontests/text/tests.py
@@ -1,17 +1,38 @@
-"""
-# Tests for stuff in django.utils.text.
+# coding: utf-8
+r"""
+# Tests for stuff in django.utils.text and other text munging util functions.
>>> from django.utils.text import *
### smart_split ###########################################################
>>> list(smart_split(r'''This is "a person" test.'''))
-['This', 'is', '"a person"', 'test.']
+[u'This', u'is', u'"a person"', u'test.']
>>> print list(smart_split(r'''This is "a person's" test.'''))[2]
"a person's"
->>> print list(smart_split(r'''This is "a person\\"s" test.'''))[2]
+>>> print list(smart_split(r'''This is "a person\"s" test.'''))[2]
"a person"s"
>>> list(smart_split('''"a 'one'''))
-['"a', "'one"]
+[u'"a', u"'one"]
>>> print list(smart_split(r'''all friends' tests'''))[1]
friends'
+
+### urlquote #############################################################
+>>> from django.utils.http import urlquote, urlquote_plus
+>>> urlquote(u'Paris & Orl\xe9ans')
+u'Paris%20%26%20Orl%C3%A9ans'
+>>> urlquote_plus(u'Paris & Orl\xe9ans')
+u'Paris+%26+Orl%C3%A9ans'
+
+### iri_to_uri ###########################################################
+>>> from django.utils.encoding import iri_to_uri
+>>> iri_to_uri(u'red%09ros\xe9#red')
+'red%09ros%C3%A9#red'
+>>> iri_to_uri(u'/blog/for/J\xfcrgen M\xfcnster/')
+'/blog/for/J%C3%BCrgen%20M%C3%BCnster/'
+>>> iri_to_uri(u'locations/%s' % urlquote_plus(u'Paris & Orl\xe9ans'))
+'locations/Paris+%26+Orl%C3%A9ans'
+
+iri_to_uri() is idempotent:
+>>> iri_to_uri(iri_to_uri(u'red%09ros\xe9#red'))
+'red%09ros%C3%A9#red'
"""