Ticket #6630: 00-newforms-fieldsets.diff

File 00-newforms-fieldsets.diff, 48.3 KB (added by Petr Marhoun <petr.marhoun@…>, 16 years ago)
  • django/newforms/extras/widgets.py

    === modified file 'django/newforms/extras/widgets.py'
     
    2121    day_field = '%s_day'
    2222    year_field = '%s_year'
    2323
    24     def __init__(self, attrs=None, years=None):
     24    def __init__(self, attrs=None, row_attrs=None, years=None):
    2525        # years is an optional list/tuple of years to use in the "year" select box.
    26         self.attrs = attrs or {}
     26        super(SelectDateWidget, self).__init__(attrs, row_attrs)
    2727        if years:
    2828            self.years = years
    2929        else:
  • django/newforms/fields.py

    === modified file 'django/newforms/fields.py'
     
    513513            return value
    514514        if self.verify_exists:
    515515            import urllib2
    516             from django.conf import settings
    517516            headers = {
    518517                "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
    519518                "Accept-Language": "en-us,en;q=0.5",
  • django/newforms/forms.py

    === modified file 'django/newforms/forms.py'
     
    44
    55from copy import deepcopy
    66
    7 from django.utils.datastructures import SortedDict
    8 from django.utils.html import escape
     7from django.utils.datastructures import SortedDict, InheritableOptions
     8from django.utils.html import conditional_escape
    99from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
    1010from django.utils.safestring import mark_safe
    1111
     
    2222    name = name[0].upper() + name[1:]
    2323    return name.replace('_', ' ')
    2424
    25 def get_declared_fields(bases, attrs, with_base_fields=True):
    26     """
    27     Create a list of form field instances from the passed in 'attrs', plus any
    28     similar fields on the base classes (in 'bases'). This is used by both the
    29     Form and ModelForm metclasses.
    30 
    31     If 'with_base_fields' is True, all fields from the bases are used.
    32     Otherwise, only fields in the 'declared_fields' attribute on the bases are
    33     used. The distinction is useful in ModelForm subclassing.
    34     """
    35     fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
    36     fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
    37 
    38     # If this class is subclassing another Form, add that Form's fields.
    39     # Note that we loop over the bases in *reverse*. This is necessary in
    40     # order to preserve the correct order of fields.
    41     if with_base_fields:
    42         for base in bases[::-1]:
    43             if hasattr(base, 'base_fields'):
    44                 fields = base.base_fields.items() + fields
    45     else:
    46         for base in bases[::-1]:
    47             if hasattr(base, 'declared_fields'):
    48                 fields = base.declared_fields.items() + fields
    49 
    50     return SortedDict(fields)
    51 
    52 class DeclarativeFieldsMetaclass(type):
    53     """
    54     Metaclass that converts Field attributes to a dictionary called
    55     'base_fields', taking into account parent class 'base_fields' as well.
    56     """
     25class FormOptions(InheritableOptions):
     26    _default_options = {
     27        'fieldsets': None,
     28        'fields': None,
     29        'exclude': None,
     30    }
     31
     32class FormMetaclass(type):
     33
     34    def create_options(cls, new_cls):
     35        new_cls._meta = new_cls.options(new_cls)
     36        try:
     37            delattr(new_cls, 'Meta')
     38        except AttributeError:
     39            pass
     40    create_options = classmethod(create_options)
     41
     42    def create_declared_fields(cls, new_cls):
     43        fields = []
     44        for name, attr in new_cls.__dict__.items():
     45            if isinstance(attr, Field):
     46                fields.append((name, attr))
     47                delattr(new_cls, name)
     48        fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
     49        new_cls._declared_fields = SortedDict(fields)
     50    create_declared_fields = classmethod(create_declared_fields)
     51
     52    def create_base_fields_pool_from_declared_fields(cls, new_cls):
     53        fields = []
     54        # Add all declared fields from this class and from superclasses. Note
     55        # that we loop over the bases in *reverse*. This is necessary
     56        # in order to preserve the correct order of fields.
     57        for base in new_cls.__mro__[::-1]:
     58            try:
     59                fields += base._declared_fields.items()
     60            except AttributeError:
     61                pass
     62        new_cls._base_fields_pool = SortedDict(fields)
     63    create_base_fields_pool_from_declared_fields = classmethod(create_base_fields_pool_from_declared_fields)
     64
     65    def create_base_fields_from_base_fields_pool(cls, new_cls):
     66        if new_cls._meta.fieldsets:
     67            names = []
     68            for fieldset in new_cls._meta.fieldsets:
     69                names.extend(fieldset['fields'])
     70        elif new_cls._meta.fields:
     71            names = new_cls._meta.fields
     72        elif new_cls._meta.exclude:
     73            names = [name for name in new_cls._base_fields_pool if name not in new_cls._meta.exclude]
     74        else:
     75            names = new_cls._base_fields_pool.keys()
     76        new_cls.base_fields = SortedDict([(name, new_cls._base_fields_pool[name]) for name in names])
     77    create_base_fields_from_base_fields_pool = classmethod(create_base_fields_from_base_fields_pool)
     78
    5779    def __new__(cls, name, bases, attrs):
    58         attrs['base_fields'] = get_declared_fields(bases, attrs)
    59         return type.__new__(cls, name, bases, attrs)
     80        new_cls = type.__new__(cls, name, bases, attrs)
     81        cls.create_options(new_cls)
     82        cls.create_declared_fields(new_cls)
     83        cls.create_base_fields_pool_from_declared_fields(new_cls)
     84        cls.create_base_fields_from_base_fields_pool(new_cls)
     85        return new_cls
    6086
    6187class BaseForm(StrAndUnicode):
    6288    # This is the main implementation of all the Form logic. Note that this
     
    98124        return BoundField(self, field, name)
    99125
    100126    def _get_errors(self):
    101         "Returns an ErrorDict for the data provided for the form"
     127        "Returns an ErrorDict for the data provided for the form."
    102128        if self._errors is None:
    103129            self.full_clean()
    104130        return self._errors
     
    111137        """
    112138        return self.is_bound and not bool(self.errors)
    113139
    114     def add_prefix(self, field_name):
     140    def has_fieldsets(self):
     141        "Returns True if this form has fieldsets."
     142        return bool(self._meta.fieldsets)
     143
     144    def add_prefix(self, name):
    115145        """
    116146        Returns the field name with a prefix appended, if this Form has a
    117147        prefix set.
    118148
    119149        Subclasses may wish to override.
    120150        """
    121         return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
    122 
    123     def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
     151        return self.prefix and ('%s-%s' % (self.prefix, name)) or name
     152
     153    def first_fieldset_attributes(self):
     154        "Returns attributes for first fieldset as HTML code."
     155        if self.has_fieldsets() and 'attrs' in self._meta.fieldsets[0]:
     156            return flatatt(self._meta.fieldsets[0]['attrs'])
     157        else:
     158            return u''
     159
     160    def first_fieldset_legend_tag(self):
     161        "Returns legend tag for first fieldset as HTML code."
     162        if self.has_fieldsets() and 'legend' in self._meta.fieldsets[0]:
     163            return mark_safe(u'<legend>%s</legend>' % conditional_escape(force_unicode(self._meta.fieldsets[0]['legend'])))
     164        else:
     165            return u''
     166
     167    def top_errors_html_output(self, top_errors, top_errors_row):
     168        "Helper function for outputting HTML from a top errors row. Used by _html_output."
     169        return top_errors_row % top_errors
     170
     171    def fieldset_html_output(self, fieldset, fields, fieldset_start, fieldset_end, is_first, is_last):
     172        "Helper function for outputting HTML from a fieldset. Used by _html_output."
     173        output = []
     174        if fieldset_start and not is_first:
     175            fieldset_attrs = flatatt(fieldset.get('attrs', {}))
     176            if 'legend' in fieldset:
     177                legend_tag = u'\n<legend>%s</legend>' % conditional_escape(force_unicode(fieldset['legend']))
     178            else:
     179                legend_tag = u''
     180            output.append(fieldset_start % (u'<fieldset%s>%s' % (fieldset_attrs, legend_tag)))
     181        for name in fieldset['fields']:
     182            output.append(fields[name])
     183        if fieldset_end and not is_last:
     184            output.append(fieldset_end % u'</fieldset>')
     185        return u'\n'.join(output)
     186
     187    def hidden_fields_html_output(self, hidden_fields, hidden_fields_row):
     188        "Helper function for outputting HTML from a hidden fields row. Used by _html_output."
     189        return hidden_fields_row % u''.join(hidden_fields)
     190
     191    def _html_output(self, output_type, top_errors_row, fieldset_start, fieldset_end, hidden_fields_row):
    124192        "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
     193        output = []
    125194        top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
    126         output, hidden_fields = [], []
     195        hidden_fields, visible_fields = [], {}
    127196        for name, field in self.fields.items():
    128197            bf = BoundField(self, field, name)
    129             bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
    130198            if bf.is_hidden:
    131                 if bf_errors:
    132                     top_errors.extend(['(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
     199                if bf.errors:
     200                    top_errors.extend(['(Hidden field %s) %s' % (name, conditional_escape(force_unicode(e))) for e in bf.errors])
    133201                hidden_fields.append(unicode(bf))
    134202            else:
    135                 if errors_on_separate_row and bf_errors:
    136                     output.append(error_row % force_unicode(bf_errors))
    137                 if bf.label:
    138                     label = escape(force_unicode(bf.label))
    139                     # Only add the suffix if the label does not end in
    140                     # punctuation.
    141                     if self.label_suffix:
    142                         if label[-1] not in ':?.!':
    143                             label += self.label_suffix
    144                     label = bf.label_tag(label) or ''
    145                 else:
    146                     label = ''
    147                 if field.help_text:
    148                     help_text = help_text_html % force_unicode(field.help_text)
    149                 else:
    150                     help_text = u''
    151                 output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
     203                visible_fields[name] = getattr(bf.widget, 'for_%s' % output_type)(rendered_widget=unicode(bf),
     204                    rendered_errors=unicode(bf.errors), label_tag=bf.label_tag, help_text=bf.help_text, row_attrs=bf.row_attrs)
    152205        if top_errors:
    153             output.insert(0, error_row % top_errors)
    154         if hidden_fields: # Insert any hidden fields in the last row.
    155             str_hidden = u''.join(hidden_fields)
    156             if output:
    157                 last_row = output[-1]
    158                 # Chop off the trailing row_ender (e.g. '</td></tr>') and
    159                 # insert the hidden fields.
    160                 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
    161             else:
    162                 # If there aren't any rows in the output, just append the
    163                 # hidden fields.
    164                 output.append(str_hidden)
     206            output.append(self.top_errors_html_output(top_errors, top_errors_row))
     207        if self.has_fieldsets():
     208            for i, fieldset in enumerate(self._meta.fieldsets):
     209                output_method = fieldset.get('html_output_method', self.__class__.fieldset_html_output)
     210                fields = dict((name, visible_fields[name]) for name in fieldset['fields'] if name in visible_fields)
     211                is_first = (i == 0)
     212                is_last = (i + 1 == len(self._meta.fieldsets))
     213                output.append(output_method(self, fieldset, fields, fieldset_start, fieldset_end, is_first, is_last))
     214        else:
     215            for name in self.fields:
     216                if name in visible_fields:
     217                    output.append(visible_fields[name])
     218        if hidden_fields:
     219            output.append(self.hidden_fields_html_output(hidden_fields, hidden_fields_row))
    165220        return mark_safe(u'\n'.join(output))
    166221
    167222    def as_table(self):
    168223        "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
    169         return self._html_output(u'<tr><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', u'<br />%s', False)
     224        return self._html_output('table', u'<tr><td colspan="2">%s</td></tr>', u'%s\n<table>', u'</table>\n%s', u'<tr><td colspan="2">%s</td></tr>')
    170225
    171226    def as_ul(self):
    172227        "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
    173         return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
     228        return self._html_output('ul', u'<li>%s</li>', u'%s\n<ul>', u'</ul>\n%s', u'<li>%s</li>')
    174229
    175230    def as_p(self):
    176231        "Returns this form rendered as HTML <p>s."
    177         return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
     232        return self._html_output('p', u'%s', u'%s', u'%s', u'<p>%s</p>')
    178233
    179234    def non_field_errors(self):
    180235        """
     
    209264                    value = getattr(self, 'clean_%s' % name)()
    210265                    self.cleaned_data[name] = value
    211266            except ValidationError, e:
    212                 self._errors[name] = e.messages
     267                self._errors[name] = self.error_class(e.messages)
    213268                if name in self.cleaned_data:
    214269                    del self.cleaned_data[name]
    215270        try:
    216271            self.cleaned_data = self.clean()
    217272        except ValidationError, e:
    218             self._errors[NON_FIELD_ERRORS] = e.messages
     273            self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
    219274        if self._errors:
    220275            delattr(self, 'cleaned_data')
    221276
     
    245300    # fancy metaclass stuff purely for the semantic sugar -- it allows one
    246301    # to define a form using declarative syntax.
    247302    # BaseForm itself has no way of designating self.fields.
    248     __metaclass__ = DeclarativeFieldsMetaclass
     303    __metaclass__ = FormMetaclass
     304    options = FormOptions
    249305
    250306class BoundField(StrAndUnicode):
    251307    "A Field plus data"
    252308    def __init__(self, form, field, name):
    253309        self.form = form
    254310        self.field = field
     311        self.widget = field.widget
    255312        self.name = name
    256313        self.html_name = form.add_prefix(name)
    257         if self.field.label is None:
    258             self.label = pretty_name(name)
    259         else:
    260             self.label = self.field.label
    261         self.help_text = field.help_text or ''
    262314
    263315    def __unicode__(self):
    264316        """Renders this field as an HTML widget."""
     
    279331        field's default widget will be used.
    280332        """
    281333        if not widget:
    282             widget = self.field.widget
     334            widget = self.widget
    283335        attrs = attrs or {}
    284336        auto_id = self.auto_id
    285337        if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
     
    312364        """
    313365        Returns the data for this BoundField, or None if it wasn't given.
    314366        """
    315         return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
     367        return self.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
    316368    data = property(_data)
    317369
    318     def label_tag(self, contents=None, attrs=None):
    319         """
    320         Wraps the given contents in a <label>, if the field has an ID attribute.
    321         Does not HTML-escape the contents. If contents aren't given, uses the
    322         field's HTML-escaped label.
    323 
    324         If attrs are given, they're used as HTML attributes on the <label> tag.
    325         """
    326         contents = contents or escape(self.label)
    327         widget = self.field.widget
    328         id_ = widget.attrs.get('id') or self.auto_id
    329         if id_:
    330             attrs = attrs and flatatt(attrs) or ''
    331             contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
    332         return mark_safe(contents)
     370    def _label(self):
     371        "Returns label for this field as safe HTML code."
     372        if self.field.label is None:
     373            return pretty_name(self.name)
     374        else:
     375            return conditional_escape(force_unicode(self.field.label))
     376    label = property(_label)
     377
     378    def _label_tag(self):
     379        "Returns label tag for this field as safe HTML code."
     380        label = self.label
     381        if self.form.label_suffix:
     382            # Only add the suffix if the label does not end in punctuation.
     383            if label and label[-1] not in ':?.!':
     384                label += self.form.label_suffix
     385        id_ = self.widget.attrs.get('id') or self.auto_id
     386        if label and id_:
     387            id_ = self.widget.id_for_label(id_)
     388            return mark_safe(self.widget.label_tag(id_, label))
     389        else:
     390            return label
     391    label_tag = property(_label_tag)
     392
     393    def _help_text(self):
     394        "Returns help text for this field as safe HTML code."
     395        if self.field.help_text:
     396            return force_unicode(self.field.help_text)
     397        else:
     398            return u''
     399    help_text = property(_help_text)
     400
     401    def _row_attrs(self):
     402        "Returns row attributes for this field as safe HTML code."
     403        return flatatt(self.widget.row_attrs)
     404    row_attrs = property(_row_attrs)
    333405
    334406    def _is_hidden(self):
    335407        "Returns True if this BoundField's widget is hidden."
    336         return self.field.widget.is_hidden
     408        return self.widget.is_hidden
    337409    is_hidden = property(_is_hidden)
    338410
    339411    def _auto_id(self):
  • django/newforms/models.py

    === modified file 'django/newforms/models.py'
     
    77
    88from django.utils.translation import ugettext_lazy as _
    99from django.utils.encoding import smart_unicode
    10 from django.utils.datastructures import SortedDict
    11 from django.core.exceptions import ImproperlyConfigured
     10from django.utils.datastructures import SortedDict, InheritableOptions
    1211
    1312from util import ValidationError, ErrorList
    14 from forms import BaseForm, get_declared_fields
     13from forms import FormOptions, FormMetaclass, BaseForm
    1514from fields import Field, ChoiceField, EMPTY_VALUES
    1615from widgets import Select, SelectMultiple, MultipleHiddenInput
    1716
     
    205204            field_list.append((f.name, formfield))
    206205    return SortedDict(field_list)
    207206
    208 class ModelFormOptions(object):
    209     def __init__(self, options=None):
    210         self.model = getattr(options, 'model', None)
    211         self.fields = getattr(options, 'fields', None)
    212         self.exclude = getattr(options, 'exclude', None)
    213 
    214 
    215 class ModelFormMetaclass(type):
    216     def __new__(cls, name, bases, attrs,
    217                 formfield_callback=lambda f: f.formfield()):
    218         try:
    219             parents = [b for b in bases if issubclass(b, ModelForm)]
    220         except NameError:
    221             # We are defining ModelForm itself.
    222             parents = None
    223         if not parents:
    224             return super(ModelFormMetaclass, cls).__new__(cls, name, bases,
    225                     attrs)
    226 
    227         new_class = type.__new__(cls, name, bases, attrs)
    228         declared_fields = get_declared_fields(bases, attrs, False)
    229         opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
    230         if opts.model:
    231             # If a model is defined, extract form fields from it.
    232             fields = fields_for_model(opts.model, opts.fields,
    233                                       opts.exclude, formfield_callback)
    234             # Override default model fields with any custom declared ones
    235             # (plus, include all the other declared fields).
    236             fields.update(declared_fields)
    237         else:
    238             fields = declared_fields
    239         new_class.declared_fields = declared_fields
    240         new_class.base_fields = fields
    241         return new_class
     207class ModelFormOptions(InheritableOptions):
     208    _model_options = {
     209        'model': None,
     210        'formfield_for_dbfield': lambda self, dbfield: dbfield.formfield(),
     211    }
     212    _default_options = FormOptions._default_options.copy()
     213    _default_options.update(_model_options)
     214
     215class ModelFormMetaclass(FormMetaclass):
     216
     217    def create_model_fields(cls, new_cls):
     218        fields = []
     219        if new_cls._meta.model:
     220            for dbfield in new_cls._meta.model._meta.fields + new_cls._meta.model._meta.many_to_many:
     221                if dbfield.editable:
     222                    formfield = new_cls._meta.formfield_for_dbfield(new_cls._meta, dbfield)
     223                    if formfield:
     224                        fields.append((dbfield.name, formfield))
     225        new_cls._model_fields = SortedDict(fields)
     226    create_model_fields = classmethod(create_model_fields)
     227
     228    def create_base_fields_pool_from_model_fields_and_declared_fields(cls, new_cls):
     229        model_fields = []
     230        declared_fields = []
     231        # Add all declared fields from this class and from superclasses. Note
     232        # that we loop over the bases in *reverse*. This is necessary
     233        # in order to preserve the correct order of fields. Also add first
     234        # model fields.
     235        for base in new_cls.__mro__[::-1]:
     236            try:
     237                declared_fields += base._declared_fields.items()
     238                if base._meta.model:
     239                    model_fields = base._model_fields.items()
     240            except AttributeError:
     241                pass
     242        new_cls._base_fields_pool = SortedDict(model_fields + declared_fields)
     243    create_base_fields_pool_from_model_fields_and_declared_fields = classmethod(create_base_fields_pool_from_model_fields_and_declared_fields)
     244
     245    def __new__(cls, name, bases, attrs):
     246        new_cls = type.__new__(cls, name, bases, attrs)
     247        cls.create_options(new_cls)
     248        cls.create_model_fields(new_cls)
     249        cls.create_declared_fields(new_cls)
     250        cls.create_base_fields_pool_from_model_fields_and_declared_fields(new_cls)
     251        cls.create_base_fields_from_base_fields_pool(new_cls)
     252        return new_cls
    242253
    243254class BaseModelForm(BaseForm):
    244255    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
     
    251262            object_data = {}
    252263        else:
    253264            self.instance = instance
    254             object_data = model_to_dict(instance, opts.fields, opts.exclude)
     265            object_data = model_to_dict(instance, self.base_fields.keys())
    255266        # if initial was provided, it should override the values from instance
    256267        if initial is not None:
    257268            object_data.update(initial)
     
    269280            fail_message = 'created'
    270281        else:
    271282            fail_message = 'changed'
    272         return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
     283        return save_instance(self, self.instance, self.base_fields.keys(), fail_message, commit)
    273284
    274285class ModelForm(BaseModelForm):
    275286    __metaclass__ = ModelFormMetaclass
     287    options = ModelFormOptions
    276288
    277289
    278290# Fields #####################################################################
  • django/newforms/util.py

    === modified file 'django/newforms/util.py'
     
    1 from django.utils.html import escape
     1from django.utils.html import conditional_escape
    22from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
    3 from django.utils.functional import Promise
    43from django.utils.safestring import mark_safe
    54
    65def flatatt(attrs):
     
    109    XML-style pairs.  It is assumed that the keys do not need to be XML-escaped.
    1110    If the passed dictionary is empty, then return an empty string.
    1211    """
    13     return u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
     12    return mark_safe(u''.join([u' %s="%s"' % (k, conditional_escape(v)) for k, v in attrs.items()]))
    1413
    1514class ErrorDict(dict, StrAndUnicode):
    1615    """
     
    2423    def as_ul(self):
    2524        if not self: return u''
    2625        return mark_safe(u'<ul class="errorlist">%s</ul>'
    27                 % ''.join([u'<li>%s%s</li>' % (k, force_unicode(v))
     26                % ''.join([u'<li>%s%s</li>' % (k, conditional_escape(force_unicode(v)))
    2827                    for k, v in self.items()]))
    2928
    3029    def as_text(self):
     
    4039    def as_ul(self):
    4140        if not self: return u''
    4241        return mark_safe(u'<ul class="errorlist">%s</ul>'
    43                 % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
     42                % ''.join([u'<li>%s</li>' % conditional_escape(force_unicode(e)) for e in self]))
    4443
    4544    def as_text(self):
    4645        if not self: return u''
  • django/newforms/widgets.py

    === modified file 'django/newforms/widgets.py'
     
    2929    is_hidden = False          # Determines whether this corresponds to an <input type="hidden">.
    3030    needs_multipart_form = False # Determines does this widget need multipart-encrypted form
    3131
    32     def __init__(self, attrs=None):
     32    def __init__(self, attrs=None, row_attrs=None):
    3333        if attrs is not None:
    3434            self.attrs = attrs.copy()
    3535        else:
    3636            self.attrs = {}
     37        if row_attrs is not None:
     38            self.row_attrs = row_attrs.copy()
     39        else:
     40            self.row_attrs = {}
    3741
    3842    def __deepcopy__(self, memo):
    3943        obj = copy.copy(self)
    4044        obj.attrs = self.attrs.copy()
     45        obj.row_attrs = self.row_attrs.copy()
    4146        memo[id(self)] = obj
    4247        return obj
    4348
     
    7782        return id_
    7883    id_for_label = classmethod(id_for_label)
    7984
     85    def label_tag(self, id_, label):
     86        "Returns label rendered as HTML <label>."
     87        return u'<label for="%s">%s</label>' % (id_, label)
     88
     89    def for_table(self, rendered_widget, rendered_errors, label_tag, help_text, row_attrs):
     90        "Returns this widget rendered as HTML <tr>."
     91        if help_text:
     92            help_text = u'<br />%s' % help_text
     93        return u'<tr%(row_attrs)s><th>%(label_tag)s</th><td>%(rendered_errors)s%(rendered_widget)s%(help_text)s</td></tr>' % locals()
     94
     95    def for_ul(self, rendered_widget, rendered_errors, label_tag, help_text, row_attrs):
     96        "Returns this widget rendered as HTML <li>."
     97        if help_text:
     98            help_text = u' %s' % help_text
     99        return u'<li%(row_attrs)s>%(rendered_errors)s%(label_tag)s %(rendered_widget)s%(help_text)s</li>' % locals()
     100
     101    def for_p(self, rendered_widget, rendered_errors, label_tag, help_text, row_attrs):
     102        "Returns this widget rendered as HTML <p>."
     103        if help_text:
     104            help_text = u' %s' % help_text
     105        return u'%(rendered_errors)s<p%(row_attrs)s>%(label_tag)s %(rendered_widget)s%(help_text)s</p>' % locals()
     106
    80107class Input(Widget):
    81108    """
    82109    Base class for all <input> widgets (except type='checkbox' and
     
    98125class PasswordInput(Input):
    99126    input_type = 'password'
    100127
    101     def __init__(self, attrs=None, render_value=True):
    102         super(PasswordInput, self).__init__(attrs)
     128    def __init__(self, attrs=None, row_attrs=None, render_value=True):
     129        super(PasswordInput, self).__init__(attrs, row_attrs)
    103130        self.render_value = render_value
    104131
    105132    def render(self, name, value, attrs=None):
     
    115142    A widget that handles <input type="hidden"> for fields that have a list
    116143    of values.
    117144    """
    118     def __init__(self, attrs=None, choices=()):
    119         super(MultipleHiddenInput, self).__init__(attrs)
     145    def __init__(self, attrs=None, row_attrs=None, choices=()):
     146        super(MultipleHiddenInput, self).__init__(attrs, row_attrs)
    120147        # choices can be any iterable
    121148        self.choices = choices
    122149
     
    144171        return files.get(name, None)
    145172
    146173class Textarea(Widget):
    147     def __init__(self, attrs=None):
     174    def __init__(self, attrs=None, row_attrs=None):
    148175        # The 'rows' and 'cols' attributes are required for HTML correctness.
    149         self.attrs = {'cols': '40', 'rows': '10'}
     176        default_attrs = {'cols': '40', 'rows': '10'}
    150177        if attrs:
    151             self.attrs.update(attrs)
     178            default_attrs.update(attrs)
     179        super(Textarea, self).__init__(default_attrs, row_attrs)
    152180
    153181    def render(self, name, value, attrs=None):
    154182        if value is None: value = ''
     
    161189    input_type = 'text'
    162190    format = '%Y-%m-%d %H:%M:%S'     # '2006-10-25 14:30:59'
    163191
    164     def __init__(self, attrs=None, format=None):
    165         super(DateTimeInput, self).__init__(attrs)
     192    def __init__(self, attrs=None, row_attrs=None, format=None):
     193        super(DateTimeInput, self).__init__(attrs, row_attrs)
    166194        if format:
    167195            self.format = format
    168196
     
    174202        return super(DateTimeInput, self).render(name, value, attrs)
    175203
    176204class CheckboxInput(Widget):
    177     def __init__(self, attrs=None, check_test=bool):
    178         super(CheckboxInput, self).__init__(attrs)
     205    def __init__(self, attrs=None, row_attrs=None, check_test=bool):
     206        super(CheckboxInput, self).__init__(attrs, row_attrs)
    179207        # check_test is a callable that takes a value and returns True
    180208        # if the checkbox should be checked for that value.
    181209        self.check_test = check_test
     
    201229        return super(CheckboxInput, self).value_from_datadict(data, files, name)
    202230
    203231class Select(Widget):
    204     def __init__(self, attrs=None, choices=()):
    205         super(Select, self).__init__(attrs)
     232    def __init__(self, attrs=None, row_attrs=None, choices=()):
     233        super(Select, self).__init__(attrs, row_attrs)
    206234        # choices can be any iterable, but we may need to render this widget
    207235        # multiple times. Thus, collapse it into a list so it can be consumed
    208236        # more than once.
     
    227255    """
    228256    A Select Widget intended to be used with NullBooleanField.
    229257    """
    230     def __init__(self, attrs=None):
     258    def __init__(self, attrs=None, row_attrs=None):
    231259        choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No')))
    232         super(NullBooleanSelect, self).__init__(attrs, choices)
     260        super(NullBooleanSelect, self).__init__(attrs, row_attrs, choices)
    233261
    234262    def render(self, name, value, attrs=None, choices=()):
    235263        try:
     
    242270        value = data.get(name, None)
    243271        return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
    244272
    245 class SelectMultiple(Widget):
    246     def __init__(self, attrs=None, choices=()):
    247         super(SelectMultiple, self).__init__(attrs)
    248         # choices can be any iterable
    249         self.choices = choices
    250 
     273class SelectMultiple(Select):
    251274    def render(self, name, value, attrs=None, choices=()):
    252275        if value is None: value = []
    253276        final_attrs = self.build_attrs(attrs, name=name)
     
    406429
    407430    You'll probably want to use this class with MultiValueField.
    408431    """
    409     def __init__(self, widgets, attrs=None):
     432    def __init__(self, widgets, attrs=None, row_attrs=None):
    410433        self.widgets = [isinstance(w, type) and w() or w for w in widgets]
    411         super(MultiWidget, self).__init__(attrs)
     434        super(MultiWidget, self).__init__(attrs, row_attrs)
    412435
    413436    def render(self, name, value, attrs=None):
    414437        # value is a list of values, each corresponding to a widget
     
    460483    """
    461484    A Widget that splits datetime input into two <input type="text"> boxes.
    462485    """
    463     def __init__(self, attrs=None):
     486    def __init__(self, attrs=None, row_attrs=None):
    464487        widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
    465         super(SplitDateTimeWidget, self).__init__(widgets, attrs)
     488        super(SplitDateTimeWidget, self).__init__(widgets, attrs, row_attrs)
    466489
    467490    def decompress(self, value):
    468491        if value:
  • django/utils/datastructures.py

    === modified file 'django/utils/datastructures.py'
     
    339339            d = dict(self, content='<omitted>')
    340340            return dict.__repr__(d)
    341341        return dict.__repr__(self)
     342
     343class InheritableOptions(object):
     344    """
     345    A structure for meta attributes. Each subclass must have _defined_options
     346    with names and default values.
     347    """
     348    def __init__(self, cls):
     349        # Start with default options.
     350        self.__dict__.update(self._default_options)
     351        # Add options defined in bases - loop over them in *reverse*.
     352        for base in cls.__mro__[::-1]:
     353            try:
     354                self.__dict__.update(base._meta._defined_options)
     355            except AttributeError:
     356                pass
     357        # Add options defined here.
     358        self._defined_options = {}
     359        for name in self._default_options:
     360            try:
     361                option = getattr(cls.Meta, name)
     362                # Use function if is available.
     363                try:
     364                    self._defined_options[name] = option.im_func
     365                except AttributeError:
     366                    self._defined_options[name] = option
     367            except AttributeError:
     368                pass
     369        self.__dict__.update(self._defined_options)
  • tests/modeltests/model_forms/models.py

    === modified file 'tests/modeltests/model_forms/models.py'
     
    144144...         exclude = ['url']
    145145
    146146>>> CategoryForm.base_fields.keys()
    147 ['name']
     147['name', 'url']
    148148
    149149Don't allow more than one 'model' definition in the inheritance hierarchy.
    150150Technically, it would generate a valid form, but the fact that the resulting
     
    194194...     class Meta:
    195195...         model = Category
    196196>>> class SubCategoryForm(CategoryForm):
    197 ...     class Meta(CategoryForm.Meta):
     197...     class Meta:
    198198...         exclude = ['url']
    199199
    200200>>> print SubCategoryForm()
  • tests/regressiontests/forms/extra.py

    === modified file 'tests/regressiontests/forms/extra.py'
     
    372372>>> f = CommentForm(data, auto_id=False, error_class=DivErrorList)
    373373>>> print f.as_p()
    374374<p>Name: <input type="text" name="name" maxlength="50" /></p>
    375 <div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div>
    376 <p>Email: <input type="text" name="email" value="invalid" /></p>
    377 <div class="errorlist"><div class="error">This field is required.</div></div>
    378 <p>Comment: <input type="text" name="comment" /></p>
     375<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div><p>Email: <input type="text" name="email" value="invalid" /></p>
     376<div class="errorlist"><div class="error">This field is required.</div></div><p>Comment: <input type="text" name="comment" /></p>
    379377
    380378#################################
    381379# Test multipart-encoded form #
  • tests/regressiontests/forms/forms.py

    === modified file 'tests/regressiontests/forms/forms.py'
     
    8989<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
    9090<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
    9191>>> print p.as_p()
    92 <ul class="errorlist"><li>This field is required.</li></ul>
    93 <p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
    94 <ul class="errorlist"><li>This field is required.</li></ul>
    95 <p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
    96 <ul class="errorlist"><li>This field is required.</li></ul>
    97 <p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
     92<ul class="errorlist"><li>This field is required.</li></ul><p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
     93<ul class="errorlist"><li>This field is required.</li></ul><p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
     94<ul class="errorlist"><li>This field is required.</li></ul><p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
    9895
    9996If you don't pass any values to the Form's __init__(), or if you pass None,
    10097the Form will be considered unbound and won't do any validation. Form.errors
     
    543540...     composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=MultipleHiddenInput)
    544541>>> f = SongFormHidden(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])), auto_id=False)
    545542>>> print f.as_ul()
    546 <li>Name: <input type="text" name="name" value="Yesterday" /><input type="hidden" name="composers" value="J" />
     543<li>Name: <input type="text" name="name" value="Yesterday" /></li>
     544<li><input type="hidden" name="composers" value="J" />
    547545<input type="hidden" name="composers" value="P" /></li>
    548546
    549547When using CheckboxSelectMultiple, the framework expects a list of input and
     
    768766>>> print p
    769767<tr><th>First name:</th><td><input type="text" name="first_name" /></td></tr>
    770768<tr><th>Last name:</th><td><input type="text" name="last_name" /></td></tr>
    771 <tr><th>Birthday:</th><td><input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></td></tr>
     769<tr><th>Birthday:</th><td><input type="text" name="birthday" /></td></tr>
     770<tr><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr>
    772771>>> print p.as_ul()
    773772<li>First name: <input type="text" name="first_name" /></li>
    774773<li>Last name: <input type="text" name="last_name" /></li>
    775 <li>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></li>
     774<li>Birthday: <input type="text" name="birthday" /></li>
     775<li><input type="hidden" name="hidden_text" /></li>
    776776>>> print p.as_p()
    777777<p>First name: <input type="text" name="first_name" /></p>
    778778<p>Last name: <input type="text" name="last_name" /></p>
    779 <p>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></p>
     779<p>Birthday: <input type="text" name="birthday" /></p>
     780<p><input type="hidden" name="hidden_text" /></p>
    780781
    781782With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label.
    782783>>> p = Person(auto_id='id_%s')
    783784>>> print p
    784785<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
    785786<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
    786 <tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr>
     787<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
     788<tr><td colspan="2"><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr>
    787789>>> print p.as_ul()
    788790<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
    789791<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
    790 <li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></li>
     792<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
     793<li><input type="hidden" name="hidden_text" id="id_hidden_text" /></li>
    791794>>> print p.as_p()
    792795<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
    793796<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
    794 <p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></p>
     797<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
     798<p><input type="hidden" name="hidden_text" id="id_hidden_text" /></p>
    795799
    796800If a field with a HiddenInput has errors, the as_table() and as_ul() output
    797801will include the error message(s) with the text "(Hidden field [fieldname]) "
     
    802806<tr><td colspan="2"><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></td></tr>
    803807<tr><th>First name:</th><td><input type="text" name="first_name" value="John" /></td></tr>
    804808<tr><th>Last name:</th><td><input type="text" name="last_name" value="Lennon" /></td></tr>
    805 <tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></td></tr>
     809<tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /></td></tr>
     810<tr><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr>
    806811>>> print p.as_ul()
    807812<li><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></li>
    808813<li>First name: <input type="text" name="first_name" value="John" /></li>
    809814<li>Last name: <input type="text" name="last_name" value="Lennon" /></li>
    810 <li>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></li>
     815<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /></li>
     816<li><input type="hidden" name="hidden_text" /></li>
    811817>>> print p.as_p()
    812818<ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul>
    813819<p>First name: <input type="text" name="first_name" value="John" /></p>
    814820<p>Last name: <input type="text" name="last_name" value="Lennon" /></p>
    815 <p>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></p>
     821<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /></p>
     822<p><input type="hidden" name="hidden_text" /></p>
    816823
    817824A corner case: It's possible for a form to have only HiddenInputs.
    818825>>> class TestForm(Form):
     
    820827...     bar = CharField(widget=HiddenInput)
    821828>>> p = TestForm(auto_id=False)
    822829>>> print p.as_table()
    823 <input type="hidden" name="foo" /><input type="hidden" name="bar" />
     830<tr><td colspan="2"><input type="hidden" name="foo" /><input type="hidden" name="bar" /></td></tr>
    824831>>> print p.as_ul()
    825 <input type="hidden" name="foo" /><input type="hidden" name="bar" />
     832<li><input type="hidden" name="foo" /><input type="hidden" name="bar" /></li>
    826833>>> print p.as_p()
    827 <input type="hidden" name="foo" /><input type="hidden" name="bar" />
     834<p><input type="hidden" name="foo" /><input type="hidden" name="bar" /></p>
    828835
    829836A Form's fields are displayed in the same order in which they were defined.
    830837>>> class TestForm(Form):
     
    11751182>>> p = UserRegistration(auto_id=False)
    11761183>>> print p.as_ul()
    11771184<li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li>
    1178 <li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li>
     1185<li>Password: <input type="password" name="password" /></li>
     1186<li><input type="hidden" name="next" value="/" /></li>
    11791187
    11801188Help text can include arbitrary Unicode characters.
    11811189>>> class UserRegistration(Form):
     
    12191227...     haircut_type = CharField()
    12201228>>> b = Beatle(auto_id=False)
    12211229>>> print b.as_ul()
     1230<li>Instrument: <input type="text" name="instrument" /></li>
    12221231<li>First name: <input type="text" name="first_name" /></li>
    12231232<li>Last name: <input type="text" name="last_name" /></li>
    12241233<li>Birthday: <input type="text" name="birthday" /></li>
    1225 <li>Instrument: <input type="text" name="instrument" /></li>
    12261234<li>Haircut type: <input type="text" name="haircut_type" /></li>
    12271235
    12281236# Forms with prefixes #########################################################
     
    15241532Recall from above that passing the "auto_id" argument to a Form gives each
    15251533field an "id" attribute.
    15261534>>> t = Template('''<form action="">
    1527 ... <p>{{ form.username.label_tag }}: {{ form.username }}</p>
    1528 ... <p>{{ form.password1.label_tag }}: {{ form.password1 }}</p>
    1529 ... <p>{{ form.password2.label_tag }}: {{ form.password2 }}</p>
     1535... <p>{{ form.username.label_tag }} {{ form.username }}</p>
     1536... <p>{{ form.password1.label_tag }} {{ form.password1 }}</p>
     1537... <p>{{ form.password2.label_tag }} {{ form.password2 }}</p>
    15301538... <input type="submit" />
    15311539... </form>''')
    15321540>>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
     
    15381546</form>
    15391547>>> print t.render(Context({'form': UserRegistration(auto_id='id_%s')}))
    15401548<form action="">
    1541 <p><label for="id_username">Username</label>: <input id="id_username" type="text" name="username" maxlength="10" /></p>
    1542 <p><label for="id_password1">Password1</label>: <input type="password" name="password1" id="id_password1" /></p>
    1543 <p><label for="id_password2">Password2</label>: <input type="password" name="password2" id="id_password2" /></p>
     1549<p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>
     1550<p><label for="id_password1">Password1:</label> <input type="password" name="password1" id="id_password1" /></p>
     1551<p><label for="id_password2">Password2:</label> <input type="password" name="password2" id="id_password2" /></p>
    15441552<input type="submit" />
    15451553</form>
    15461554
    15471555User form.[field].help_text to output a field's help text. If the given field
    15481556does not have help text, nothing will be output.
    15491557>>> t = Template('''<form action="">
    1550 ... <p>{{ form.username.label_tag }}: {{ form.username }}<br />{{ form.username.help_text }}</p>
    1551 ... <p>{{ form.password1.label_tag }}: {{ form.password1 }}</p>
    1552 ... <p>{{ form.password2.label_tag }}: {{ form.password2 }}</p>
     1558... <p>{{ form.username.label_tag }} {{ form.username }}<br />{{ form.username.help_text }}</p>
     1559... <p>{{ form.password1.label_tag }} {{ form.password1 }}</p>
     1560... <p>{{ form.password2.label_tag }} {{ form.password2 }}</p>
    15531561... <input type="submit" />
    15541562... </form>''')
    15551563>>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
     
    15621570>>> Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)}))
    15631571u''
    15641572
    1565 The label_tag() method takes an optional attrs argument: a dictionary of HTML
    1566 attributes to add to the <label> tag.
    1567 >>> f = UserRegistration(auto_id='id_%s')
    1568 >>> for bf in f:
    1569 ...     print bf.label_tag(attrs={'class': 'pretty'})
    1570 <label for="id_username" class="pretty">Username</label>
    1571 <label for="id_password1" class="pretty">Password1</label>
    1572 <label for="id_password2" class="pretty">Password2</label>
    1573 
    15741573To display the errors that aren't associated with a particular field -- e.g.,
    15751574the errors caused by Form.clean() -- use {{ form.non_field_errors }} in the
    15761575template. If used on its own, it is displayed as a <ul> (or an empty string, if
  • tests/regressiontests/forms/regressions.py

    === modified file 'tests/regressiontests/forms/regressions.py'
     
    5656>>> activate('ru')
    5757>>> f = SomeForm({})
    5858>>> f.as_p()
    59 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>'
     59u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul><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>'
    6060>>> deactivate()
    6161
    6262Deep copying translated text shouldn't raise an error
Back to Top