diff --git a/django/newforms/fields.py b/django/newforms/fields.py
index b9e2ed35c7..179555fc77 100644
--- a/django/newforms/fields.py
+++ b/django/newforms/fields.py
@@ -2,7 +2,7 @@
Field classes
"""
-from util import ValidationError, DEFAULT_ENCODING
+from util import ValidationError, DEFAULT_ENCODING, smart_unicode
from widgets import TextInput, CheckboxInput, Select, SelectMultiple
import datetime
import re
@@ -55,10 +55,7 @@ class CharField(Field):
"Validates max_length and min_length. Returns a Unicode object."
Field.clean(self, value)
if value in EMPTY_VALUES: value = u''
- if not isinstance(value, basestring):
- value = unicode(str(value), DEFAULT_ENCODING)
- elif not isinstance(value, unicode):
- value = unicode(value, DEFAULT_ENCODING)
+ value = smart_unicode(value)
if self.max_length is not None and len(value) > self.max_length:
raise ValidationError(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:
@@ -165,10 +162,7 @@ class RegexField(Field):
"""
Field.clean(self, value)
if value in EMPTY_VALUES: value = u''
- if not isinstance(value, basestring):
- value = unicode(str(value), DEFAULT_ENCODING)
- elif not isinstance(value, unicode):
- value = unicode(value, DEFAULT_ENCODING)
+ value = smart_unicode(value)
if not self.regex.search(value):
raise ValidationError(self.error_message)
return value
@@ -244,10 +238,7 @@ class ChoiceField(Field):
"""
value = Field.clean(self, value)
if value in EMPTY_VALUES: value = u''
- if not isinstance(value, basestring):
- value = unicode(str(value), DEFAULT_ENCODING)
- elif not isinstance(value, unicode):
- value = unicode(value, DEFAULT_ENCODING)
+ value = smart_unicode(value)
valid_values = set([str(k) for k, v in self.choices])
if value not in valid_values:
raise ValidationError(u'Select a valid choice. %s is not one of the available choices.' % value)
@@ -267,11 +258,8 @@ class MultipleChoiceField(ChoiceField):
raise ValidationError(u'This field is required.')
new_value = []
for val in value:
- if not isinstance(val, basestring):
- value = unicode(str(val), DEFAULT_ENCODING)
- elif not isinstance(val, unicode):
- value = unicode(val, DEFAULT_ENCODING)
- new_value.append(value)
+ val = smart_unicode(val)
+ new_value.append(val)
# Validate that each value in the value list is in self.choices.
valid_values = set([k for k, v in self.choices])
for val in new_value:
diff --git a/django/newforms/util.py b/django/newforms/util.py
index 3887010c85..6b101fc152 100644
--- a/django/newforms/util.py
+++ b/django/newforms/util.py
@@ -2,6 +2,8 @@
DEFAULT_ENCODING = 'utf-8' # TODO: First look at django.conf.settings, then fall back to this.
def smart_unicode(s):
+ if not isinstance(s, basestring):
+ s = unicode(str(s))
if not isinstance(s, unicode):
s = unicode(s, DEFAULT_ENCODING)
return s
diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py
index c9b3d02913..318c76e55d 100644
--- a/django/newforms/widgets.py
+++ b/django/newforms/widgets.py
@@ -8,6 +8,7 @@ __all__ = (
'Select', 'SelectMultiple', 'RadioSelect',
)
+from util import smart_unicode
from django.utils.html import escape
from itertools import chain
@@ -18,7 +19,7 @@ except NameError:
# Converts a dictionary to a single string with key="value", XML-style with
# a leading space. Assumes keys do not need to be XML-escaped.
-flatatt = lambda attrs: ''.join([' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
+flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
class Widget(object):
requires_data_list = False # Determines whether render()'s 'value' argument should be a list.
@@ -43,7 +44,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'] = value # Only add the 'value' attribute if a value is non-empty.
+ if value != '': final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
return u'' % flatatt(final_attrs)
class TextInput(Input):
@@ -61,6 +62,7 @@ class FileInput(Input):
class Textarea(Widget):
def render(self, name, value, attrs=None):
if value is None: value = ''
+ value = smart_unicode(value)
final_attrs = self.build_attrs(attrs, name=name)
return u'' % (flatatt(final_attrs), escape(value))
@@ -80,10 +82,11 @@ class Select(Widget):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, name=name)
output = [u'')
return u'\n'.join(output)
@@ -98,10 +101,11 @@ class SelectMultiple(Widget):
if value is None: value = []
final_attrs = self.build_attrs(attrs, name=name)
output = [u'')
return u'\n'.join(output)
@@ -116,7 +120,7 @@ class RadioInput(object):
return u'' % (self.tag(), self.choice_label)
def is_checked(self):
- return self.value == str(self.choice_value)
+ return self.value == smart_unicode(self.choice_value)
def tag(self):
final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
@@ -142,7 +146,7 @@ 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 = str(value) # Normalize to string.
+ str_value = smart_unicode(value) # Normalize to string.
return RadioFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices)))
class CheckboxSelectMultiple(Widget):
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index b02ccb8ee8..c4dd7074a5 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -1,4 +1,5 @@
-"""
+# -*- coding: utf-8 -*-
+r"""
>>> from django.newforms import *
>>> import datetime
>>> import re
@@ -17,6 +18,11 @@ u''
+# Note that doctest in Python 2.4 (and maybe 2.5?) doesn't support non-ascii
+# characters in output, so we're displaying the repr() here.
+>>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
+u''
+
You can also pass 'attrs' to the constructor:
>>> w = TextInput(attrs={'class': 'fun'})
>>> w.render('email', '')
@@ -55,6 +61,9 @@ u''
>>> w.render('email', '', attrs={'class': 'special'})
u''
+>>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
+u''
+
# HiddenInput Widget ############################################################
>>> w = HiddenInput()
@@ -81,6 +90,14 @@ u''
>>> w.render('email', '', attrs={'class': 'special'})
u''
+>>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
+u''
+
+'attrs' passed to render() get precedence over those passed to the constructor:
+>>> w = HiddenInput(attrs={'class': 'pretty'})
+>>> w.render('email', '', attrs={'class': 'special'})
+u''
+
# FileInput Widget ############################################################
>>> w = FileInput()
@@ -102,10 +119,8 @@ u''
>>> w.render('email', 'foo@example.com')
u''
-'attrs' passed to render() get precedence over those passed to the constructor:
->>> w = HiddenInput(attrs={'class': 'pretty'})
->>> w.render('email', '', attrs={'class': 'special'})
-u''
+>>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
+u''
# Textarea Widget #############################################################
@@ -133,6 +148,9 @@ u''
>>> w.render('msg', '', attrs={'class': 'special'})
u''
+>>> w.render('msg', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
+u''
+
# CheckboxInput Widget ########################################################
>>> w = CheckboxInput()
@@ -236,6 +254,9 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
+>>> w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
+u''
+
# SelectMultiple Widget #######################################################
>>> w = SelectMultiple()
@@ -340,6 +361,9 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
+>>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
+u''
+
# RadioSelect Widget ##########################################################
>>> w = RadioSelect()